前言
在这个例子中,需要做的是把一个使用Express
框架的并且带有Redis
后端的Node.js
应用完全Docker化。目的是能够将Docker的特性结合起来使用。主要的工作内容是
- 一个Node容器,用来服务于Node应用
- 一个Redis主容器,用于保存和集群化应用状态
- 两个Redis副本容器,用于集群化应用状态
- 一个日志容器,用于捕获应用日志
最终我们的Node应用程序会运行在一个容器中,它后面会有一个配置为”主-副本”模式运行在多个容器中的Redis集群。
Node.js镜像
首先我们需要构建一个安装了Node.js的镜像。这个镜像有Express应用和相应的必要的软件包。
第一步先在本地创建一个名为nodejs
的文件夹
1 | mkdir nodejs && cd nodejs |
第二步在nodejs
文件夹下创建一个存放node.js
应用源码的文件夹名为nodeapp
1 | mkdir nodeapp && cd nodeapp |
获取node.js
源码
如果下载不下来的话,代码地址:https://github.com/turnbullpress/dockerbook-code/tree/master/code/6/node/nodejs/nodeapp
1 | wget https://raw.githubusercontent.com/turnbullpress/dockerbook-code/master/code/6/node/nodejs/nodeapp/package.json |
1 | wget https://raw.githubusercontent.com/turnbullpress/dockerbook-code/master/code/6/node/nodejs/nodeapp/server.js |
第三步回到nodejs
目录下,创建Dockerfile文件。这个镜像安装了Node,然后我们将nodeapp
的源代码通过ADD指令添加到/opt/nodeapp
目录。这个Node.js应用是一个简单的Express服务器,包含了package.json
文件和实际应用代码的server.js
文件。server.js
文件引入了所有的依赖,并启动了Express应用,Express应用把session信息保存到Redis里,并创建了一个以JSON格式返回状态信息的节点,这个节点默认使用redis_primary作为主机名去连接Redis。这个应用会把日志记录到/var/log/nodeapp/nodeapp.log
文件里,并监听3000端口。我们还将工作目录设置为/opt/nodeapp
,并且安装了Node应用的必要软件包,还创建了用于存放Node应用日志的卷/var/log/nodeapp
,最后公开了3000端口,并使用ENTRYPOINT指定了运行Node应用的命令
1 | FROM ubuntu:18.04 |
第四步构建镜像
1 | docker build -t elssm/nodejs . |
Redis基础镜像
第一步先在本地创建一个名为redis_base
的文件夹
1 | mkdir redis_base && cd redis_base |
第二步创建Dockerfile文件。这个Redis基础镜像从PPA库安装了最新版本的Redis包。并指定了两个卷,公开了Redis的默认端口是6379。我们只是讲这个镜像作为基础镜像从而构建别的镜像,因此不会执行这个镜像。
1 | FROM ubuntu:18.04 |
第三步构建Redis基础镜像
1 | docker build -t elssm/redis . |
Redis主镜像
第一步先在本地创建一个名为redis_primary
的文件夹
1 | mkdir redis_primary && cd redis_primary |
第二步创建Dockerfile文件。Redis主镜像基于Redis基础镜像,并通过ENTRYPOINT指定了Redis服务启动命令,将Redis服务的日志文件保存到/var/log/redis/redis-server.log
1 | FROM elssm/redis |
第三步构建Redis主奖项
1 | docker build -t elssm/redis_primary . |
Redis副本镜像
为了配合Redis主镜像,我们会创建Redis副本镜像,保证为Node.js应用提供Redis服务的冗余度。
第一步先在本地创建一个名为redis_replica
的文件夹
1 | mkdir redis_replica && cd redis_replica |
第二步创建Dockerfile文件。Redis副本镜像也是基于Redis基础镜像构建的。并通过ENTRYPOINT指定了运行Redis服务器的命令,设置了日志文件存储位置和slaveof
选项,这样就把Redis配置为主-副本模式,从这个镜像构建的任何容器都会将redis_primary主机的Redis作为主服务,连接其6379端口,成为对应的副本服务器
1 | FROM elssm/redis |
第三步构建Redis副本镜像
1 | docker build -t elssm/redis_replica . |
创建Redis后端集群
现在我们已经有了Redis主镜像和副本镜像。如下图所示
接下来我们可以构建自己的Redis环境了。首先我们创建一个用来运行我们的Express应用程序的网络,名为express
1 | docker network create express |
使用docker network ls
查看网络
1 | docker network ls |
现在让我们在这个网络中运行Redis主容器,如下代码所示
1 | docker run -d -h redis_primary --net express --name redis_primary elssm/redis_primary |
这里使用docker run
命令从elssm/redis_primary
镜像创建了一个容器。-h
用来设置容器的主机名。这会覆盖默认的行为(默认将容器的主机名设置为容器ID)并允许我们指定自己的主机名,使用这个标志可以确保容器使用redis_primary
作为主机名,并被本地的DNS服务正确解析。--net
确保该容器在express
网络中运行,--name
确保容器的名字是redis_primary
接下来使用docker logs
命令来查看Redis主容器的运行情况
1 | docker logs redis_primary |
执行命令后发现什么都没有,这是因为Redis服务会将日志记录到一个文件而不是记录到标准输出,所有使用Docker查不到任何日志。因此我们可以使用之前创建的/var/log/redis
卷。
1 | docker run -it --rm --volumes-from redis_primary ubuntu cat /var/log/redis/redis-server.log |
其中-it
是以交互方式运行了另一个容器,--rm
会在进程运行完后自动删除容器。--volumes-from
告诉它从redis_primary
容器挂载了所有的卷,然后我们指定了一个ubuntu基础镜像,并执行cat命令来查看日志,这种方式利用了卷的优点,可以直接从 redis_primary
容器挂载/var/log/redis
目录并读取里面的日志文件,日志文件内容如下图所示
接下来我们创建一个Redis副本容器
1 | docker run -d -h redis_replica1 --name redis_replica1 --net express elssm/redis_replica |
这里我们运行了另一个容器,这个容器来自elssm/redis_replica
镜像,-d
标志在后台运行,-h
指定主机名,--name
指定容器名,--net
指定在express
网络中运行Redis副本容器。
和查看Redis主容器一样,我们来查看一下Redis副本容器中的日志
1 | docker run -it --rm --volumes-from redis_replica1 ubuntu cat /var/log/redis/redis-replica.log |
日志文件内容如下图所示
到这里我们已经成功启动了redis_primary和redis_replica1容器,并让这两个容器进行主从复制。现在我们再来加入另一个容器副本redis_replica2,从而确保万无一失!
1 | docker run -d -h redis_replica2 --name redis_replica2 --net express elssm/redis_replica |
查看副本redis_replica2容器中的日志
1 | docker run -it --rm --volumes-from redis_replica2 ubuntu cat /var/log/redis/redis-replica.log |
如下图所示
创建Node容器
现在我们已经让Redis集群运行了,我们可以为Node.js应用启动一个容器,代码如下所示
1 | docker run -d --name nodeapp -p 3000:3000 --net express elssm/nodejs |
这里我们从elssm/nodejs
镜像创建了一个新容器,命名为nodeapp
,并将容器内的3000端口映射到宿主机的3000端口,同样我们的nodeapp
容器也是运行在express网络中的。
使用docker logs
命令来查看nodeapp
容器在做什么
1 | docker logs nodeapp |
访问本地3000端口,如下图所示
这个输出表明应用正在工作,浏览器的会话状态(session)回先被记录到Redis主容器redis_primary,然后复制到两个Redis副本容器redis_replica1和redis_replica2
捕获应用日志
现在应用已经正常运行了,我们需要把这个应用放到生产环境中,在生产环境里需要确保可以捕获日志并将日志保存到日志服务器。我们将使用Logstash来完成这件事,首先创建一个Logstash镜像。
第一步还是在本地创建一个名为logstash
的文件夹
1 | mkdir logstash && cd logstash |
第二步创建Dockerfile文件
1 | FROM ubuntu:18.04 |
这个镜像在安装了Logstash之后,将logstash.conf
文件使用ADD指令添加到了/usr/share/logstash/
目录。logstash.conf
文件和Dockerfile在统计目录下,它的内容如下
1 | input { |
这个logstash配置很简单,它监控两个文件/var/log/nodeapp/nodeapp.log
和/var/log/redis/redis-server.log
。logstash会一直监控这两个文件,将其中新的内容发送个Logstash,配置文件的第二部分是output部分,接受所有logstash输入的内容并将其输出到标准输出上,在实际情况中,一般会将logstash配置为输出到ElasticSearch集群或者是其他的目的地。
第三步构建logstash镜像
1 | docker build -t elssm/logstash . |
镜像构建成功之后,我们从这个镜像启动一个容器,代码如下
1 | docker run -d --name logstash --volumes-from redis_primary --volumes-from nodeapp elssm/logstash |
这样我们就启动了一个名为logstash的新容器,并通过--volumes-from
标志分别挂载了redis_primary和nodeapp容器的卷,这样就可以访问Redis和Node的日志文件了,任何加到这些日志文件里的内容都会反应在logstash容器的卷里。
现在可以使用-f
标志来查看logstash容器的日志
1 | docker logs -f logstash |
在浏览器里刷新Web应用之后,会产生一个新的日志时间,我们就能在logstash容器的输出中看到这个事件。如下图所示
相应的当我们启动redis_primary容器后,也可以在logstash中看到相应的日志事件