swarm是docker原生集成的容器编排服务,类似google的kubernetes和apache的mesos。
之前的BLOG里提过docker容器,它大大简化了应用程序的环境配置过程,并实现了一定程度的网络隔离,但它没有解决以下问题:
swarm解决这两个问题的方案是:
基础性的介绍还是推荐阅读官方文档。
docker有两个基础概念,镜像和容器。swarm则在此基础上拓展了一些新的概念。
一个service规定了某个镜像在运行时的执行策略,包括文件映射,端口映射,环境变量,启动的副本数量等,即docker run
的执行参数。CLI为docker service ...
。
一个stack通常由一个或多个service组成。每个stack可以指定一个或多个要加入的网络名,同一个网络下的容器,可以通过service name或者stack+service name互相访问。每一个stack通常对应一个compose-file。CLI为docker stack ...
。
swarm通常是一个或者多个node的集合(这里node可以简单的理解为vm)。node有三种角色:leader, manager, worker.
docker stack deploy
和创建overlay网络等。每个node可以同时具有manager和worker的身份,即它本身也可以运行容器。CLI有两个,创建、加入swarm等使用docker swarm ...
,查看swarm状态则使用docker node ...
。这是一个比较奇怪的设计。
overlay网络是一种跨主机通信的网络,它和swarm紧密结合,提供集群内容器间的互访能力。实际上,只能在swarm集群的manager节点上创建overlay网络,非manager节点或者本身不是某个swarm的成员都是无法创建overlay网络的。CLI为docker network create --driver overlay ...
swarm部署使用的文件叫做compose file. 还是推荐阅读官方文档
swarm使用raft一致性算法进行集群选举. 基本要求是有过半的投票数才能选出leader, 只有manager节点才具有投票权, worker节点是不计算在内的. leader当选的条件是获得超过半数的成员支持, 通常选择奇数个节点作为manager, 是因为这样更节约资源.
更多数量的节点会带来更好的可靠性, 但耗费在节点间通信上的开销也会增加, 因此需要在二者之间取得一个平衡.
处于群龙无首状态的swarm集群会发生什么? 引用官方文档的一句话:
This means that in a cluster of 5 Managers running Raft, if 3 nodes are unavailable, the system cannot process any more requests to schedule additional tasks. The existing tasks keep running but the scheduler cannot rebalance tasks to cope with failures if the manager set is not healthy.
即: 没有leader的集群, 服务暂时是可用的, 但维持容器状态的机制已经消失了, 比如有容器挂掉了也不会有manager来自动添加新容器来维持副本数量达到要求了.
这个内置的负载均衡器是swarm比较神奇的功能之一。swarm的负载均衡分为两个部分,一个是外部访问负载均衡,另一个是swarm内部容器间互访的负载均衡。
容器内部发起的请求会应用此负载均衡策略。内部负载均衡又分两种模式,分别是virtual ip和dns round-robin.
在这种模式下,docker会给每个service分配一个virtual ip,假设我们的service叫做web,容器和服务ip分配如下所示:
Container/Service | Overlay Addr | Ingress Addr |
---|---|---|
容器#1 | 10.0.0.42 | 10.255.0.6 |
容器#2 | 10.0.0.43 | 10.255.0.7 |
web | 10.0.0.5 | 10.255.0.5 |
那么,在任何一个容器里去ping web
,得到的结果都是web的virtual ip 10.0.0.5,后端再按照round-robin模式进行请求分发。
在容器#2内键入curl http://web
,假设此时负载均衡到了容器#1上,那么容器#2内部netstat
显示类似10.0.0.5:44929 -> 10.0.0.5:80
,被访问的容器#1显示10.0.0.43:44929 -> 10.0.0.42:80
。像是转发数据包的同时修改了SIP和DIP,这背后的原因还有待进一步的研究。
DNSRR模式下service不再分配virtual ip,DNS将会返回全部容器的overlay网络地址。
跨vm间用ip地址+端口互访以及公网的访问将会应用外部负载均衡策略。
假设现在集群上有三台机器ABC,A为manager节点,现在执行一个compose file,部署副本数为2的web服务器,该服务会返回当前的主机名,因为运行在在容器里,所以这个主机名即为容器的id。集群有三个节点,副本数为2, 必然有一台机器是没有运行该容器的。假设A和B执行了该容器,那么尝试访问没有运行该容器的节点,即C,会得到类似以下输出:
$ curl http://35.187.206.120
43a12a6f881b is working
$ curl http://35.187.206.120
ba4c33452acc is working
如果继续执行,会发现这两个id继续循环出现。访问A,B的现象也就是类似的。也就是说:
再做一个实验,把运行容器的节点之一B执行关机操作。此时,通过B已经无法访问该服务了,A和C仍然能正常访问,但是其中一个返回的主机名改变了,原因是manager节点为了维持2个副本又重新运行了一个容器。
参考阿里云的这篇文章集群内服务服务间路由和负载均衡,不难得出负载均衡的实现的一些关键信息。
netstat
:/home/app # netstat -apn
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.11:40765 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/node
tcp 0 0 10.255.0.19:80 10.255.0.2:32204 ESTABLISHED 1/node
udp 0 0 127.0.0.11:52236 0.0.0.0:*
输出中有一个ESTABLISHED
状态的连接,就是我们的curl
请求,其源IP已经被修改到了ingress网段内,因此,负载均衡工作在FULLNAT模式。
FULLNAT模式相较于DNAT,更加灵活,负载均衡无需位于网关位置,但它也有NAT的缺点,所有的流量都会经过负载均衡,这会是一个瓶颈。