Docker Swarm的介绍与使用
What
Docker(容器) Swarm(集群)——简单来说,Docker swarm就是容器集群(包含了集群管理和编排功能),由Docker公司研发并且在Dokcer v1.12版本后自带此服务(早期作为一项独立服务叫做swarmkit,需要单独安装)。
集群管理:创建新集群、升级集群的主节点和工作节点、执行节点维护(例如内核升级)和升级运行集群的版本等。
容器编排:应用一般由单独容器化的组件(通常称为微服务)组成,这些组件必须按特定顺序在网络中进行组织并照计划运行,这种对多个容器进行组织的流程即称为容器编排(使用Docker Compose实际就是容器编排)。
For Example:
standalone-mysql-5.7.yaml
1 | version: "2" |
Key Concepts
- 架构

从架构图可以看出 Docker Client使用Swarm对 集群(Cluster)进行调度使用。
上图可以看出,Swarm是典型的master-slave结构,通过发现服务来选举manager。manager是中心管理节点,各个node上运行agent接受manager的统一管理,集群会自动通过Raft协议分布式选举出manager节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了DNS的负载均衡和对外部负载均衡机制的集成支持。
node(节点): 一个节点是docker引擎集群的一个实例,也可以将其视为一个Docker节点。可以在单个物理计算机或云服务器上运行一个或多个节点,但在部署生产集群时通常将节点分布部署在多个物理和云计算机上。
要将应用程序部署到swarm,需要将服务定义信息提交给 manager node(管理节点)。管理节点将称为tasks(任务)的工作单元分派给 worker nodes(工作节点)。
管理节点还执行编排和集群管理功能,从而使群集处于需要的状态。管理节点会选举一个领导者来执行编排任务。
工作节点接收并执行从管理器节点分派的任务。默认情况下,管理器节点也可以作为工作节点运行,但可以将它们配置为仅运行管理任务并且仅是管理节点。代理程序在每个工作节点上运行着一个代理程序,这个代理程序会报告分配给这个工作节点的任务的详细信息。工作节点会通知管理节点分配给它的任务的当前状态,以便管理节点可以维护每个工作节点的状态。service(服务):服务是要执行在节点上的一组task(任务)的定义,它是集群系统的核心结构,同时也是用户与swarm系统进行交互的核心内容。Service有两种运行模式,一种是replicated,swarm会将服务所需的任务按照你定义的数量复制部署在多个节点上;另一种是global,在所有符合运行条件的Node上,都运行一个服务所需的任务。
task(任务):一个任务是一个docker容器和容器内运行命令的集合。它是swarm任务调度的原子单位,管理节点会根据服务内对任务的定义将任务分派到工作节点,一旦一个任务分配到了一个节点,这个任务就不能再移动都其它节点,他只能再一开始分派到的节点上运行或者直到失败。
负载均衡:swarm使用了入口负载均衡器来把服务开放给swarm外部,swarm可以自动给服务分配外部端口也可以由用户自定义外部端口,如果用户不定义外部端口的话,swarm会在30000~3276这个范围内随机给服务分配一个端口。外部组件比如云负载均衡器或者nginx之类的组件,可以从集群内的任意一个节点(无论该节点是否在运行要访问服务的任务)来访问服务。所有集群内节点都可以路由入口连接到一个运行着任务的实例。另外swarm模式还有一个内部DNS组件来给服务自动分派一个DNS入口。swarm manager使用一个内部负载均衡器根据每个服务的DNS地址来分配集群内服务的请求。
Why
为什么使用docker swarm,其实也就是一些功能特点,满足了需求就用:
与Docker Engine集成的集群管理:使用Docker Engine CLI创建一组Docker引擎,您可以在其中部署应用程序服务。您不需要其他编排软件来创建或管理群集。
节点分散式设计:Docker Engine不是在部署时处理节点角色之间的差异,而是在运行时处理角色变化。您可以使用Docker Engine部署两种类型的节点,管理节点和工作节点。这意味着您可以很方便地从单个镜像开始构建整个群集。
声明式服务模型:Docker Engine使用声明式方法来定义应用程序堆栈中各种服务的所需状态。例如,您可以描述由具有消息队列服务和数据库后端的Web前端服务组成的应用程序。
可扩容与缩放容器:对于每个服务,您可以声明要运行的任务数。当您扩容或缩放时,swarm管理器通过添加或删除任务来自动适应,以保持所需的任务数量来保证集群的可靠状态。
容器容错状态协调:集群管理节点不断监视群集状态,并协调期望状态与实际状态之间的任何差异。例如,如果设置了一个运行10个副本容器的服务,然后托管其中两个副本容器的工作节点崩溃了,那么管理器将创建两个新副本容器以替换崩溃的副本容器。 swarm管理器将新副本容器分配给正在运行和可用的工作节点上。
多主机网络:您可以为服务指定承载网络。当swarm管理器初始化或更新应用程序时,它会自动为承载网络上的容器分配地址。
服务发现:Swarm管理节点为swarm中的每个服务分配唯一的DNS名称,并对运行的容器进行负载均衡。您可以通过嵌入在swarm中的DNS服务器查询在集群中运行的每个容器。
负载均衡:您可以将服务的端口公开给外部负载平衡器。在内部,swarm允许您指定如何在节点之间分发服务容器。
缺省安全:集群中的每个节点强制执行TLS验证和加密,以保护其自身与所有其他节点之间的通信。可以选择使用自签名根证书或来自自定义根CA的证书。
滚动更新:在已经运行期间,您可以增量地应用服务更新到节点。 swarm管理器允许您控制将服务部署到不同节点集之间的延迟。如果出现任何问题,您可以将任务回滚到服务的先前版本。
How
Set up(一些准备):
- 三台linux机器,之间网络互通并且都安装了docker(最好同一版本,安装方法可以参照以前的文章)
- 三台机器中选一台作为manager节点,记住该机器的ip,确保其它节点可以通过该ip访问到manager节点
- 开放三台机器的对应端口:2377(TCP,用于集群管理通讯),7946(TCP and UDP,用于节点间通讯),4789(UDP,用于服务负载网络)
Create a swarm(创建集群):
ssh连接到选定的管理节点上
在管理节点上运行如下命令
1
$ docker swarm init --advertise-addr <MANAGER-IP>
注意运行命令后的输出,其中docker swarm join命令就是用来将其它工作节点加入集群的命令,最后的docker swarm join-token manager是用于向集群内新增manager节点的,因此要记住token:
1
2
3
4
5
6
7
8
9
10$ docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.如果不小心忘记了token,可以在管理节点上运行docker swarm join-token worker来查看自动生成的加入工作节点命令
在管理节点上运行docker info命令可以看到如下输出显示当前集群的状态:
1
2
3
4
5
6
7
8
9
10
11
12
13$ docker info
Containers: 2
Running: 0
Paused: 0
Stopped: 2
...snip...
Swarm: active
NodeID: dxn1zf6l61qsb1josjja83ngz
Is Manager: true
Managers: 1
Nodes: 1
...snip...在管理节点上运行docker node ls命令可以看到如下输出显示当前集群节点信息,其中带*的这行表示你目前连接到了这个节点上:
1
2
3
4$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
dxn1zf6l61qsb1josjja83ngz * manager1 Ready Active Leader
向集群中加入节点:
ssh连接到剩余的两台作为工作节点的机器上
运行之前记录下来的加入工作节点的命令,具体如下:
1
2
3
4
5$ docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
192.168.99.100:2377
This node joined a swarm as a worker.ssh回到管理节点,运行docker node ls命令来查看两个工作节点是否被成功加入集群,具体如下:
1
2
3
4
5$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
03g1y59jwfg7cf99w4lt0f662 worker2 Ready Active
9j68exjopxe7wfl6yuxml7a7j worker1 Ready Active
dxn1zf6l61qsb1josjja83ngz * manager1 Ready Active Leader
部署服务到集群:
ssh连接到管理节点上
运行如下命令:
1
$ docker service create --replicas 1 --name helloworld alpine ping docker.com
docker service create: 用于创建服务的命令
–name helloworld:服务名称叫做helloworld
–replicas 1: 服务的任务副本(或者说容器数量)只保留一个副本
alpine :镜像名 ,代表 Alpine Linux container
ping docker.com: 容器内运行的命令
运行docker service ls查看运行中的服务,具体如下图:
1
2
3
4$ docker service ls
ID NAME SCALE IMAGE COMMAND
9uk4639qpg7n helloworld 1/1 alpine ping docker.com
查看服务:
查看服务详细信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14[manager1]$ docker service inspect --pretty helloworld
ID: 9uk4639qpg7npwf3fn2aasksr
Name: helloworld
Service Mode: REPLICATED
Replicas: 1
Placement:
UpdateConfig:
Parallelism: 1
ContainerSpec:
Image: alpine
Args: ping docker.com
Resources:
Endpoint Mode: vip查看服务运行节点:
1
2
3
4[manager1]$ docker service ps helloworld
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
helloworld.1.8p1vev3fq5zm0mi8g0as41w35 alpine worker2 Running Running 3 minutes在服务运行节点上使用docker ps 查看运行中的容器:
1
2
3
4[worker2]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e609dde94e47 alpine:latest "ping docker.com" 3 minutes ago Up 3 minutes helloworld.1.8p1vev3fq5zm0mi8g0as41w35扩容服务:
使用 docker service scale
= 来扩容,具体如下: 1
2
3$ docker service scale helloworld=5
helloworld scaled to 5查看扩容后的结果:
1
2
3
4
5
6
7
8$ docker service ps helloworld
NAME IMAGE NODE DESIRED STATE CURRENT STATE
helloworld.1.8p1vev3fq5zm0mi8g0as41w35 alpine worker2 Running Running 7 minutes
helloworld.2.c7a7tcdq5s0uk3qr88mf8xco6 alpine worker1 Running Running 24 seconds
helloworld.3.6crl09vdcalvtfehfh69ogfb1 alpine worker1 Running Running 24 seconds
helloworld.4.auky6trawmdlcne8ad8phb0f1 alpine manager1 Running Running 24 seconds
helloworld.5.ba19kca06l18zujfwxyc5lkyn alpine worker2 Running Running 24 seconds
删除服务:
使用docker service rm 即可删除服务,具体如下
1
2
3$ docker service rm helloworld
helloworld滚动更新服务:
以redis 为例,先创建一个有三个副本的redis服务:
1
2
3
4
5
6
7$ docker service create \
--replicas 3 \
--name redis \
--update-delay 10s \
redis:3.0.6
0u6a4s31ybk7yw2wyvtikmu50注意通过–update-delay声明了服务更新的延迟时间为10秒,也可以通过 单独或组合使用10s,10m,10h来表示不同的延迟,比如10m30s代表十分三十秒
开始更新,将redis版本进行升级:
1
2$ docker service update --image redis:3.0.7 redis
redis升级过程中可以使用docker service inspect 来查看升级状况,如下图是升级失败:
1
2
3
4
5
6
7
8
9
10docker service inspect --pretty redis
ID: 0u6a4s31ybk7yw2wyvtikmu50
Name: redis
...snip...
Update status:
State: paused
Started: 11 seconds ago
Message: update paused due to failure or early termination of task 9p7ith557h8ndf0ui9s0q951b
...snip...升级失败了重新运行升级命令docker service update redis即可
升级过程中可以使用docker service ps来查看升级状况,有的会是3.0.6 running,有的是3.0.7running,一旦升级完毕则全部都是3.0.7 running如下图 :
1
2
3
4
5
6
7
8
9$ docker service ps redis
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
redis.1.dos1zffgeofhagnve8w864fco redis:3.0.7 worker1 Running Running 37 seconds
\_ redis.1.88rdo6pa52ki8oqx6dogf04fh redis:3.0.6 worker2 Shutdown Shutdown 56 seconds ago
redis.2.9l3i4j85517skba5o7tn5m8g0 redis:3.0.7 worker2 Running Running About a minute
\_ redis.2.66k185wilg8ele7ntu8f6nj6i redis:3.0.6 worker1 Shutdown Shutdown 2 minutes ago
redis.3.egiuiqpzrdbxks3wxgn8qib1g redis:3.0.7 worker1 Running Running 48 seconds
\_ redis.3.ctzktfddb2tepkr45qcmqln04 redis:3.0.6 mmanager1 Shutdown Shutdown 2 minutes ago
排空一个节点:
某些情况下要把某个节点下线或者关机时,需要先将节点上的tasks转移,这个过程就叫做排空,如下图
1
2
3docker node update --availability drain worker1
worker1再运行docker service ps redis,可以看到worker1上的容器都停止了并在worker2上开了新的容器:
1
2
3
4
5
6
7$ docker service ps redis
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
redis.1.7q92v0nr1hcgts2amcjyqg3pq redis:3.0.6 manager1 Running Running 4 minutes
redis.2.b4hovzed7id8irg1to42egue8 redis:3.0.6 worker2 Running Running About a minute
\_ redis.2.7h2l8h3q3wqy5f66hlv9ddmi6 redis:3.0.6 worker1 Shutdown Shutdown 2 minutes ago
redis.3.9bg7cezvedmkgg6c8yzvbhwsd redis:3.0.6 worker2 Running Running 4 minutes当你需要使排空的节点可用,运行以下命令即可,之后该节点就可以正常接受新的任务了:
1
2
3$ docker node update --availability active worker1
worker1暴露服务端口
- 如docker中使用-p 8080:80一样,也可以使用同样的方式暴露服务端口,但下面的命令更为清晰:
1
2
3
4
5$ docker service create \
--name my-web \
--publish published=8080,target=80 \
--replicas 2 \
nginx之后你访问三台机器的 8080端口,都可以访问到nginx,即使那台机器上没有运行nginx的任务——这个特性被叫做routing mesh(路由混合)
- 需要更新服务端口的话可以使用如下命令
1
2
3$ docker service update \
--publish-add published=<PUBLISHED-PORT>,target=<CONTAINER-PORT> \
<SERVICE>默认暴露的是TCP端口,如果要暴露UDP端口需要如下设置
1
2
3
4
5
6
7
8
9$ docker service create --name dns-cache \
--publish published=53,target=53 \
--publish published=53,target=53,protocol=udp \
dns-cache
或者
$ docker service create --name dns-cache \
-p 53:53 \
-p 53:53/udp \
dns-cache跳过路由混合:
如果不想使用以上“访问三台机器的 8080端口,都可以访问到nginx,即使那台机器上没有运行nginx的任务”这个特性,可以通过对端口声明mode的方式进行控制,如下所示:
1
2
3
4$ docker service create --name dns-cache \
--publish published=53,target=53,protocol=udp,mode=host \
--mode global \
dns-cache这种情况下,访问机器的53端口,就要求那台机器上必须要有运行中的task,并且访问的就是那个task
使用服务承载网络:
如果集群中多个服务需要互相通讯,就可以使用承载网络,如下所示:
先创建网络
1
$ docker network create --driver overlay my-network
创建服务时声明其所在网络
1
2
3
4
5$ docker service create \
--replicas 3 \
--network my-network \
--name my-web \
nginx将已存在的服务加入到某个网络
1
docker service update --network-add my-network my-web
Docker stack
Docker Compose缺点是不能在分布式多机器上使用;Docker swarm缺点是不能同时编排多个服务,所以才有了Docker Stack,可以在分布式多机器上同时编排多个服务。stack就是一组共用一个承载网络的服务的集合。
例子:
- Setup
- service1:
1 | 4j |
1 | FROM openjdk:8 |
- service2:
1 | 4j |
1 | FROM openjdk:8 |
分别打包镜像
1
2docker build -t service1:V1 .
docker build -t service2:V1 .编写docker-compose.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15version: "3.9"
services:
service1:
image: "zhangjie47/service1:V1"
deploy:
replicas: 2
ports:
- "8080:8080"
service2:
image: "zhangjie47/service2:V1"
deploy:
replicas: 3
ports:
- "8081:8081"stack 部署
1
2
3
4
5
6
7# myapps是stack的自定义名称,使用具体路径的compose配置文件进行部署
docker stack deploy myapps --compose-file=docker-compose.yml
或者
docker stack deploy myapps -c docker-compose.yml
Creating network myapps_default
Creating service myapps_service1
Creating service myapps_service2查看docker stack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# 查看所有stack的信息
docker stack ls
NAME SERVICES ORCHESTRATOR
myapps 2 Swarm
# 查看某个stack中的所有任务信息
docker stack ps myapps
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
tvvujrf3qcr1 myapps_service1.1 zhangjie47/service1:V1 node1 Running Running 46 seconds ago
igjeydmmvzzm myapps_service1.2 zhangjie47/service1:V1 manager Running Running 46 seconds ago
7p5c96eplwl3 myapps_service2.1 zhangjie47/service2:V1 node1 Running Running 34 seconds ago
7shglsajip5d myapps_service2.2 zhangjie47/service2:V1 manager Running Running 39 seconds ago
upo0mr7j9tn1 myapps_service2.3 zhangjie47/service2:V1 node2 Running Preparing 41 seconds ago
# 查看某个stack中的所有服务信息
docker stack services myapps
ID NAME MODE REPLICAS IMAGE PORTS
icz3kjn0skb3 myapps_service1 replicated 2/2 masonzhang/service1:V1 *:8080->8080/tcp
myuzlwnrxag4 myapps_service2 replicated 3/3 masonzhang/service2:V1 *:8081->8081/tcp
# 查看具体服务信息等请参照上文
# 移除stack
docker stack rm myapps
Removing service myapps_service1
Removing service myapps_service2
Removing network myapps_default