前言
单机模式下,我们可以使用 Docker Compose 来编排多个服务,而在 上一篇文章 中介绍的 Docker Swarm 只能实现对单个服务的简单部署。于是就引出了本文的主角 Docker Stack ,通过 Docker Stack 我们只需对已有的 docker-compose.yml 配置文件稍加改造就可以完成 Docker 集群环境下的多服务编排。
正文
-
首先创建一个 docker-compose.yml 文件,使用 Docker Compose v3 语法
内容比较简单,一个有四个实例的 nginx 服务,两个只部署在 manager 节点上的单实例监控工具服务:portainer 和 visualizer
version: "3"
services:
nginx:
image: nginx:alpine
ports:
- 80:80
deploy:
mode: replicated
replicas: 4
visualizer:
image: dockersamples/visualizer
ports:
- "9001:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
portainer:
image: portainer/portainer
ports:
- "9000:9000"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
- 部署服务
$ docker stack deploy -c docker-compose.yml stack-demo
- 部署成功之后查看详情
$ docker stack services stack-demo
ID NAME MODE REPLICAS IMAGE PORTS
4yb35ywqvo49 stack-demo_portainer replicated 1/1 portainer/portainer:latest *:9000->9000/tcp
mzd2volqug28 stack-demo_nginx replicated 4/4 nginx:alpine *:80->80/tcp
r0zlzpp3wujg stack-demo_visualizer replicated 1/1 dockersamples/visualizer:latest *:9001->8080/tcp
- 在浏览器中访问监控工具,对应端口如下:
portainer ——→ ip:9000
visualizer ——→ ip:9001
注意:如果有多个 manager 节点,portainer 和 visualizer 可能分别部署在两台机器上,所以ip可能会不一样。
- 修改 docker-compose.yml 文件后重新部署即可完成对修改内容的更新
$ docker stack deploy -c docker-compose.yml stack-demo
关于负载均衡
评论区有小伙伴提到,容器间通过服务名 ( 比如文中的 nginx ) 通讯时,对应的 IP 却和容器的实际 IP 对不上。出现这个情况是因为负载均衡( 对外表现为一个服务,内部为多个服务 )。下面是我做的试验,希望能帮助大家理解。
按上面的配置启动集群 ( 由两台服务器构成 )
-
在 manager 节点服务器中看下运行的服务
$ docker ps CONTAINER ID IMAGE NAMES 9b96f07bbb91 dockersamples/visualizer:latest stack-demo_visualizer.1.p5hy7gsc50vbm0wkxm1c17rl6 942dd34d024e nginx:alpine stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz 706ae42e0089 nginx:alpine stack-demo_nginx.2.vnlmlky5m5qy7l8qxq6k5nllk 6dba55dd7d63 portainer/portainer:latest stack-demo_portainer.1.yv76gf0i7gou2awen44kshm1j
-
在这台服务器上启动了两个 nginx 容器实例,随便进一个实例看下 IP
$ docker exec -it stack-demo_nginx.2.vnlmlky5m5qy7l8qxq6k5nllk ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:FF:00:3E inet addr:10.255.0.62 Bcast:10.255.255.255 Mask:255.255.0.0 eth1 Link encap:Ethernet HWaddr 02:42:0A:00:06:07 inet addr:10.0.6.7 Bcast:10.0.6.255 Mask:255.255.255.0 eth2 Link encap:Ethernet HWaddr 02:42:AC:13:00:04 inet addr:172.19.0.4 Bcast:172.19.255.255 Mask:255.255.0.0 lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0
发现容器中绑了3个网卡 eth0 、eth1 、eth2 ,我猜想分别对应 整个 Swarm 集群的局域网、当前 Stack 集群的局域网 、当前主机下 Compose 服务的局域网 三个网络。
-
查看当前主机下的 docker 网络
$ docker network ls NETWORK ID NAME DRIVER SCOPE bd4fa8219483 bridge bridge local e51735fef0d6 docker_gwbridge bridge local 26360437865a host host local yvupj4ex3odl ingress overlay swarm f0a0190c3b1f none null local oft930l7jpdn stack-demo_default overlay swarm
-
上一步看到有两个 swarm 的网络,进去看下具体信息
$ docker network inspect ingress "IPAM": { "Config": [ { "Subnet": "10.255.0.0/16", "Gateway": "10.255.0.1" } ] }, "Containers": { "6dba55dd7d63f7166e2e0ee3afed8e427089b7140d62f39a835d3145a058b868": { "Name": "stack-demo_portainer.1.yv76gf0i7gou2awen44kshm1j", "IPv4Address": "10.255.0.59/16", }, "706ae42e00890444087aa6d51ccb966b76b5ad4c985b48fdf5215c192bcf0836": { "Name": "stack-demo_nginx.2.vnlmlky5m5qy7l8qxq6k5nllk", "IPv4Address": "10.255.0.62/16", }, "942dd34d024e218aad4e5034e1194a2cfa1d9be81a839ec86403cf237d41368b": { "Name": "stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz", "IPv4Address": "10.255.0.64/16", }, "9b96f07bbb91a570bb8d26945996d77151c5633c0a6057361ab4474b393da364": { "Name": "stack-demo_visualizer.1.p5hy7gsc50vbm0wkxm1c17rl6", "IPv4Address": "10.255.0.66/16", }, "ingress-sbox": { "Name": "ingress-endpoint", "IPv4Address": "10.255.0.2/16", } }
内容太多就省略其他无关内容了,从上面的信息已经可以证明第三步的猜想了 ( ingress 对应 Swarm 集群 , stack-demo_default 对应 Stack 集群 ) ,要进一步确认可以再看下另一台服务器上的网络。
-
通过容器 nginx.4 使用服务名的方式 ping 一下 nginx
$ docker exec -it stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz ping nginx PING nginx (10.0.6.5): 56 data bytes 64 bytes from 10.0.6.5: seq=0 ttl=64 time=0.121 ms
发现 IP 是 10.0.6.5 ,属于 stack-demo_default 网络,但是在两台服务器的 docker 网络详情里面都找不到这个实例。
-
在容器 nginx.4 中安装 curl 然后再访问 nginx 看下效果
$ docker exec -it stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz sh -c 'echo -e "https://mirrors.ustc.edu.cn/alpine/latest-stable/main\nhttps://mirrors.ustc.edu.cn/alpine/latest-stable/community" > /etc/apk/repositories && apk --update add curl' $ docker exec -it stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz curl nginx
可以看到访问成功,再多调用几次。
-
打印 nginx 容器的日志 ( 可以多开几个终端打印日志,再访问 nginx 看下实时日志的效果,这样更直观 )
$ docker logs stack-demo_nginx.2.vnlmlky5m5qy7l8qxq6k5nllk && echo "---分界线---" && docker logs stack-demo_nginx.4.tp6u05jmg9iuookqc9i9e11kz 10.0.6.4 - - [06/Dec/2018:10:15:18 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.60.0" "-" ---分界线--- 10.0.6.4 - - [06/Dec/2018:10:13:16 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.60.0" "-" 10.0.6.4 - - [06/Dec/2018:10:15:19 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.60.0" "-"
可以看到明显的负载均衡的效果,请求都来自 10.0.6.4,它就是 stack-demo_default 网络中一个名为
lb-
开头的实例,它就是这个负载均衡的入口。"lb-stack-demo_default": { "Name": "stack-demo_default-endpoint", "IPv4Address": "10.0.6.4/24", }
-
再去另一台服务器看下 nginx 的日志
发现并没有访问记录,说明这个负载均衡仅限于 当前服务器 、相同服务 的 多个实例,不会跨服务器负载均衡。
-
在另一台服务器上再重复 7 & 8 两个步骤
发现另一台服务器上不存在名为
lb-
开头的实例,而负载均衡的入口是其中一个普通的 nginx 实例。
总结下:
整个请求的调用流程应该就是:通过服务名 nginx 访问 -- 指向 --> stack 集群网关 ( 10.0.6.5 ) -- 转发 --> stack 集群中,位于当前服务器的负载均衡实例 ( 10.0.6.4 ) -- 分发 --> 最终的应用 。
相关命令
命令 | 描述 |
---|---|
docker stack deploy | 部署新的堆栈或更新现有堆栈 |
docker stack ls | 列出现有堆栈 |
docker stack ps | 列出堆栈中的任务 |
docker stack rm | 删除一个或多个堆栈 |
docker stack services | 列出堆栈中的服务 |