Docker Swarm 是 Docker 官方项目之一,提供 Docker 容器集群服务,是 Docker 官方对容器云生态进行支持的核心方案。使用它,用户可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,快速打造一套容器云平台。
Docker 1.12 Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm
。
Swarm mode 内置 kv 存储功能,提供了众多的新特性,比如:具有容错能力的去中心化设计、内置服务发现、负载均衡、路由网格、动态伸缩、滚动更新、安全传输等。
概念
docker 中和 swarm 相关的命令有:
docker swarm
dokcer node
docker service
docker stack
docker secret
Swarm 中一台主机就是一个节点(node
),节点分为管理( manager
)和工作(worker
)节点。
管理节点用于 Swarm 集群的管理。一个 Swarm 集群可以有多个管理节点,但只有一个管理节点可以成为 leader
,leader
通过 raft
协议实现。
管理节点有内置 Raft 数据库,它们用来存放配置等数据。而且它们之间的通信都是加密的。
工作节点是任务执行节点,管理节点将服务 (service) 下发至工作节点执行。管理节点默认也作为工作节点。也可以通过让服务只运行在管理节点上,管理节点和工作节点只是它们的权限不同,工作节点就没有管理节点那么多权限,比如在工作节点上不能查看集群中的容器。
有了 swarm 我们就不用自己一个个的创建容器了,比如我们有 3 个主机,我们告诉 swarm 我们要创建 5 个 nginx 容器,swarm 会自己帮我们部署到不同主机上,比如那个主机部署一个那个部署两个。
启用 Swarm
代码语言:javascript复制$ docker swarm init# 我们执行这个命令,来启用 swarm,我们当前主机作为管理节点# 它还创建了 swarm 根证书,还创建了 join token,用来让其他节点加入这个 Swarm# 还创建了 raft 数据库。Swarm initialized: current node (86pi89uue3q0eqiqpomna3ejy) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5aco0o1iqxbgjludw6m6rhl9qug60ylm8lj938hnyfy2cyt8d7-7jq47t76cohvtah61sab7rwcf 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
我们可以直接复制上面那条输出的命令,让其他节点作为 worker
加入这个 swarm
。
如我我们想让加入的节点作为 manager
加入这个 swarm
可以执行:
$ docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5aco0o1iqxbgjludw6m6rhl9qug60ylm8lj938hnyfy2cyt8d7-ecmkdz7x0eswu3uyn0vij72dy 192.168.65.3:2377
代码语言:javascript复制$ docker node ls# docker node 命令可以用来管理我们的节点,ls 表示列出集群所有节点
代码语言:javascript复制$ docker service lsID NAME MODE REPLICAS IMAGE PORTS# docker service 命令用来取代 docker run 命令# 因为在 swarm 中我们不关心容器的个个配置信息,而且也不会去其他节点自己手动创建容器# 我们只需要抛出一个 任务(task) 由 Swarm 来协调
任务 (Task
)是 Swarm
中的最小的调度单位,目前来说就是一个单一的容器。
服务 (Services
) 是指一组任务的集合,服务定义了任务的属性。服务有两种模式:
replicated services
按照一定规则在各个工作节点上运行指定个数的任务。global services
强制在每个 node 上都运行一个且最多一个容器。
$ docker service create alpine ping baidu.com# 创建一个容器,让它来 ping 百度$ docker service lsID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 1/1 alpine:latest# 我们发现 MODE 是 replicated(默认)# REPLICAS 是 1/1 右边是要运行,左边是实际运行的数量$ docker service ps nostalgic_zhukovskyID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
snpjphe14ztv nostalgic_zhukovsky.1 alpine:latest linuxkit-00155d01020f Running Running 4 minutes ago# 查看这个 service 的任务(容器)
代码语言:javascript复制$ docker service update --replicas 3 nostalgic_zhukovsky# docker service update 用来更新一个 service# 这里让它运行 3 个任务$ docker service lsID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 3/3 alpine:latest$ docker service scale -d nostalgic_zhukovsky=10# 我们还可以通过 scale 来扩大缩小我们的服务$ docker service lsID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 7/10 alpine:latest
代码语言:javascript复制$ docker service scale nostalgic_zhukovsky=2$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS584829ddf89f alpine:latest "ping baidu.com" 56 seconds ago Up 49 seconds
8678f634f14b alpine:latest "ping baidu.com" 18 minutes ago Up 18 minutes$ docker rm -f 8678f634f14b$ docker service ps yjoejehnu4yxID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERRORsnpjphe14ztv nostalgic_zhukovsky.1 alpine:latest linuxkit-00155d01020f Running Running 17 minutes ago
vk0ih9ds1kdm nostalgic_zhukovsky.2 alpine:latest linuxkit-00155d01020f Running Running 9 seconds ago
m4ylibww9l87 _ nostalgic_zhukovsky.2 alpine:latest linuxkit-00155d01020f Shutdown Failed 15 seconds ago "task: non-zero exit (137)"# 我们发现我们强制关闭一个容器,swarm 自动帮我们新建了一个。
不像我们自己启动容器,使用 docker swarm 我们只要说出自己任务就可以了,swarm 会自己有没有完成任务,比上面发现要运行两个,却发现一个被我们删了,它就会再创建一个容器。
多节点
创建 Swarm 集群,我们要创建多个 node,就需要多台主机或虚拟机。
我们可以去 play with docker 网站来创建多个节点,这个网站是免费的,但是 4 个小时后就会销毁你创建的所有虚拟机。
我们还可以使用 docker machine 帮我们快速的在本地创建多台 docker 虚拟机。
再或者去云服务商那里买几台云主机,使用 这个脚本 快速安装 docker。
docker machine
Docker Machine 是 Docker 官方编排(Orchestration)项目之一,负责在多种平台上快速安装 Docker 环境。它使用 Go 语言实现。
对于 windos 和 mac 安装 docker 的时候就自带了 docker machine。
对于 linux 可以使用如下命令安装。
代码语言:javascript复制$ sudo curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine$ sudo chmod x /usr/local/bin/docker-machine
代码语言:javascript复制$ docker-machine create
--engine-registry-mirror https://dockerhub.azk8s.cn
-d virtualbox
node1# 创建一个驱动是 virtualbox 类型的 docker 主机# --engine-registry-mirror 用来指定镜像加速器# 驱动除了是 virtualbox 还可以是 macOS 的 xhyve 驱动# 它比 virtualbox 运行效率要高
如果是在 Windows 10 上执行上面那个命令就会报错,因为 Windows 10 安装 Docker for Windows 之后不能再安装 VirtualBox,也就不能使用 virtualbox 驱动来创建 Docker Machine。
我们可以使用 hyperv
驱动,而且必须事先在 Hyper-V 管理器中新建一个 外部虚拟交换机 执行下面的命令时,使用 --hyperv-virtual-switch=MY_SWITCH
指定虚拟交换机名称。
$ docker-machine create
--engine-registry-mirror https://dockerhub.azk8s.cn
--hyperv-virtual-switch PVS
-d hyperv
node1# 然后我们以管理员身份打开终端执行这条命令
# --hyperv-virtual-switch 后面是刚刚取的虚拟交换机名称
# 我们重复上面那个命令,分别创建 node1 node2 node3 3个节点
创建好 3 个 docker 主机后,我们登录上去创建 swarm 集群。
代码语言:javascript复制$ docker-machine ssh node1# 登录到 node1$ docker swarm init# docker swarm join --token SWMTKN-1-1ve56x9y7784s0enqvhghnls4rbjazwxovz5me1cmqub9jlgwa-83msyrvzwpgrk46fsifmht8nj 192.168.1.168:2377
但后去 node2 和 node3 执行上面输出的那条命令,然后再登录到 node1。
代码语言:javascript复制$ docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
2y3ih8gtvit225155x5u33690 * node1 Ready Active Leader 18.09.7
q0ag3cuma8sqiszjlou2ttc3t node2 Ready Active 18.09.7
cx4yap7apqnii58xmmxs8mdo7 node3 Ready Active 18.09.7
可以看到这个 Swarm 中已经有 3 个节点了,其中 node1 是管理节点,剩下两个是工作节点。
worker 节点没有管理集群,为了方便我们可以复制 manager 的 token 让另外两个节点也成为 manager。
或者在 node1 上执行:
代码语言:javascript复制$ docker node update --role manager node2$ docker node update --role manager node3
网络
在使用多节点之前,我们先在本地执行一下,如下命令:
代码语言:javascript复制$ docker network lsNETWORK ID NAME DRIVER SCOPE
rkfr0w8kpqvi ingress overlay swarm
我们可以看到一个驱动类型是 overlay
类型的网络,那是刚才 swarm 帮我们创建的。
要使用服务发现,需要相互通信的 service 必须属于同一个 overlay
网络。自动创建的 ingress
没有提供服务发现,必须创建自己的 overlay 网络。
它是 Swarm 范围桥接网络,容器可以跨主机互相访问,就像它们在一台主机上一样。通过 overlay 网络,主机与容器、容器与容器之间可以相互访问。
它只在一个 Swarm 内部,这样就不会搞乱主机网络配置。
使用
现在我们使用 Swarm 来创建一个 drupal 网站(Drupal 是自由开源内容管理系统,用PHP语言写成)。
登录到 node1 首先创建一个自己 overlay
网络
$ docker network create -d overlay drupal
然后创建一个 postgres
service,用来存储数据。
$ docker service create --name psql --network drupal -e POSTGRES_PASSWORD=mypass postgres# --network 加入到我们刚刚创建的那个网络# -e 添加环境变量,用来指定 postgres 密码
然后就是 drupal service。
代码语言:javascript复制$ docker service create --name drupal --network drupal -p 80:80 drupal$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS4c1rgorpz5hp drupal replicated 1/1 drupal:latest *:80->80/tcp
wyd2y3roqwh3 psql replicated 1/1 postgres:latest
$ docker service ps psql
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
v05c9pi98i4a psql.1 postgres:latest node2 Running Running 10 minutes ago
$ docker service ps drupal
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
qz4443imlexv drupal.1 drupal:latest node3 Running Running 6 minutes ago
# 可以看到 postgres 运行在 node2,drupal 运行在 node3
我们打开浏览器,输入上面 node1 的 ip 地址。可以使用 docker-machine ls
或 docker-machine ip node1
查看。
打开过后就可以看到 drupal 页面,快速填写一下配置。
我们可以输入一下 node2 和 node3 的 ip 到浏览器。
我们发现同样可以访问到 drupal 网站,虽然它只运行在 node2 上。
routing mesh
swarm 向外暴露端口,所有节点都参与进入 routing mesh 中。每个节点都能接受暴露端口连接,即使 node 中没有运行这个 service。routing mesh 会将请求路由到运行这个 service 上活跃的容器中,它在所有节点上做负载均衡。
当内部容器对容器通信时,它们使用虚拟 IP (VIP)通信,它是 Swarm 虚拟网络中的私有 IP,它会让请求分布到所有服务任务中,比如我们有 10 个 worker 容器,我们无需做负载均衡,swarm 已经帮你做了。
当外部流量访问监听的端口时,所有 node 都会监听该端口流量,然后它会将该流量负载均衡的路由到合适的容器。如果是不同 node 那么就会通过虚拟网络路由到容器,如果是同一个 node 上,那么就会直接路由到容器的端口。
可以看到每个节点中都会有一个 load balancer。swarm 的负载均衡会把你的请求路由到一个任意节点的可用的容器上。routing mesh 在 swarm 节点的所有 IP 上监听 published 端口。
如果在一个服务器端口上运行一个网站,我们就会失去 swarm 的负载均衡,因为它是 TCP 层面的负载均衡而不是 DNS 层面的。这时候我们就需要一个外部负载均衡,可以使用 nginx 或 HAProxy。
滚动升级
滚动升级是一次只升级一部分副本,不一次性全部升级,它降低了应用更新的风险,如果某个副本更新失败,整个更新将暂停,其他副本则可以继续提供服务。在更新的过程中,总是有副本在运行的,也保证了业务的连续性。
现在我们把 nginx:1.16
版本升级到 nginx:1.17
。
$ docker service create --name web --replicas=3 nginx:1.16# --replicas=3 启用 3 个副本
代码语言:javascript复制$ docker service update --image nginx:1.17 web# 升级到 1.17# swarm 会停止一个容器,更新它,如果失败就会暂停整个更新过程
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
PORTS
nqch5ivpmmcd web.1 nginx:1.17 linuxkit-00155d010229 Running Running 57 seconds ago
uclfd0iev0sk _ web.1 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown 58 seconds ago
ku2aomr1br3d web.2 nginx:1.17 linuxkit-00155d010229 Running Running about a minute ago
fpqp9mdfez9r _ web.2 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown about a minute ago
ww6hxdsutlog web.3 nginx:1.17 linuxkit-00155d010229 Running Running about a minute ago
hvy2g68j9ah4 _ web.3 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown about a minute ago
Swarm 还有回滚功能,可以通过 --rollback
快速恢复到更新之前的状态。
$ docker service update --rollback web# 也可以写成 docker serivice rollback web
--rollback
只能回滚到上一次执行 docker service update
之前的状态,并不能无限制地回滚。
控制 Service 运行的节点
Swarm 会自动帮我们把 Service 分配到合适的 node 上,性能高的 node 分配的 Service 就越多。除了 Swarm 自动分配,我们也可以手动分配。
我们可以给 node 设置 label,然后让 Service 运行在指定 label 的 node 上。
代码语言:javascript复制$ docker node update --label-add key=value node1
$ docker node inspect node1 --pretty
...
Labels:
- key = value
...
然后创建 Service
代码语言:javascript复制$ docker service create --constraint node.labels.key==value
--replicas 3
--name web
--p 80:80
nginx
# --constraint 限制将 service 部署到指定的 node$ docker service inspect web --pretty...
Placement:Contraints: [node.labels.key==value]
...
我们还可以更新 Service constraint,让它更换 node。
代码语言:javascript复制$ docker service update --constraint-rm node.labels.env==test web
$ docker service update --constraint-add node.labels.env==prod web
如果我们想 swarm 强制更新可以使用 --force
$ docker service update --force web
Secret
我们经常要向容器传递敏感信息,比如上面传递的 POSTGRES_PASSWORD=mypass
环境变量。密码是以明文的形式写在命令中,潜在的巨大的安全风险。
我们可以通过 Secret 安全地管理 Swarm 集群中密码、密钥证书等敏感数据,并允许在多个 Docker 容器实例之间共享访问指定的敏感数据。它最大支持 500KB 的字符串或二进制内容。
Secret
会被加密的保存在管理节点的硬盘上,被加密传输。只有被允许的容器才能查看 Secret
,在容器中它只会被存在内存中,可以在 /run/secrets/<secret_name | secret_alias>
访问到。
Secret
可以通过两种方式创建,一种是文件另一种是 stdin
创建。
$ docker secret create psql_user psql_user.txt$ echo 'mypass' | docker secret create psql_pass -
代码语言:javascript复制$ docker service create --name psql --secret psql_user --secret psql_pass
-e POSTGRES_PASSWORD_FILE=/run/secrets/psql_pass
-e POSTGRES_USER_FILE=/run/secrets/psql_user postgres
--secret
用来指定 Service 能使用那个 secret
。
如果我们现在删除 Service 的 secret
,可以使用 --secret-rm
$ docker service update --secret-rm psql_pass psql# 删除 Service 的 secret。# 容器会自动重建,因为 Service 是容器的一部分。
管理配置文件
Swarm 除了可以帮我们管理敏感数据,还可以帮我们管理配置文件。config
与 secret
命令的使用方法完全一致。
$ docker config create redis.conf redis.conf
$ docker service create --name redis
# --config source=redis.conf,target=/etc/redis.conf
--config redis.conf
-p 6379:6380
redis:latest
redis-server /redis.conf
Stack
我们每次启动一个 service 的时候都要写一堆命令行命令非常的笨拙冗长。
我们可以用 Stack 来简化这个操作。Stack 是 Swarm 调用的抽象,和 docker-compose
一样它也接收 docker compose
文件,用来定义 Services, Networks 和 Volumes 等。
我们使用 docker stack deploy
而不是 docker service create
,Stack 会帮我们管理这些对象。
我们可以直接用 docker-compose.yml
文件,但是不能 build
镜像,Swarm 只接收构建好的镜像,新加了一个 deploy
字段。当使用 docker-compose
执行这个文件时,会忽略 deploy
字段。docker stack
中会忽略 build
字段,所以我们可以开发和发布都使用一个 docker-compose.yml
文件。
version: '3.7'services:
db:
image: mysql:5.7
networks:
- wordpress
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets: # 指定 secret
- db_root_password
- db_password
deploy: # 部署
update_config: # 更新规则
parallelism: 2 # 一次两个
delay: 10s # 更新延迟 10s,给 app 一个启动时间
restart_policy: # 重启规则
condition: on-failure
placement:
constraints: [node.role == manager] # 只部署在 manager 节点
wordpress:
depends_on:
- db
image: wordpress
networks:
- wordpress
ports:
- '80:80'
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
deploy:
replicas: 2
labels: [APP=WORDPRESS] # 添加自定义 label
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
visualizer: # 可视化页面,打开浏览器 8080 端口可以看见效果
image: dockersamples/visualizer:stable
networks:
- wordpress
ports:
- "8080:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]volumes:
db-data:networks:
wordpress:secrets:
db_password:
file: db_password.txt
db_root_password:
file: db_root_password.txt
然后我们就可以部署这个 stack
代码语言:javascript复制$ docker stack deploy -c docker_compose.yml wordpress# -c 指定配置文件# deploy 也可以换成 up$ docker stack lsNAME SERVICES ORCHESTRATOR
wordpress 2 Swarm$ docker stack ps wordpressID NAME IMAGE NODE DESIRED STATE CURRENT STATE
qk67so5btzln wordpress_wordpress.1 wordpress:latest linuxkit-00155d01022b Running Running 2 minutes ago
vmnk1gb7zl05 wordpress_db.1 mysql:latest linuxkit-00155d01022b Running Running 2 minutes ago
s5x09e5q8fr9 wordpress_wordpress.2 wordpress:latest linuxkit-00155d01022b Running Running 2 minutes ago$ docker stack services wordpressID NAME MODE REPLICAS IMAGE PORTS
kuro9uavayjq wordpress_wordpress replicated 2/2 wordpress:latest *:80->80/tcp
l667za415j6s wordpress_db replicated 1/1 mysql:latest$ docker network lsNETWORK ID NAME DRIVER SCOPE
xnunueoy9t5m wordpress_wordpress overlay swarm$ docker secret lsID NAME DRIVER CREATED UPDATED
6wxq7esrrikmiq9rnhnl1uuzk wordpress_db_password 5 minutes ago 6 minutes ago
scvzglyfk4cjxfco1nytoajjz wordpress_db_root_password 5 minutes ago 6 minutes ago
通过上面可以看到 stack 自动帮我们创建 secret, network 等,我们只需要一个命令就可以了,如果我们更新这个配置文件,我们可以再执行下面这个命令更新。
代码语言:javascript复制$ docker stack deploy -c docker-compose.yml wordpress
如果要停止这个 stack
,可以执行 rm
命令
$ docker stack rm wordpress
多配置文件
我们可以只是用一个 yaml
文件完成本地和生产环境开发,但是当我们的应用变得复杂的时候,一个配置文件可能没那么好用,这时候我们就可以使用多配置文件。
比如我们可以新建如下 yaml
文件
docker-compose.yml
docker-compose.override.yml
docker-compose.test.yml
docker-compose.prod.yml
docker-compose.yml
作为其他配置文件的基础,它会合并到其他配置文件上。
docker-compose.override.yml
当执行 docker-compose up
的时候 docker-compose
会自动将 docker-compose.yml
和名为 docker-compose.override.yml
合并成为一个文件执行。
docker-compose.test.yml
用于 CI 环境,我们可以执行
$ docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d# 基本文件在前面
docker-compose.prod.yml
生产环境文件,我们可以使用 docker-compose
将它和 docker-compose.yml
合并成一个文件再交给 docker stack
$ docker-compose -f docker-compose.yml -f docker-compose.prod.yml config > out.yml