一、什么是Docker Swarm?
Swarm是Docker公司推出的用来管理docker集群的平台,几乎全部用GO语言来完成的开发的,代码开源在https://github.com/docker/swarm, 它是将一群Docker宿主机变成一个单一的虚拟主机,Swarm使用标准的Docker API接口作为其前端的访问入口,换言之,各种形式的Docker
Client(compose,docker-py等)均可以直接与Swarm通信,甚至Docker本身都可以很容易的与Swarm集成,这大大方便了用户将原本基于单节点的系统移植到Swarm上,同时Swarm内置了对Docker网络插件的支持,用户也很容易的部署跨主机的容器集群服务。
Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排项目,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署,显然 Docker Swarm 会更加适合。
从 Docker 1.12.0 版本开始,Docker Swarm 已经包含在 Docker 引擎中(docker swarm),并且已经内置了服务发现工具,我们就不需要像之前一样,再配置 Etcd 或者 Consul 来进行服务发现配置了。
Swarm deamon只是一个调度器(Scheduler)加路由器(router),Swarm自己不运行容器,它只是接受Docker客户端发来的请求,调度适合的节点来运行容器,这就意味着,即使Swarm由于某些原因挂掉了,集群中的节点也会照常运行,放Swarm重新恢复运行之后,他会收集重建集群信息。
二、Docker Swarm基本构图展示
在结构图可以看出 Docker Client使用Swarm对 集群(Cluster)进行调度使用。
上图可以看出,Swarm是典型的master-slave结构,通过发现服务来选举manager。manager是中心管理节点,各个node上运行agent接受manager的统一管理,集群会自动通过Raft协议分布式选举出manager节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了DNS的负载均衡和对外部负载均衡机制的集成支持
三、Swarm几个关键概念
1.Swarm
集群的管理和编排是使用嵌入docker引擎的SwarmKit,可以在docker初始化时启动swarm模式或者加入已存在的swarm
2.Node
一个节点是docker引擎集群的一个实例。您还可以将其视为Docker节点。您可以在单个物理计算机或云服务器上运行一个或多个节点,但生产群集部署通常包括分布在多个物理和云计算机上的Docker节点。
要将应用程序部署到swarm,请将服务定义提交给 管理器节点。管理器节点将称为任务的工作单元分派 给工作节点。
Manager节点还执行维护所需群集状态所需的编排和集群管理功能。Manager节点选择单个领导者来执行编排任务。
工作节点接收并执行从管理器节点分派的任务。默认情况下,管理器节点还将服务作为工作节点运行,但您可以将它们配置为仅运行管理器任务并且是仅管理器节点。代理程序在每个工作程序节点上运行,并报告分配给它的任务。工作节点向管理器节点通知其分配的任务的当前状态,以便管理器可以维持每个工作者的期望状态。
3.Service
一个服务是任务的定义,管理机或工作节点上执行。它是群体系统的中心结构,是用户与群体交互的主要根源。创建服务时,你需要指定要使用的容器镜像。
4.Task
任务是在docekr容器中执行的命令,Manager节点根据指定数量的任务副本分配任务给worker节点
------------------------------------------使用方法-------------------------------------
docker swarm:集群管理,子命令有init,
join
, leave, update。(docker swarm --help查看帮助)
docker service:服务创建,子命令有create, inspect, update, remove, tasks。(docker service--help查看帮助)
docker node:节点管理,子命令有accept, promote, demote, inspect, update, tasks,
ls
,
rm
。(docker node --help查看帮助)
node是加入到swarm集群中的一个docker引擎实体,可以在一台物理机上运行多个node,node分为:
manager nodes,也就是管理节点
worker nodes,也就是工作节点
1)manager node管理节点:执行集群的管理功能,维护集群的状态,选举一个leader节点去执行调度任务。
2)worker node工作节点:接收和执行任务。参与容器集群负载调度,仅用于承载task。
3)service服务:一个服务是工作节点上执行任务的定义。创建一个服务,指定了容器所使用的镜像和容器运行的命令。
service是运行在worker nodes上的task的描述,service的描述包括使用哪个docker 镜像,以及在使用该镜像的容器中执行什么命令。
4)task任务:一个任务包含了一个容器及其运行的命令。task是service的执行实体,task启动docker容器并在容器中执行任务。
四、Swarm的工作模式
1.node
2.Service
3.任务与调度
4.服务副本与全局服务
五、Swarm的调度策略
Swarm在调度(scheduler)节点(leader节点)运行容器的时候,会根据指定的策略来计算最适合运行容器的节点,目前支持的策略有:spread, binpack, random.
1)Random
顾名思义,就是随机选择一个Node来运行容器,一般用作调试用,spread和binpack策略会根据各个节点的可用的CPU, RAM以及正在运
行的容器的数量来计算应该运行容器的节点。
2)Spread
在同等条件下,Spread策略会选择运行容器最少的那台节点来运行新的容器,binpack策略会选择运行容器最集中的那台机器来运行新的节点。
使用Spread策略会使得容器会均衡的分布在集群中的各个节点上运行,一旦一个节点挂掉了只会损失少部分的容器。
3)Binpack
Binpack策略最大化的避免容器碎片化,就是说binpack策略尽可能的把还未使用的节点留给需要更大空间的容器运行,尽可能的把容器运行在
一个节点上面。
六、Swarm cluster 模式特性
1)批量创建服务
建立容器之前先创建一个overlay的网络,用来保证在不同主机上的容器网络互通的网络模式
2)强大的集群的容错性
当容器副本中的其中某一个或某几个节点宕机后,cluster会根据自己的服务注册发现机制,以及之前设定的值--replicas n,
在集群中剩余的空闲节点上,重新拉起容器副本。整个副本迁移的过程无需人工干预,迁移后原本的集群的load balance依旧好使!
不难看出,docker service其实不仅仅是批量启动服务这么简单,而是在集群中定义了一种状态。Cluster会持续检测服务的健康状态
并维护集群的高可用性。
3)服务节点的可扩展性
Swarm Cluster不光只是提供了优秀的高可用性,同时也提供了节点弹性扩展或缩减的功能。当容器组想动态扩展时,只需通过scale
参数即可复制出新的副本出来。
仔细观察的话,可以发现所有扩展出来的容器副本都run在原先的节点下面,如果有需求想在每台节点上都run一个相同的副本,方法
其实很简单,只需要在命令中将
"--replicas n"
更换成
"--mode=global"
即可!
复制服务(--replicas n)
将一系列复制任务分发至各节点当中,具体取决于您所需要的设置状态,例如“--replicas 3”。
全局服务(--mode=global)
适用于集群内全部可用节点上的服务任务,例如“--mode global”。如果大家在 Swarm 集群中设有 7 台 Docker 节点,则全部节点之上都将存在对应容器。
4. 调度机制
所谓的调度其主要功能是cluster的server端去选择在哪个服务器节点上创建并启动一个容器实例的动作。它是由一个装箱算法和过滤器
组合而成。每次通过过滤器(constraint)启动容器的时候,swarm cluster 都会调用调度机制筛选出匹配约束条件的服务器,并在这上面运行容器。
------------------Swarm cluster的创建过程包含以下三个步骤----------------------
1)发现Docker集群中的各个节点,收集节点状态、角色信息,并监视节点状态的变化
2)初始化内部调度(scheduler)模块
3)创建并启动API监听服务模块
一旦创建好这个cluster,就可以用命令docker service批量对集群内的容器进行操作,非常方便!
在启动容器后,docker 会根据当前每个swarm节点的负载判断,在负载最优的节点运行这个task任务,用
"docker service ls"
和
"docker service ps + taskID"
可以看到任务运行在哪个节点上。容器启动后,有时需要等待一段时间才能完成容器创建。
七、案例展示
环境:
准备:
三台装有docker环境的虚拟机,选择的系统是Centos8
最好将三台环境用docker-machine联系起来(不一定需要,但是联系好了,方便操作,所有操作只需要在一个窗口操作即可)
三台主机的地址分别为:
ip hostName
172.16.9.115 manager
172.16.9.118 docker118
172.16.9.169 docker169
①、创建Swarm集群
docker swarm init --advertise-addr 172.16.9.115 (你得告诉它在哪个环境上创建这个集群)
注意: 这个会创建一个集群token,获取全球唯一的 token,作为集群唯一标识。后续将其他节点加入集群都会用到这个token值。
其中,--advertise-addr参数表示其它swarm中的worker节点使用此ip地址与manager联系。命令的输出包含了其它节点如何加入集群的命令。
切换到118环境上的docker环境。这个就是docker-machine的好处了
创建node节点
这样就创建好了,我们来查看一下,可以看大我们当前创建集群的node被选为主导者,
参数:
ID:指的是当前节点唯一唯一值
HOSTNAME:各个节点的名称
STATUS:当前节点状态
AVAILABILITY:当前节点的运行活跃性,这里AVAILABILITY还有Drain,down
active状态下,node可以接受来自manager节点的任务分派;
drain状态下,node节点会结束task,且不再接受来自manager节点的任务分派(也就是下线节点)
down状态下,node节点就意味着自己主动离开了这个集群
②、创建一个nginx容器,然后用集群分布(单服务集群)
1)创建网络,创建网络的目的之前也说了,用来保证容器在不同主机下网络互通的网络模式
docker network create -d overlay my_network
保证当前节点上有nginx镜像,我提前pull一个镜像到我的主机
2)创建容器(服务部署)
docker service create --replicas 1 --network my_network -d --name myNginx -p 8080:80 nginx:1.14-alpine
--replicas 1 只创建一个副本容器
-d 后台运行
--name 给容器起个名字
-p 外部端口:内部端口
最后就是镜像
查看当前运行服务列表:
docker service ls
查看在哪些节点运行了该容器
docker service ps myNginx
3)在swarm中扩展服务
docker service scale myNginx=3
我们登录到docker118上,查看一下容器
同时改命令也可以缩容
docker service scale myNginx=1
4)模拟docker宕机情况,看一下swarm把当前的服务到底该怎么分布
可以看到我们集群118环境上状态已经为dowm
然后我们看一下swarm如何处理服务器单机情况
可以看到我们服务器上已经服务自动被分配到manager服务上,所以现实生活中,如果一台服务器宕机,我们集群服务也不会有问题,用户是无感的
重启我们docker118上的docker服务
然后将之前分配到manager上的task关闭
docker stop myNginx.2.qzfehketo9igugqulqp2xodrm
可以看到服务器恢复了,服务又跑到docker118上
③、多服务swarm集群
1)准备工作:
swarm集群搭建好
准备一个以上的镜像,这里我准备了三个镜像,nginx:1.14-alpine,redis:latest,tomcat:latest
2)创建docker-compose文件
前面也说了docker-compose主要是编排和管理单个节点服务
这里我就将docker-compose.yml与docker swarm整合
docker-compose.yml
version: "3"
services:
nginx:
image: nginx:1.14-alpine
ports:
- "8899:80"
deploy:
mode: replicated
replicas: 3redis:
image: redis:latest
ports:
- "63790:6379"
deploy:
mode: replicated
replicas: 3tomcat:
image: tomcat:latest
ports:
- "8999:8080"
deploy:
mode: replicated
replicas: 3
注意:
docker service部署的是单个服务,我们可以使用docker stack进行多服务编排部署
执行:docker stack deploy -c docker-compose.yml my_service
看看效果:
nginx:
manager:
nginx
tomcat:
redis:进入到其容器进入其库中
进入容器:
docker exec -it 846b773fd1f8 bash
找到redis安装客户端(这里我用的是免密的)
执行./redis-cli
我们登录到118和169节点查看一下容器
可以看到容器完美的启动起来了,内容和manager一模一样
八、docker swarm 容器网络
在Docker版本1.12之后swarm模式原生支持覆盖网络(overlay networks),可以先创建一个覆盖网络,然后启动容器的时候启用这个覆盖网络,
这样只要是这个覆盖网络内的容器,不管在不在同一个宿主机上都能相互通信,即跨主机通信!不同覆盖网络内的容器组之间是相互隔离的(相互
ping
不通)。便于管理
swarm模式的覆盖网络包括以下功能:
1)可以附加多个服务到同一个网络。
2)默认情况下,service discovery为每个swarm服务分配一个虚拟IP地址(vip)和DNS名称,使得在同一个网络中容器之间可以使用服务名称为互相连接。
3)可以配置使用DNS轮循而不使用VIP
4)为了可以使用swarm的覆盖网络,在启用swarm模式之间你需要在swarm节点之间开放以下端口:
--TCP
/UDP
端口7946 – 用于容器网络发现
--UDP端口4789 – 用于容器覆盖网络
例:
在我本地的swarm集群创建我自定义的覆盖网络
命令:
docker network create --driver overlay --opt encrypted --subnet 10.0.6.0/24 server_net
参数:
--opt encrypted 默认情况下swarm中的节点通信是加密的。在不同节点的容器之间,可选的–opt encrypted参数能在它们的vxlan流量启用附加的加密层。
--subnet 命令行参数指定overlay网络使用的子网网段。当不指定一个子网时,swarm管理器自动选择一个子网并分配给网络。
查看一下当前网络
docker network ls
由上可知,Swarm当中拥有2套覆盖网络。其中
"ngx_net"
网络正是我们在部署容器时所创建的成果。而
"ingress"
覆盖网络则为默认提供。
Swarm 管理节点会利用 ingress 负载均衡以将服务公布至集群之上。
在将服务连接到这个创建的网络之前,网络覆盖到manager节点。上面输出的SCOPE为 swarm 表示将服务部署到Swarm时可以使用此网络。
在将服务连接到这个网络后,Swarm只将该网络扩展到特定的worker节点,这个worker节点被swarm调度器分配了运行服务的任务。
在那些没有运行该服务任务的worker节点上,网络并不扩展到该节点。
现在以该网络为例,建一个新的server,用该网络分配
docker service create --replicas 5 --network server_net --name mynginx -p 80:80 nginx:1.14-alpine
下面看一下该网络内部情况
当前节点是manager查看结果:
[root@manager _data]# docker network inspect server_net
[
{
"Name": "server_net",
"Id": "bd2nid7zcmkku8s3d6tnsqpk2",
"Created": "2021-10-20T15:30:24.055441695+08:00",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.6.0/24",
"Gateway": "10.0.6.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"34e684acf212c77b58757313e979c73df109ff4976ed35c73cdc6734b86b9cd7": {
"Name": "server_nginx.3.5vo89iserzv8d2r9gx6viebm1",
"EndpointID": "7faa16cdec61c504cab8db9a32d911919169a8acd8b46029c6e2f6b55d6f1d9c",
"MacAddress": "02:42:0a:00:06:06",
"IPv4Address": "10.0.6.6/24",
"IPv6Address": ""
},
"c78698449a5a409274829784474f2ebc4fae6361e655f42a624e20d1aaf77088": {
"Name": "server_nginx.5.7w8gg9i0gh5yjqrmon84zkj05",
"EndpointID": "dc7014a45dd56b9eb4f02220a45a0b552ec638727a28b2f3195acf58c487ecfb",
"MacAddress": "02:42:0a:00:06:03",
"IPv4Address": "10.0.6.3/24",
"IPv6Address": ""
},
"lb-server_net": {
"Name": "server_net-endpoint",
"EndpointID": "b8f5246e67bf3663c8a62569303aaf28610647b613987f2e644f444712046c1f",
"MacAddress": "02:42:0a:00:06:09",
"IPv4Address": "10.0.6.9/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4104",
"encrypted": ""
},
"Labels": {},
"Peers": [
{
"Name": "09f254b7f1d9",
"IP": "172.16.9.115"
},
{
"Name": "1cc453463e4f",
"IP": "172.16.9.169"
},
{
"Name": "c21e759ab233",
"IP": "172.16.9.118"
}
]
}
]
结果是有两个容器被随机分配了一个子网地址
再切换到docker118节点上查看
[root@manager _data]# docker-machine ssh docker118
Activate the web console with: systemctl enable --now cockpit.socketLast login: Wed Oct 20 15:02:11 2021 from 172.16.9.115
[root@docker118 ~]# docker network inspect server_net
[
{
"Name": "server_net",
"Id": "bd2nid7zcmkku8s3d6tnsqpk2",
"Created": "2021-10-20T15:30:24.056724875+08:00",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.6.0/24",
"Gateway": "10.0.6.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"95bfcdd73501e868feaf366e8b15c12933b3286e56127ff7d7272e24ab3469af": {
"Name": "server_nginx.1.v88vupd2coea51jh0jlru9c2x",
"EndpointID": "3eb04f0cac93e7196d85a955e9ef822efef057b0f528ca1a54fbff8da8968507",
"MacAddress": "02:42:0a:00:06:04",
"IPv4Address": "10.0.6.4/24",
"IPv6Address": ""
},
"lb-server_net": {
"Name": "server_net-endpoint",
"EndpointID": "bbac6cddee071057fb9788165636f86a391aae41127fe56f6c8465f23dc3845e",
"MacAddress": "02:42:0a:00:06:0a",
"IPv4Address": "10.0.6.10/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4104",
"encrypted": ""
},
"Labels": {},
"Peers": [
{
"Name": "c21e759ab233",
"IP": "172.16.9.118"
},
{
"Name": "09f254b7f1d9",
"IP": "172.16.9.115"
},
{
"Name": "1cc453463e4f",
"IP": "172.16.9.169"
}
]
}
]
结论:从上面信息来看,在manager-node模式下 一个名为 server_nginx的服务有两个分别为server_nginx.3.5vo89iserzv8d2r9gx6viebm1和server_nginx.5.7w8gg9i0gh5yjqrmon84zkj05的task连接到名为server_net网络上
切换到node节点docker118上也是发现一个名为 server_nginx的服务有一个server_nginx.1.v88vupd2coea51jh0jlru9c2x 的task任务连接到名为server_net网络上
想要弄清楚这整个网络结构的话,先查看一下该网络对应的虚拟地址
[root@manager _data]# docker service inspect --format='{{json .Endpoint.VirtualIPs}}' server_nginx
[{"NetworkID":"k1aonfxs861nbd65nbzif5l7a","Addr":"10.0.0.38/24"},{"NetworkID":"bd2nid7zcmkku8s3d6tnsqpk2","Addr":"10.0.6.2/24"}]
虚拟地址(v-ip)为10.0.6.2
然后 我们随便进入一个服务的容器中,查看一下该服务的容器的网段
[root@manager _data]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c78698449a5a nginx:1.14-alpine "nginx -g 'daemon of…" 29 minutes ago Up 29 minutes 80/tcp server_nginx.5.7w8gg9i0gh5yjqrmon84zkj05
34e684acf212 nginx:1.14-alpine "nginx -g 'daemon of…" 30 minutes ago Up 29 minutes 80/tcp server_nginx.3.5vo89iserzv8d2r9gx6viebm1
7a1e754dc2a7 portainer/portainer "/portainer" 4 weeks ago Up 2 hours 0.0.0.0:9000->9000/tcp prtainer
[root@manager _data]# docker exec -it c78698449a5a bash
OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown
[root@manager _data]# docker exec -it c78698449a5a sh
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
97: eth0@if98: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether 02:42:0a:00:00:27 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.39/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
99: eth2@if100: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:13:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.4/16 brd 172.19.255.255 scope global eth2
valid_lft forever preferred_lft forever
101: eth1@if102: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1424 qdisc noqueue state UP
link/ether 02:42:0a:00:06:03 brd ff:ff:ff:ff:ff:ff
inet 10.0.6.3/24 brd 10.0.6.255 scope global eth1
valid_lft forever preferred_lft forever
/ # ping 10.0.6.2
PING 10.0.6.2 (10.0.6.2): 56 data bytes
64 bytes from 10.0.6.2: seq=0 ttl=64 time=0.220 ms
64 bytes from 10.0.6.2: seq=1 ttl=64 time=0.075 ms
64 bytes from 10.0.6.2: seq=2 ttl=64 time=0.071 ms
64 bytes from 10.0.6.2: seq=3 ttl=64 time=0.073 ms
很清楚的发现这个之间毫无阻碍的访问
网络结构图
总结:
默认情况下,当创建了一个服务并连接到某个网络后,swarm会为该服务分配一个VIP。此VIP根据服务名映射到DNS。在网络上的容器共享该服务的DNS映射,
所以网络上的任意容器可以通过服务名访问服务。
在同一overlay网络中,不用通过端口映射来使某个服务可以被其它服务访问。Swarm内部的负载均衡器自动将请求发送到服务的VIP上,然后分发到所有的
active的task上。