本教程介绍如何在同一台服务器上使用Docker部署Web应用程序,Redis,Postgres和Nginx。在本教程中,Web应用程序是node.js(express)应用程序。我们使用Redis作为缓存存储,Postgres作为数据库,Nginx作为反向代理服务器。您可以访问https://github.com/vinceyuan/DockerizingWebAppTutorial获取所有源代码。

为何选择Docker

Docker是一种虚拟化技术。我最喜欢的关键功能是提供资源隔离。构建(低流量)网站的传统方式是我们直接在服务器上安装Web应用程序,缓存,数据库,Nginx。改变设置或内容并不容易,因为它们处于相同的环境中。改变一个可能会影响其他人 使用Docker,我们可以将每个服务放在一个容器中。它使主机服务器非常干净。我们可以轻松创建/删除/更改/重新创建容器。

在主机上安装Docker

Docker仅在64位Linux操作系统上运行。如果您的Linux是32位,则必须重新安装64位版本。我原来的操作系统是32位CentOS。现在我使用的是64位Debian 8.我选择Debian的主要原因是它的分发大小很小,Docker推荐它采用最佳实践(这很荒谬,几乎所有docker.com上的例子都使用ubuntu)。实际上主机的操作系统可能与容器的操作系统不同。我选择Debian而不是64位CentOS,因为我不想在差异上花费任何时间。例如,Debian和CentOS上的包管理工具是不同的。一个是贴切的,另一个是yum。

目前,Docker 在Debian 8上的官方安装不起作用。您需要以root身份运行以下命令。用户是主机操作系统的用户。

sudo su
curl -sSL https://get.docker.com/ | sh
# Add theuser to docker group to run docker as a non-root user
# MUST logout and re-login to let it effective
usermod -aG docker theuser
# Start docker service
systemctl enable docker.service
systemctl start docker.service

准备

cd /
git clone https://github.com/vinceyuan/DockerizingWebAppTutorial.git

该文件夹/DockerizingWebAppTutorial包含我们所需的一切。mynodeapp是一个非常简单的node.js(express)app。它只是从Redis读取一个数字,并从Postgres获取查询结果。dockerfiles文件夹中有几个Dockerfiles。我们将使用它们来构建图像。

创建文件夹:

cd / && mkdir mydata && cd myata
mkdir redis_data && mkdir postgres_data && mkdir nginx_data
root@pophubserver:/mydata# mkdir log_mynodeapp && mkdir log_nginx

让我们运行第一个容器。

Redis的

我们使用Redis官方图片。使用以下命令直接运行它:

docker run -d -v /mydata/redis_data:/data --name myredis --restart=always redis

-v /mydata/redis_data:/data表示我们将/mydata/redis_data主机的文件夹作为卷/data装入容器中。Nginx将保存dump.rdb/mydata/redis_data主机中。如果我们不挂载卷,Nginx将保存dump.rdb在容器中。删除此容器时,dump.rdb也会将其删除。所以我们应该总是为重要数据安装一个卷,例如数据库文件,日志。 --name myredis表示我们将此容器命名--restart=always为myredis 表示容器在意外退出后将重新启动。它还使容器在服务器重新启动后自动启动。

该命令输出:

$ docker run -d -v /mydata/redis_data:/data --name myredis --restart=always redis
Unable to find image 'redis:latest' locally
latest: Pulling from redis
7a3e804ed6c0: Pull complete 
b96d1548a24e: Pull complete 
5ba9a5b9710f: Pull complete 
37f07aacbfe5: Pull complete 
ec7f3a6b5dc6: Pull complete 
499b313c4d4e: Pull complete 
4416945429c6: Pull complete 
0daf71066555: Pull complete 
1f86439b265d: Pull complete 
9e6288fa06c0: Pull complete 
3c083702089f: Pull complete 
71cc4c7123fc: Pull complete 
91e5e3734476: Pull complete 
8d7fb9bd09ab: Pull complete 
e6b7cf8bf1b1: Pull complete 
96182c1bd121: Pull complete 
4b7672067154: Already exists 
redis:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:01b59520487a9ada4b8e31558c0580930a4e5f2a565a1cb85b66efe7c6ce810d
Status: Downloaded newer image for redis:latest

a96b6d2555e9f9fb1f70fea60f8cf75326cd331ebef9d4b667e322cea899d48c

redis:latest从Docker Hub 下载图像。我们来看看myredis容器是否正在运行。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
a96b6d2555e9        redis:latest        "/entrypoint.sh redi   16 minutes ago      Up 16 minutes       6379/tcp            myredis

我们可以看到myredis正在运行。

我们需要redis-cli在此容器中运行以在Redis中设置值。

$ docker exec -i -t myredis bash
root@a96b6d2555e9:/data# redis-cli
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
root@a96b6d2555e9:/data# exit

Postgres的

我们也使用官方的Postgres图像。直接运行它。

docker run -d --name mypostgres -e POSTGRES_PASSWORD=postgres -v /mydata/postgres_data:/var/lib/postgresql/data --restart=always postgres

-e POSTGRES_PASSWORD=postgres意味着我们将环境变量设置POSTGRES_PASSWORD为postgres。

-v /mydata/postgres_data:/var/lib/postgresql/data意味着我们/mydata/postgres_data作为一个卷安装。这个非常重要。将数据库文件保存在主机中是安全的。

创建mynodeappdb:

$ docker exec -i -t mypostgres bash
root@11602c44f706:/# psql -U postgres
psql (9.4.2)
Type "help" for help.

postgres=# create database mynodeappdb;
CREATE DATABASE
postgres=# \q

root@11602c44f706:/# exit

我们可以看到mypostgres和myredis正在运行。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS               NAMES
11602c44f706        postgres:latest     "/docker-entrypoint.   About a minute ago   Up About a minute   5432/tcp            mypostgres          
a96b6d2555e9        redis:latest        "/entrypoint.sh redi   32 minutes ago       Up 32 minutes       6379/tcp            myredis

Redis客户端和Postgres客户端

Redis客户端的Dockerfile:

FROM debian:7

RUN apt-get update \
 && apt-get install -y redis-server \
 && apt-get clean \
 && apt-get autoremove \
 && rm -rf /var/lib/apt/lists/*

RUN service redis-server stop

它基于debian:7。它实际上安装了redis服务器和客户端。但我们只需要客户。所以它停止了redis-server。

建立它:

cd /DockerizingWebAppTutorial/dockerfiles/myredisclient
docker build -t myredisclient .

Postgres客户端的Dockerfile:

FROM myredisclient

RUN apt-get update \
 && apt-get install -y wget \
 && echo 'deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main' >> /etc/apt/sources.list.d/pgdg.list \
 && wget --no-check-certificate --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
 && apt-get update \
 && apt-get install -y --force-yes postgresql-client \
 && apt-get clean \
 && apt-get autoremove \
 && rm -rf /var/lib/apt/lists/*

它基于myredisclient,因为我们的网络应用程序需要访问redis和postgres。令人讨厌的是Debian apt中的默认postgresql-client是一个非常旧的版本(pg_dump不起作用,因为版本与服务器的版本不匹配)。此Dockerfile安装最新版本(目前为9.4)。

建立它

cd /DockerizingWebAppTutorial/dockerfiles/myredispgclient
docker build -t myredispgclient .

我们可以看到主机中有5张图片。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
myredispgclient     latest              78b18351c561        6 minutes ago       132.5 MB
myredisclient       latest              bb2ac4846244        8 minutes ago       87.7 MB
postgres            latest              1636d90f0662        2 days ago          214 MB
redis               latest              4b7672067154        4 days ago          111 MB
debian              7                   b96d1548a24e        9 days ago          84.97 MB

Node.js的

让我们构建一个Node.js图像。在mynodejs映像的Dockerfile中,我们永远安装node.js,express,然后设置NODE_ENV生产。在这个例子中,我没有使用最新版本。

FROM myredispgclient

RUN apt-get update \
 && apt-get install -y --force-yes --no-install-recommends \
      apt-transport-https \
      build-essential \
      curl \
      ca-certificates \
      git \
      lsb-release \
      python-all \
      rlwrap \
 && apt-get clean \
 && apt-get autoremove \
 && rm -rf /var/lib/apt/lists/*

RUN curl https://deb.nodesource.com/node/pool/main/n/nodejs/nodejs_0.10.30-1nodesource1~wheezy1_amd64.deb > node.deb \
 && dpkg -i node.deb \
 && rm node.deb

RUN npm install -g express@3.4.7 \
 && npm install -g forever \
 && npm cache clear

ENV NODE_ENV production

建立它。

cd /DockerizingWebAppTutorial/dockerfiles/mynodejs
docker build -t mynodejs .

mynodeapp

然后我们为mynodeapp构建一个图像。在Dockerfile中,我们运行npm install,并使用forever来运行node.js应用程序。我们不使用forever start,因为我们不将其作为守护进程运行(否则,容器将立即退出)。

FROM mynodejs

COPY . /src
RUN cd /src && npm install
VOLUME /log
CMD ["forever", "-l", "/log/forever.log", "-o", "/log/out.log", "-e", "/log/err.log", "/src/app.js"]

建立它

cd /DockerizingWebAppTutorial/mynodeapp
docker build -t mynodeapp .

实际上我们可以将这4个Dockerfiles合并为一个来创建一个图像。我构建了4个图像以重复使用图像。例如,如果我们想为另一个node.js app构建一个图像,我们可以根据mynodejs图像编写一个Dockerfile。如果我们想用Go替换node.js,我们可以编写一个基于myredispgclient的Dockerfile。

mynodeapp的核心代码:

var conString;

if ('development' == app.get('env')) {
  app.use(express.errorHandler());
  conString = "postgres://vince:@localhost/mynodeappdb"; // Use your db, user and password
} else {
 conString = "postgres://postgres:postgres@localhost/mynodeappdb"; // Use your db, user and password
}

var pgClient = new pg.Client(conString);

pgClient.connect(function(err) {
  if(err) return console.error('Could not connect to postgres', err);
  console.log('Connected to postgres');
});

var redisClient = redis.createClient(6379, '127.0.0.1', {})

app.get('/', function(req, res) {
  pgClient.query('SELECT NOW() AS "theTime"', function(err1, result) {
    redisClient.get("number", function(err2, reply) {
      res.render('index', { pgTime: result.rows[0].theTime, number: reply });
    });
  }); // Errors Ignored in this example. You should check errors in a real project. 
});

http.createServer(app).listen(app.get('port'), function() {
  console.log('Express server listening on port ' + app.get('port'));
});

有一个问题。我们正在使用localhost127.0.0.1用于redis和postgres的主机地址。它仅在它们安装在同一服务器上时才有效。但现在他们在不同的容器中。即使我们使用--link,我们仍然无法通过localhost和访问它们127.0.0.1。我们可以使用以下代码来获取正确的主机和端口。

var redis_host = process.env.REDIS_PORT_6379_TCP_ADDR || '127.0.0.1';
var redis_port = process.env.REDIS_PORT_6379_TCP_PORT || 6379;
var db_host = process.env.POSTGRES_PORT_5432_TCP_ADDR || 'localhost';

REDIS_PORT_6379_TCP_ADDR如果您运行容器,则由Docker创建--link myredis:redis。您也可以从环境变量中获取Postgres用户帐户,密码和端口。

基于mynodeapp映像运行容器。我们还将容器命名为mynodeapp。您可以随意重命名。

docker run -d --name mynodeapp --link mypostgres:postgres --link myredis:redis -v /mydata/log_mynodeapp:/log -p 3000:3000 --restart=always mynodeapp

默认情况下,每个容器都是隔离的。--link允许容器访问另一个容器。--link mypostgres:postgres意味着我们可以用别名访问mypostgres容器postgres一样localhost127.0.0.1。 -v /mydata/log_mynodeapp:/log安装一个卷。我们希望将日志保留在主机中。 -p 3000:3000将主机的端口3000映射到容器的端口3000.这不是强制性的。但有了它,我们可以curl localhost:3000在主机中使用来检查mynodeapp容器是否正确运行。

$ curl localhost:3000
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><H1>mynodeapp</H1><p>Number from Redis: 1</p><p>Time from Postgres: Fri May 29 2015 09:47:54 GMT+0000 (UTC)</p></body></html>

Web应用程序在容器中正确运行。

Nginx的

现在我们安装Nginx。在Dockerfile中,我们创建目录/mynodeapp/public。主机中的文件夹将安装在此处。

FROM nginx

# Create folder for static files
RUN mkdir /mynodeapp && mkdir /mynodeapp/public

# copy sslcert files to /etc/nginx/ for https
#COPY mydomain.* /etc/nginx/

# copy conf
COPY nginx-docker.conf /etc/nginx/nginx.conf

在nginx-docker.conf中,我们使用mynodeapp作为服务器地址,因为它是链接的。

    upstream mynodeapp_upstream {
        server mynodeapp:3000;
        keepalive 64;
    }

构建映像并运行容器。

cd /DockerizingWebAppTutorial/dockerfiles/mynginx
docker build -t mynginx .

运行mynginx容器。

docker run -d --name mynginx --link mynodeapp:mynodeapp -v /mydata/nginx_data:/var/cache/nginx -v /mydata/log_nginx:/var/log/nginx -v /DockerizingWebAppTutorial/mynodeapp/public:/mynodeapp/public -p 80:80 -p 443:443 --restart=always mynginx

--link mynodeapp:mynodeapp意味着我们将mynodeapp容器链接到mynginx容器。我们没有链接myredis和mypostgres,因为mynginx不直接访问它们。我们还安装了2个文件夹用于记录。 -p 443:443是为了https。但是,此示例不提供ssl证书文件。

运行curl localhostcurl localhost/stylesheets/style.css检查mynginx是否正确运行。

# curl localhost
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><H1>mynodeapp</H1><p>Number from Redis: 1</p><p>Time from Postgres: Fri May 29 2015 10:12:35 GMT+0000 (UTC)</p></body></html>root@pophubserver:/DockerizingWebAppTutorial/dockerfiles/mynginx# 

root@pophubserver:/DockerizingWebAppTutorial/dockerfiles/mynginx# curl localhost/stylesheets/style.css
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

现在我们完成了使用Docker部署Web应用程序Redis,Postgres和Nginx。我花了很多时间用Docker真正部署我的真实应用程序。幸运的是我在VirtualBox VM中测试过。我可以使用Docker轻松地来回删除/创建图像/容器。

缺少一个重要的部分。那是恢复和备份数据库。