Docker Swarm
Outline:
- Basic Idea
- Commands
- Swarm Service
- Swarm Lock
Preface
Docker Swarm是一个Docker容器集群编排方案, 同类型的还有K8S, 简而言之, K8S > Docker Swarm. 尽管如此, 但Docker Swarm由Docker原生支持, 而且比K8S简单, 学习它可以初步了解一下容器编排., 可以当作K8S的简易入门.
- 也就是说Docker Swarm就是个玩具...
本章需要结合Docker Network理解
使用Swarm, 需要打开Swarm所需端口:
TCP/2377
: Swarm的集群管理默认使用2377端口TCP/7946
,UDP/7946
: Swarm的节点发现使用7946端口
Basic Idea
Docker Swarm由Docker原生支持, 能够创建Dokcer容器集, 并进行编排
node
node: 一个集群( Swarm )由多个node组成, 一个node就是一台docker主机. node分为Manager和worker
- Manager: 负责集群的控制面板( Control Plane ), Manager
- Worker
Swarm node采取主从方式, 有多个Manager, 但同一时刻有且仅有一个Manager处于Active, 处于Active的Manager称为leader
, 其余Manager称为follower
leader
是唯一一个对Swarm进行控制的节点, 如果follower
接收到了Swarm命令, 它会将其转发到主节点leader
,follower
是Raft的术语, Swarm采用了Raft共识算法
Raft
Docker Swarm采用了Raft共识算法, 以下是两个原则:
- 部署奇数个Manager. 如果Manager之间分区, 掌握多数Manager的分区就会对Swarm进行管理; 反之,如果偶数个分区,会出现Manager数量相同的情况,无法进行决策,称为Split-Brain
- Manager数量控制在3~5个. 更多的Manager意味着要花更长的时间达成共识
- Manager可以进行分区, 但是各分区之间的网络一定要确保畅通
service
Docker Swarm的最小调度单元是服务, 也就是Docker Compose里定义的"服务"
- 服务就是容器的组合, 而且提供了一些高级特性, 比如一些配置文件
etcd
etcd( European Toiletries and Cosmetics Database) : a distributed, consistent key-value store for shared configuration and service discovery, 许多分布式系统都采用了etcd, 包括K8S和Docker Swarm
etcd位于所有管理节点上, 保存了Swarm的配置和状态
etcd运行在内存, 并保持数据的最新状态
token
任何node加入Swarm的唯一凭证就是准入令牌<token>
token的格式为: PREFIX - VERSION - SWARM ID - TOKEN
PREFIX
: 永远是SWMTKN
VERSION
: Swarm的版本SWARM ID
: Swarm认证信息的哈希值TOKEN
: 其内容决定了该令牌是Manager还是worker的准入令牌因此,对于指定了加入Swarm的角色的token, 除了
TOKEN
字段外,其他字段应该相同
TLS
Docker Swarm采用TLS, 每个node都有自己的客户端证书, 证书的更新周期默认是90天
查看CA配置:
1 | docker system info |
指定外部CA
docker swarm init
加--external-ca
参数来指定外部CA
更新证书的更新周期
可以用docker swarm update
配置证书的更新周期:
1 | # 更新到30天 |
CA配置
1 | docker swarm ca |
- 使用时用
--help
来查看具体功能
Commands
- 只有manager能查看集群信息, worker无权查看, 因此
docker service ls
之类的命令只有Manager能用
创建Swarm
创建一个新的Swarm, 执行该命令的节点会成为第一个Manager, 也是根CA节点, 且切换到Swarm模式, :
1 | docker swarm init |
--advertise-addr <ip:port>
: 指定该节点公布给外界的ip和端口( 用于让其他服务器加入swarm ). 这是可选的,但建议总是手动设置这两个属性- 可以指定一个节点上没有的ip用于负载均衡
- 端口默认是
2377/tcp
- 最好一定要指定一个公网ip. 否则默认会公布一个内网ip, 例如
10.0.16.7
, 假如其它服务器不在同一个内网, 是无法通过该节点的令牌( 带有其公布的ip )加入swarm的
--listen-addr <ip:port>
: 指定用于承载Swarm流量的ip和端口, 通常与--advertise-addr
相匹配--autolock
: 开启swarm锁--external-ca
: 指定外部CA
获取准入令牌
在Manager上, 获取加入到该Swarm的准入令牌( token ):
1 | docker swarm join-token manager # 获得的token能让其他节点作为manager加入 |
更新准入令牌
废除原有的准入令牌,再发布新令牌
1 | docker swarm join-token --rotate manager # 更新已发布的manager的准入令牌 |
- 新令牌和旧令牌只有
TOKEN
字段不同,SWARM ID
还是相同的
可以废除当前令牌, 再发布新的
加入Swarm
指定节点加入某Swarm
1 | docker swarm join --token <token> <target-ip:port> |
- 可以看到, 节点以什么身份加入Swarm, 完全取决于所给的Token, 节点自己没有自定义的权利
查看Swarm
列出Swarm中所有节点:
1 | docker node ls |
仅限Manager使用, worker无权查看集群状态
MANAGER STATUS
一列没有任何显示的是workerID列显示星号(
*
)的是执行该命令的节点注意, 节点名就是主机名. 如果你觉得主机名没有可读性, 可以改名, 然后重启docker:
1
2hostnamectl set-hostname acs-services-node1
service docker restartSTATUS:
- Ready:集群中的节点
- Down:集群中 leave 的节点
MANAGER STATUS
- 无值:表示该节点是worker
- Leader:集群的第一个管理者,管理整个集群,编排决策
- Reachable:属于管理节点,当Leader节点不可用后,该节点有权利竞选Leader
- Unreachable:管理节点不可用,无法与其它管理节点连接(节点退出集群)
availability
- Active:活动的节点,可以被调度器分配任务
- Pause:不能分配新任务,已存在的任务继续运行
- Drain:不能分配新任务,已存在的任务会被停止,并将这些任务调度到在可用节点上:
离开Swarm
节点主动离开swarm:
1 | docker swarm leave |
--force
: Manager leave需要加--force
删除节点
有时只能在其他节点上强制删除另一些节点( 比如, 某些节点是云服务器, 已经过期了, 此时没办法登录这些节点让它们主动leave
, 只能在其他节点上删除它 ):
1 | docker node rm <noce> |
- 仅能删除worker, 如果要删除manager, 需要降级为worker. 或者也可以用
-rm
更改节点角色
将manager角色降级为worker:
1
docker node demote <hostname>
将worker角色升级为manager:
1
docker node promote <hostname>
创建Service
创建新服务:
1 | docker service create --name <service-name> \ |
docker service create
格式与docker container run
类似--replicas
: 告知Docker应该总是有n个此服务的副本, 这定义了期望状态
- 默认为1
- Leader会按要求在Swarm中实例化n个副本,
- 服务副本使用相同的镜像和配置
- Manager也会作为worker运行
-p, --publish port
:<host-port>:<container-port>
, 宿主机端口映射到容器端口,由于是swarm模式,该宿主机端口会监听在集群所有节点上注意,只有拥有该镜像的节点才会被调度到运行该服务, 可以从
docker service ps <service>
中看到,某些节点“拒绝了任务调度,因为”:1
No such image: <imaeg>
即使该service创建失败, 也依然会占用端口等资源.
--resolve-image never
: 在很少见的情况下, 加了这个选项可以避免平台不支持的问题, 不过我没遇到过swam集群中如果管理节点使用一个本地镜像创建服务,在给子节点分配任务时, 子节点就无法从公共docker hub上拉取到这个自建的镜像. 可以使用自建的本地registry.
也就是说, swarm集群没法使用本地镜像, 即使集群中全都是manager也不行, 因为manager也是worker; 甚至即使集群中只有一个节点, 它是manager, 且它本地有同名镜像, 也依然会使用registry的而不是本地的.
一个例子是, 我在macbook上build了镜像
lyklove/frontend_volatile_reborn:latest
, 然后push到了dockerhub, 结果在linux服务器上使用:1
2
3
4
5docker service create \
--name frontend_volatile_reborn_svc \
--network volatile_reborn -p 81:80 \
--replicas 1 \
lyklove/frontend_volatile_reborn:latest报错:
1
1/1: no suitable node (unsupported platform on 1 node)
显然,这说明了我service基于的镜像是mac的, 不是linux的, 尽管我的在服务器上也有同名的该镜像.
- 当然, 你也可以使用自建的本地registry, 不过也太麻烦了, 还是每次都push到dockerhub吧.
e.g.
1
2
3
4❯ docker service create --name web-fe \
-p 4000:8080 \
> --replicas 5 \
> nigelpoulton/pluralsight-docker-ci
Swarm会持续确保服务的实际状态和期望状态一致(也就是K8S中的调协循环)
- Swarm会在后台轮询检查所有服务, 来持续比较服务的实际状态和期望状态
查看Service
列出Swarm中所有运行中的服务:
1 | docker service ls |
查看service在哪个node:
1 | docker service ps vo |
进一步查看Swarm中某个服务的信息, 比如哪些节点在运行该服务实例:
1 | docker service ps <service> |
- 在运行任务的节点上运行
docker ps
也能看到这个service对应的容器
查看服务的详细信息:
1 | docker service inspect --pretty <service> |
--pretty
: 文本更可读
改变Service
对运行中的服务的属性进行变更, 这要求节点都处于overlay网络.
1 | docker service update <service> |
--autolock=true
: 开启swarm锁滚动更新:
1
2
3
4
5docker service update \
--image <new-image> \ # 指定新镜像
--update-parallelism <num-of-replica-per-update> \ # 每次更新的副本数
--update-delay 20s uber-svc # 每次更新的延迟
<service>
所有update操作都是持久的, 可以用docker service inspect
查看
服务扩缩容
对服务副本个数进行增减
1 | docker service scale |
删除Service
删除服务, 该命令不会要求确认
1 | docker service rm |
配置CA
1 | docker swarm ca |
服务滚动更新
1 | docker service update \ |
--update-parallelism <num>
: 每次更新num
个副本--update-delay <time>
# 每次更新有time
s 延迟
查看Service日志
查看服务的日志
1 | docker service logs |
Swarm Service
创建Service
创建新服务:
## 副本模式 vs 全局模式
服务的复制模式:
- 副本模式(
replicated
): 默认模式, 会部署期望数量的副本,并尽可能均匀地讲个副本分布在各个集群中- 这意味着一个主机可能被部署多个副本
- 全局模式了每个节点上仅运行一个副本
扩缩容
1 | docker service scale=<expected-number> |
删除服务
1 | docker service rm <service-name> |
验证服务是否被删除:
1 | docker service ls |
- 只有Manager可用
滚动更新
滚动更新需要节点处于overlay网络中. 如果位于bridge网络中, 则其他节点是不会收到更新的
先创建一个overlay网络,:
1 | docker network create -d overlay uber-net |
- 由Docker Network得, 一个Swarm节点直到在覆盖网络上启动容器时,才会将自己接入该网络, 因此,此时在其他节点上,看不到该网络
然后创建一个服务,部署4个副本, 连接到overlay网络
1 | ❯ docker service create --name uber-svc \ |
- 此时在其他节点上启动了容器,因此能看到该网络
1 | ❯ docker service update \ |
- 此时在其他节点上
docker container ls
可以看到已经运行了新版本的镜像
Swarm Security
Docker Swarm提供了许多安全特性( 许多已经被前文提到 ) , 包括:
- 加密 node ID
- 基于TLS的认证
- 安全准入令牌
- 周期性证书更新
- CA配置
- 加密集群存储: 目前的存储基于
etcd
, 且会在Swarm manager间自动复制, 存储默认加密 - 加密网络
Swarm Lock
重启旧的Manager, 进行备份恢复等操作都会对集群造成影响, 为此, Docker Swarm提供了锁机制, 这会强制要求重启的管理节点在提供一个集群解锁码之后才能接入集群
- 需要手动开启
开启锁
在创建Swarm时开启锁:
1 | docker swarm init --autolock |
在已有Swarm上开启锁:
1 | docker swarm update --autolock=true |
上锁后会得到解锁码:
1 | ❯ docker swarm update --autolock=true |
验证锁效果
开启锁后, 重启一个管理节点:
1 | service docker restart |
1 | docker node ls |
输出为:
1 | error response from daemon: Swarm is encrypted and needs to be unlocked before it can be used. Please use "docker swarm unlock" to unlock it. |
解锁
1 | docker swarm unlock |
Swarm实战: volatile
我们要将前端服务volatile_frontend_svc
(容器监听80端口)部署到集群,对外暴露集群的81端口
Server Config
主机名 | 主机ip | 角色 |
---|---|---|
lyk阿里云服务器 | ** | master,CICD工作节点 |
lyk华为云服务器 | ** | master |
lyk腾讯云服务器 | ** | master |
3台Master, 3台Worker( Master也同时作为Worker,因此实际上只有三台主机 )
Prerequists
前置准备:所有节点必须打开:
- UDP/4789: 绑定到VTE
- TCP/2377: Swarm的集群管理默认使用2377端口
- TCP/7946, UDP/7946: Swarm的节点发现使用7946端口
- TCP81: 暴露所有运行前端server的node的的81端口
步骤
先在阿里云主机上创建第一个master节点:
1
docker swarm init --advertise-addr **:2377
- 填该master节点的公网ip
生成令节点作为master加入集群的令牌:
1
docker swarm join-token manager
- 该命令的输出即为令牌
在其他节点上使用上述令牌,使其作为master加入该swarm集群。 成功后执行下述命令,查看集群中的节点:
1
docker node ls
在任意master上创建overlay网络,名为
volatile
:1
docker network create -d overlay volatile
- 一定要先创建网络, 否则其他节点无法加入该网络
在master上基于镜像创建新服务
volatile_frontend_svc
,并使用网络volatile
:1
2
3
4
5docker service create --name volatile_frontend_svc \
--network volatile \
-p 81:80 \
--replicas 3 \
lyklove/volatile_frontend:latest- 这里设置服务实例数为3
- 我们将集群的81端口映射到了容器的80端口。 因此访问集群的任意节点的81端口的流量最终都会被转发到运行了该服务副本的节点
(后续)滚动更新:
1
docker service update --image lyklove/volatile_frontend:new --update-parallelism 2 --update-delay 1s volatile_frontend_svc
- 基于新镜像
lyklove/volatile_frontend:new
更新服务,并在其他节点上也进行服务更新 - 该命令可以在任意拥有该新镜像的master节点上执行
集成CICD
可以发现,CICD只需要将服务打包成镜像,然后利用该镜像滚动更新就行了
Jenkins脚本jenkinsfile.groovy
:
1 | // ... |
- 注意, jenkinsfile里只写了
docker service update
, 而没有create. 因此, 需要先手动在server上create service, 后续cicd时才能够update
集群使用
通过[host-ip]:81
访问前端
其中host-ip
可以是集群中任意节点的ip
Swarm实战: volatile_reborn
Server Config
由于华为云服务器过期了, 这次只有两个节点. 这也意味着只能有一个专业的worker, 否则会发生brain-split
主机名 | 主机ip | 角色 |
---|---|---|
lyk腾讯云服务器 | ** | master, CICD工作节点 |
lyk阿里云服务器 | ** | master |
1台Master, 2台Worker(算上Master)
Steps
照常配置服务器端口
把之前volatile时期配置的华为云的node删掉. 由于server已经登陆不上了, 没法主动leave, 只能在其他节点上删除:
1
docker node rm k8s-master //k8s-master是华为云服务器的hostname
Create Token:
1
docker swarm init --advertise-addr 123.56.20.222:2377
Let other nodes join the swarm with this token
Create overlay network on any master, 名为
volatile_reborn
:1
docker network create -d overlay volatile_reborn
Create frontend service on manager:
1
2
3
4docker service create \
--name frontend_volatile_reborn_svc \
--network volatile_reborn -p 81:80 \
--replicas 2 lyklove/frontend_volatile_reborn:latest-linuxCreate backend service on manager:
1
2
3
4docker service create \
--name backend_volatile_reborn_svc \
--network volatile_reborn -p 8000:8000 \
--replicas 1 lyklove/backend_volatile_reborn:latest-linuxCreate eureka service on manager:
1
2
3
4docker service create \
--name backend_eureka_volatile_reborn_svc \
--network volatile_reborn -p 8001:8001 \
--replicas 1 lyklove/backend_eureka_volatile_reborn:latest-linux后续滚动更新和集成cicd都和volatile类似
Problems
- swarm面对一个镜像名, 似乎会优先使用dockerhub的镜像?
- --resolve-image never