本文包含两个主要阶段:

(1) 使用 Dockerfile 和 Docker Compose 将 Node.js 服务器应用程序和 Redis 数据库实例容器化到两个单独的 Docker 容器中,并展示这两个应用程序如何相互通信。

(2) 使用容器化的 Nginx 反向代理对 Node.js 服务器进行负载平衡。

**让我们从第一阶段开始:

(1) 使用 Dockerfile 和 Docker Compose 将 Node.js 服务器应用程序和 Redis 实例容器化到两个独立的 Docker 容器中,并展示这两个应用程序如何相互通信**

从一个简单的 Node.js 服务器 应用程序(我们称之为“test-webapp”)开始,它通过显示“访问次数”来响应 HTTP GET 请求。下面的编号方案(即 (1.1)、(1.2)、(1.3) 等)与下图中的编号相匹配:

[图 1.a –组件示意图](https://res.cloudinary.com/practicaldev/image/fetch/s--vjd6PFTp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3yp6moj945jli4n6vfl4.png)

图1.a - 组件示意图

在上面的“图1.a - 组件示意图”中,我们有以下组件:

(1.1) “Docker Container 1” - 容器运行 Node.js 服务器,名为“test-webapp”,与左侧的浏览器通信。每次我们刷新 URLlocalhost:80即我们向 Node.js 服务器test-webapp”发送一个GET命令,服务器代码增加访问次数,然后将此值保存到 Redis 数据库实例在“Docker Container 2”上运行,并且还在浏览器中显示该值。

(1.2) “Dockerfile” - 定义和控制“Docker Container 1”中的 Node.js 服务器 进程。

(1.3, 1.3.1, 1.3.2) “docker-compose.yml” – Docker Compose 配置文件定义和控制“ Docker 容器 1”和“Docker 容器 2”。 “Docker Container 1”运行Node.js服务器进程“test-webap_p”。 “_Docker Container 2”运行 Redis 数据库实例。

(1.3.3) Docker Compose 默认在“Docker Container 1”和“Docker Container 2”之间建立通信网络,允许 Node.js 服务器 进程“test-webapp”与 Redis 数据库实例通信,并在它们之间交换“对应用程序/Web 服务器的访问次数”(numVisits)值。

(1.3.4) Docker Compose 将本地主机端口 80 映射到“Docker Container 1”端口 5000。端口 5000 是 Node.js 服务器test-webapp”所在的端口” 监听并响应浏览器发送的GET条命令。

(1.4) 连接到“Docker Container 2”的shell,然后通过“redis-cli”连接到**Redis数据库实例的客户端命令行我们可以看到numVisits的值(代表数字浏览器向 Node.js 服务器发出GET命令的次数)与 Node.js 服务器在浏览器中显示的值同步——因此表明进程之间发生了进程间通信“Docker Container 1”中的“test-webapp”和“Docker Container 2”中的Redis**进程。

(1.5) 这一步说明了 Docker Compose 中的restart指令和能力(在配置文件“docker-compose.yml”中指定)——当连接到“Docker Container 1”的 Linux shell 时,我们可以kill -9Node.js server 进程,但是 Node.js server 进程会被 Docker Compose 自动重启——说明 Docker Compose 提供的自动恢复。

现在让我们描述一下这个场景的步骤和流程。下面描述中的编号方案(即(1.1)、(1.2)、(1.3)等)与“图 1.a – 组件示意图”中的编号相匹配。

(1.1) 文件结构:

[图 1.b – 阶段 1的文件结构](https://res.cloudinary.com/practicaldev/image/fetch/s--_yC5myBq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rm92ysovtnnq9azlunop.png)

图 1.b – 第 1 阶段的文件结构

进程‘test-webapp’的Node.js文件:

目录“test-webapp”的内容,Node.js 服务器test-webapp”的源代码所在的位置:

[Node.js 文件用于进程 'test-webapp'](https://res.cloudinary.com/practicaldev/image/fetch/s--bSempo81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqed4cw6m1mpm1upws9m.png)

(1.2) Dockerfile _containerizes 并控制 Node.js 应用程序,方法是从Docker Hub下载“_node:alpine”映像,在容器上安装 Node.js ,将源文件复制到容器 - 然后启动 Node.js 服务器 Web 应用程序(请参阅文件“server.js”中的源代码)。

(1.3) 转到上面的一个目录,我们看到“docker-compose.yml”文件,它组织了容器化并设置了所有组件的架构。 (文件

docker-composer-nginx.yml”将在本文的第2阶段中介绍和解释)

[用于进程 'test-webapp' 的'docker-compose.yml' 文件](https://res.cloudinary.com/practicaldev/image/fetch/s--naPa4IgZ--/c_limit%2Cf_auto%2Cfl_progressive %2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqtlkv6v7vams8x3xq9d.png)

清除所有图像和容器:

我们运行命令docker system prune -a来清除所有 Docker 映像和容器,并从一个干净的状态开始。

C:\test-docker\test-redis>docker system prune -a                                          
WARNING! This will remove:                                                                                                
- all stopped containers                                                                                                
- all networks not used by at least one container                                                                       
- all images without at least one container associated to them                                                          
- all build cache                                                                                                                                                                                                                             
Are you sure you want to continue? [y/N] y

进入全屏模式 退出全屏模式

(1.3) 使用 Docker Compose 构建并运行“test-webapp”镜像

使用命令docker-compose -f <config-filename> build构建容器以及将在每个容器中运行的应用程序:

C:\test-docker\test-redis>docker-compose -f docker-compose.yml build

请参阅以下构建的 Docker 映像的结果:

C:\test-docker\test-redis>docker images                                                                                                                     
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE                                                                                                                     
test-redis_test-webapp   latest    e8145bea0fec   4 minutes ago   175MB

进入全屏模式 退出全屏模式

使用 'docker-compose' 运行 'test-webapp' 和 'redis' 容器:

让我们启动“test-webapp”和“redis”服务,如配置文件中所述

docker-compose.yml”,使用docker-compose -f <config-filename> up命令。

[docker-compose -f docker-compose.yml up](https://res.cloudinary.com/practicaldev/image/fetch/s--7d3NTR5i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66ukw6kmm4djhk2nzxsa.PNG)

我们可以从上面的输出中看到,“redis”容器(“test-redis_1”——对应于图 1.a 中的“Docker Container 2”)和“test-webapp”容器(与图 1.a 中的“Docker Container 1”相对应的“test-webapp_1”正在运行并打印到命令行窗口中的标准输出,我们在命令行窗口中启动了 Docker Compose 以运行这两个容器。

查看“test-webapp”和“redis”运行容器:

C:\test-docker\test-redis\test-webapp>docker ps                                                                                        
CONTAINER ID   IMAGE                    PORTS                
NAMES                                         
928b8b07415d   test-redis_test-webapp   0.0.0.0:80->5000/tcp   test-redis_test-webapp_1                      
a8756127bff5   redis:alpine             6379/tcp               test-redis_test-redis_1

进入全屏模式 退出全屏模式

(1.3.1, 1.3.2) 上面的两个容器匹配上面图1.a中的容器“Docker Container 1”和“Docker Container 2”。请注意“CONTAINER ID”列,我们将在下面使用其值对每个正在运行的容器执行操作。

(1.3.4) Node.js 服务器 "test-webapp" 容器中的端口 5000 映射到本地(托管)端口 80,因此当在本地(托管)浏览器中连接到 URL 时**http://localhost:80,对于每次刷新,“test-webapp”容器中的Node.js进程**都会增加变量numVisits中的访问次数,该变量设置并保存在变量numVisits中的 Redis - 此值也被发送回并显示在浏览器中。

“Docker-compose”默认设置一个包含“test-webapp”容器(图 1.a 中的“Docker Container 1”)和“redis”容器(中的“Docker Container 2”)的网络图 1.a) 在这个网络中,两个容器都可以通过这个网络相互访问。

本地浏览器与 Node.js 服务器 容器通信。在浏览器中刷新连接时,会调用服务器回调,以更新的访问次数响应浏览器。

[1.3.4 浏览器 - localhost - 访问次数 8](https://res.cloudinary.com/practicaldev/image/fetch/s--BKBbVg8w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euscx0msi6am2kz9tk7j.png)

(1.4)我们使用docker exec -it命令允许我们连接到正在运行的容器,而-it选项允许我们捕获该容器的标准输入/标准输出。然后我们指定从上面的docker ps命令获得的 CONTAINER ID a8756127bff5,然后是我们要在进入容器时启动的 shell (sh)。

C:\test-redis\test-webapp>docker exec -it a8756127bff5 sh

然后,一旦我们进入容器的外壳,我们使用redis-cli命令连接到 Redis 数据库。在 Redis 提示符下,我们使用get numVisits来获取“redis”内部变量“numVisits”的值。我们可以看到“redis”实例与其各自容器中的“test-webapp”进程通信,并且**Redis**数据库实例中的变量“numVisits”与其在浏览器中的值同步。在这种情况下,两者都具有值“8”,因为我们刷新了 8 次“localhost:80” URL,因此在浏览器中发出了GET命令,该命令被 *Node.js 服务器拦截* 增加“访问次数”(numVisits)变量。 “访问次数”值由“test-webapp”进程发送回浏览器,该进程还将该值保存在“redis”数据库中的变量numVisits中)。

/data # redis-cli                                                                                                                                                     
127.0.0.1:6379> get numVisits                                                                                                                                      
"8"                                                                                                                                                                    
127.0.0.1:6379> 

进入全屏模式 退出全屏模式

在“redis”容器(“Docker Container 2”)中的“redis-cli”中,我们还可以在 Redis 中手动将“numVisits”变量设置为随机值,比如“342” “......

[1.4 - redis - 设置访问次数 342](https://res.cloudinary.com/practicaldev/image/fetch/s--5mFZNh5z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https: //dev-to-uploads.s3.amazonaws.com/uploads/articles/3snsztp8x4gcgco7t7ec.png)

...numVisits变量在“test-webapp”Node.js 服务器(在“Docker Container 1”中运行)中更新,因此在浏览器中更新(因为为了调用**Node.js server,需要刷新“localhost:80”的连接,访问量增加1**,因此342 + 1 u003d 343. 这表明我们在“Docker Container 1”和“Docker Container 2”中运行的进程之间有双向进程间通信。

[1.4 浏览器 - 本地主机 - 访问次数 343](https://res.cloudinary.com/practicaldev/image/fetch/s--FNra9-r7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oapryvdzrnppyzztuxfn.png)

(1.5) Docker Compose 提供的一个有用功能是能够在“docker-compose.yml”中指定“restart”选项。

这将允许我们在连接到“Docker Container 1”的外壳时,“杀死” Node.js server 进程,但 Node.js server 进程将被 ** 自动重启Docker Compose**“restart”指令。

C:\test-docker\test-redis>docker ps
CONTAINER ID   IMAGE                      PORTS                    NAMES
c675ff6c0464   test-redis_nginx           0.0.0.0:80->80/tcp       test-redis_nginx_1
3137d1468ec7   test-redis_test-webapp-2   0.0.0.0:3009->5000/tcp   test-redis_test-webapp-2_1 
57d399295421   redis:alpine                                        test-redis_test-redis_1
b30635f44151   test-redis_test-webapp-1   0.0.0.0:3008->5000/tcp   test-redis_test-webapp-1_1

进入全屏模式 退出全屏模式

连接到 ID 为 928b8b07415d 的 Docker 容器并调用 shell (sh)。

C:\test-redis\test-webapp>docker exec -it 928b8b07415d sh

在容器内,在 shell 提示符下,显示所有使用ps -al的进程 ID。

/usr/src/app # ps -al
PID   USER     TIME  COMMAND
1     root     0:00  npm start
19    root     0:00  node server.js
30    root     0:00  sh
36    root     0:00  ps -al

进入全屏模式 退出全屏模式

通过发出kill -9 <process-id>命令继续“杀死”“node server.js”进程:

/usr/src/app # kill -9 19

在运行 Docker Compose 的命令行窗口中,我们可以看到“test-webapp”如何接收“终止信号”(SIGKILL),并以代码“1”退出,然后自动重新启动。

[npm ERR 命令 sh -c node server.js](https://res.cloudinary.com/practicaldev/image/fetch/s--PyOXsmV8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m25v5kmnu9zlbobgqgf.PNG)

结论

在本示例的 Stage 1 中,我们展示了 Docker Compose 如何让我们轻松建立相互通信的独立环境,以及 Docker Compose 的自动容错(故障时重启)能力。

**让我们继续第 2 阶段:

(2) 在容器化 Nginx 反向代理的帮助下对 Node.js 服务器进行负载平衡**

图 2.a - 阶段 2 组件示意图”中的图表描述了一种类似于前面“图 1.a - 组件示意图”中描述的架构,但具有下面描述的变化。

[图 2.a – Stage 2组件示意图](https://res.cloudinary.com/practicaldev/image/fetch/s--dW6tGtNm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02s6wkco89eaeu00ezxu.png)

图 2.a – 第 2 阶段的组件示意图

在“图 2.a – 第 2 阶段组件示意图”中,我们有以下组件:

(2.1.1, 2.1.2) “Docker Container 1”和“Docker Container 2”——两个相同的容器,其源代码位于目录“test-webapp-1”中和“test-webapp-2”(如下面的“图 2.b – 第 2 阶段的文件结构”所示),它们几乎是应用程序“test-webapp”的相同副本,之前在第 1 阶段中进行了描述。这次我们使用两个 Node.js 服务器 进程,它们将从本地主机为客户端浏览器提供服务,从 Stage 1 扩展和负载平衡原始单服务器配置。这两个容器分别由它们各自的“Dockerfile”(2.1.1.1)和(2.1.1.2)定义和控制。每个 Node.js 服务器Docker Container 1”和“Docker Container 2”都会计算来自本地主机浏览器的访问次数。然后它将访问次数保存到 Redis 数据库中,它还向浏览器返回访问次数以及使用哪个特定的 Node.js 服务器 为来自浏览器,通过向浏览器发送以下类型的消息:

test-webapp-1:访问次数为:”,或

test-webapp-2:访问次数为:”

...从而突出了这个阶段的负载均衡性质。

(2.1.3) “Docker Container 3” – 运行 Redis 数据库实例的容器,与 Stage 1 中描述的相同,存储由localhost 机器浏览器到“localhost:80”。访问次数由Node.js服务器进程“test-webapp-1”和“test-webapp-2”存储在**Redis变量numVisits中,其值由每个Node传输每次在本地主机浏览器上刷新时,将 .js 服务器** 复制到 Redis 数据库。

(2.2) “docker-compose-nginx.yml” – Docker Compose 主配置文件定义和控制: (I) “Docker Container 1” 运行 Node.js服务器test-webapp-1”,(II)“Docker Container 2”运行Node.js服务器test-webapp-2”,(III)“Docker Container 3”运行**Redis** , 和 (IV) 运行 Nginx 的“Docker Container 4”。

(2.3) “Docker Container 4” running “Nginx” – 这是在 Stage 2 中引入的附加容器,由其自己的 Dockerfile (2.3.1) 定义和控制,运行一个“nginx”实例,并充当反向代理,路由来自本地主机浏览器的 HTTP GET 请求。 “Docker Container 4”中的“Nginx”进程以循环方式路由来自本地主机浏览器“localhost:80”的_HTTP GET_请求((2.3 .3)(2.3.4)),发送到“Docker Container 1”中的“test-webapp-1Node.js 服务器或“test-webapp-2Node.js 服务器在“Docker Container 2”中。 “Docker Container 4”中的“nginx”进程由**Nginx复制的_Nginx_config文件“nginx.conf”定义和控制容器的_Dockerfile_到“Docker Container 4”环境文件“/etc/nginx/conf.d./default.conf”(这是标准的Nginx设置)。 “nginx”实例分配来自本地主机浏览器的传入流量,从而扩展和负载平衡阶段 1** 中呈现的单容器 Web/应用服务器架构。

现在让我们描述一下这个场景的步骤和流程。下面描述中的编号方案(即(2.1)、(2.2)、(2.3)等)与“图 2.a – 第 2 阶段组件示意图”中的编号相匹配。

(2.1) 文件结构:

[图 2.b – 第 2 阶段的文件结构](https://res.cloudinary.com/practicaldev/image/fetch/s--5AOs-x2p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto% 2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8v7qndptehh545sknesh.png)

图 2.b - 阶段 2 的文件结构”中描述的文件结构与前面“图 1.b - 阶段 1 的文件结构”中描述的文件结构几乎相同,具有以下内容变化:

(2.1.1, 2.1.2) Stage 1 目录“test-webapp”中的文件被复制到目录“test-webapp-1”和“test-webapp- 2”。

(2.2) 转到上面的一个目录,我们看到“docker-compose-nginx.yml”配置文件,它组织了容器化并设置了所有组件的架构:

['docker-compose-nginx.yml' 文件](https://res.cloudinary.com/practicaldev/image/fetch/s--A8ZD3MU4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23ztsgagx0fthaktnn3n.png)

清除所有图像和容器:

Stage 1 一样,我们运行命令docker system prune -a以清除所有 Docker 映像和容器,并从一个干净的状态开始。

(2.3) 使用 Docker Compose 构建并运行“test-webapp-1”、“test-webapp-2”、“redis”和“nginx”镜像

使用 Docker Compose 构建:

C:\test-docker\test-redis>docker-compose -f docker-compose-nginx.yml build

使用 Docker Compose 运行:

C:\test-docker\test-redis>docker-compose -f docker-compose-nginx.yml up

在我们发出docker-compose -f docker-compose-nginx.yml up命令的命令行窗口中,Docker Compose 回复:

[4 个容器运行](https://res.cloudinary.com/practicaldev/image/fetch/s--Ufv9ddaS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/xi20q9vn6kp7m67olqst.PNG)

...显示所有4个Docker容器都已成功启动并启动并运行:“test-redis_1”对应于“Docker Container 3”中运行的**Redis进程,“test-webapp-2_1 ”对应“Docker Container 2”中运行的Node.js server进程,“test-webapp-1_1”对应“Docker Container 1”中运行的Node.js server进程,而“nginx_1”对应于“Docker Container 4”中运行的Nginx**服务器。

查看“test-webapp-1”、“test-webapp-2”、“redis”和“nginx”运行容器:

C:\test-docker\test-redis>docker ps 
CONTAINER ID   IMAGE                       PORTS       NAMES                                            c675ff6c0464   test-redis_nginx            0.0.0.0:80->80/tcp        test-redis_nginx_1                               
3137d1468ec7   test-redis_test-webapp-2    0.0.0.0:3009->5000/tcp   
test-redis_test-webapp-2_1                       
57d399295421   redis:alpine                                                                         test-redis_test-redis_1                          
b30635f44151   test-redis_test-webapp-1    0.0.0.0:3008->5000/tcp   test-redis_test-webapp-1_1

进入全屏模式 退出全屏模式

上面的四个容器匹配“图 2.a – Stage 2 组件示意图”中的容器“Docker Container 1”到“Docker Container 4

以上。请注意“CONTAINER ID”列,我们将在下面使用其值来潜在地对每个正在运行的容器执行操作。

让我们在主机上运行浏览器的前两个实例,并将它们指向 URL “localhost:80”:

[2.3 浏览器 - test-webapp-2 - 访问次数 7](https://res.cloudinary.com/practicaldev/image/fetch/s--OpVMnmS4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/42dwmonlt8jn07ifovp0.png)

[2.3 浏览器 - test-webapp-1 - 访问次数 8](https://res.cloudinary.com/practicaldev/image/fetch/s--sOWO-DD__-/c_limit%2Cf_auto%2Cfl_progressive %2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d06t4jutlz9c47pd5po8.png)

请注意,由于 Nginx 反向代理所采用的 round-robin 路由机制,“GET localhost:80”请求一次路由到“test-webapp-1Node.js服务器,一次到“test-webapp-2Node.js 服务器,实现我们打算演示的扩展和负载平衡。

让我们连接到运行 Redis 的容器,连接到它的 sh (shell) 环境:

C:\test-docker\test-redis>docker exec -it 57d399295421 sh

然后,在容器内部,让我们使用“redis-cli”连接到 Redis 本身:

/data #
/data # redis-cli
127.0.0.1:6379> 
127.0.0.1:6379> get numVisits
"8"
127.0.0.1:6379>

进入全屏模式 退出全屏模式

请注意 Redis 中的get numVisits命令如何返回“访问次数”的预期值,该值从运行 Node.js 应用服务器的容器传送到“redis”容器.

结论

在本示例的 Stage 2 中,我们展示了 Docker Compose 如何使我们能够轻松地建立多个容器及其相互通信的独立环境,以及如何使用 Nginx 实现扩展和负载平衡。

源代码:

https://github.com/marcelkatz/test-docker-nodejs-redis-nginx

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐