Docker Swarm容器集群管理工具

Docker Swarm 是在 Docker 1.12版本之后添加的容器集群管理工具,可以统一管理分布在不同主机的多个容器。相比起Kubenetes,Docker Swarm无需额外安装,只要有较新版本的Docker就可以使用。如果你尝试过搭建Kubenetes,就知道这差别有多大。之前趁着参加Dell EMC的比赛学习实践了一把Docker Swarm,感觉真不愧是Docker的自带工具,一如既往的简单好用。在这里总结下它用到的一些技术原理和使用方法。

设计架构


因为是集成在Docker之中,由Docker Client向Swarm发送指令。每个Docker engine实例称为一个Node,既可以是物理机,也可以是虚拟机。分为manager和worker。worker只负责接收并执行任务,manager在此基础上还要接收用户指令,服务调度,服务发现...


另一个重要的概念是service,通常服务是一个大的应用分割出来的小的功能单元(microservice)。比如一个web应用中,nginx是一个service,mysql是一个service,php是一个service。
而task,就是指容器中执行的具体命令。

常用命令

在Docker Swarm中经常使用的主要有docker swarmdocker nodedocker servicedocker stack

  • docker swarm

管理swarm,添加节点

Commands:
  init        Initialize a swarm   
  join        Join a swarm as a node and/or manager
  join-token  Manage join tokens
  leave       Leave the swarm
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm

初始化swarm:

docker swarm init --advertise-addr 123.45.67.89(本机IP)

加入swarm:

docker swarm join \
    --token SWMTKN-1-0vq435ac9oavqsnxkom9v49fpqbxdawfdl51xuvwelqsb6myj0-8tutrd6ucdf05nqk7xjz81cy7 \
    123.45.67.89:2377
  • docker node

用于管理节点

Commands:
  demote      Demote one or more nodes from manager in the swarm
  inspect     Display detailed information on one or more nodes
  ls          List nodes in the swarm
  promote     Promote one or more nodes to manager in the swarm
  ps          List tasks running on one or more nodes, defaults to current node
  rm          Remove one or more nodes from the swarm
  update      Update a node
  • docker service

管理service

Commands:
  create      Create a new service
  inspect     Display detailed information on one or more services
  logs        Fetch the logs of a service or task
  ls          List services
  ps          List the tasks of one or more services
  rm          Remove one or more services
  scale       Scale one or multiple replicated services
  update      Update a service
  • docker stack

与docker-compose结合的命令,可以方便地在集群中部署应用。

Commands:
  deploy      Deploy a new stack or update an existing stack
  ls          List stacks
  ps          List the tasks in the stack
  rm          Remove one or more stacks
  services    List the services in the stack

Docker Swarm的命令基本都很简单,几乎看一下--help就明白怎么用了,都不需要再去翻文档。
由于是第一次接触到分布式系统,所以又花了些时间了解了一下它背后的技术原理。想一想要实现一个这样的分布式系统主要需要解决以下几个问题:

  1. 容器随时可能开启或停止,如何管理容器,怎么检测容器的运行状态。怎么保证容器随时可能变化的情况下系统提供的服务仍可用。
  2. 集群内的容器容器间需要跨主机通信,在硬编码IP地址不适用的情况下,如何容器透明地跨主机通信。
  3. 如何让多个容器的服务提供一个统一的外界入口。怎么在集群内做负载均衡。

服务发现

Docker Swarm内置了Raft一致性算法,可以保证分布式系统的数据保持一致性同步。Etcd, Consul等高可用键值存储系统也是采用了这种算法。这个算法的作用简单点说就是随时保证集群中有一个Leader,由Leader接收数据更新,再同步到其他各个Follower节点。在Swarm中的作用表现为当一个Leader 节点 down掉时,系统会立即选取出另一个Leader节点,由于这个节点同步了之前节点的所有数据,所以可以无缝地管理集群。
Raft的详细解释可以参考《The Secret Lives of Data--Raft: Understandable Distributed Consensus》

跨主机容器通信

Docker Swarm 内置的跨主机容器通信方案是overlay网络,这是一个基于vxlan协议的网络实现。其作用是虚拟出一个子网,让处于不同主机的容器能透明地使用这个子网。所以跨主机的容器通信就变成了在同一个子网下的容器通信,看上去就像是同一主机下的bridge网络通信。


根据vxlan的作用知道,它是要在三层网络中虚拟出二层网络,即跨网段建立虚拟子网。简单的理解就是把发送到虚拟子网地址10.0.0.3的报文封装为发送到真实IP192.168.1.3的报文。这必然会有更大的数据开销,但却简化了集群的网络连接,让分布在不同主机的容器好像都在同一个主机上一样。关于vxlan的具体实现原理可以参考《vxlan 协议原理简介》
我们在Docker Swarm中,看一看overlay网络的作用。在一个拥有两个Node的Swarm Cluster中创建一个overlay网络:

root@vultr:~# docker network create --driver overlay --subnet 10.10.10.0/24 my-overlay-network

然后部署一个nginx 服务:

root@vultr:~# docker service create --name webapp --replicas=2 --network my-overlay-network -p 8080:80 nginx

这里我们部署了一个副本数为2的nginx服务,使用了刚刚创建的my-overlay-network网络

root@vultr:~# docker service ps webapp 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           
t2z1cbbq1tnm        webapp.1            nginx:latest        vultr.guest2        Running             Running 3 minutes ago                       
v116fzm54tz7        webapp.2            nginx:latest        vultr.guest1        Running             Running 3 minutes ago 
root@vultr:~# docker ps
CONTAINER ID      IMAGE            COMMAND                  CREATED           STATUS            PORTS       NAMES
df75f490d566      nginx:latest     "nginx -g 'daemon of…"   12 minutes ago    Up 11 minutes     80/tcp      webapp.2.v116fzm54tz714k7d1jrszxyt

先看看manager节点的nginx容器网络和该节点的docker网络:

可以发现这个容器有三个网卡:eth0、eth1、eth2,分别属于docker的ingress网络、my-overlay-network网络、docker_gwbridge网络。其中my-overlay-network就是我们刚刚创建的overlay网络,这个网络分配到了一个子网地址10.10.10.10。而ingress网络会用于容器间的负载均衡,docker_gwbridge网络用于容器连接外网。
再登录到另一台worker节点看看另一个nginx容器副本的网络:

这个容器eth2也连接到了my-overlay-network网络,并且分配到了一个子网地址10.10.10.9。这样两个容器就可以通过子网地址通信了。

(nginx镜像本身没有ifconfig、ping命令,需要自行安装)
同在一个子网还不够,Swarm还内置了DNS服务。将service name作为service的DNS,就可以访问到这个service了。
随意另建一个服务,也加入刚才创建的overlay网络:

root@vultr:~# docker service create --name test --replicas=1 --network my-overlay-network -p 9090:80 nginx
root@vultr:~# docker exec -it b6 ifconfig |grep -A 1 eth2
eth2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.10.10.12  netmask 255.255.255.0  broadcast 10.10.10.255

用service name webapp 访问服务:

发现能够ping通,但是解析的IP地址和之前看到的两个容器IP并不一样,这里是Swarm做了负载均衡,稍后再具体讨论下。
这里还有一个坑需要注意,如果一个节点处于NAT之后的网络,其他节点处于公网,那么这个节点是不能和其他节点建立Swarm的overlay网络的。需要用weave等方案。我猜是不是和Swarm的overlay网络是基于vxlan隧道实现而weave是基于包封装有关。

负载均衡

负载均衡分为两种:Swarm集群内的service之间的相互访问需要做负载均衡,称为内部负载均衡(Internal LB);从Swarm集群外部访问服务的公开端口,也需要做负载均衡,称外部部负载均衡(Exteral LB or Ingress LB)。

Internal LB

内部负载均衡就是我们在上一段提到的负载均衡,集群内部通过DNS访问service时,Swarm默认通过VIP(virtual IP)、iptables、IPVS转发到某个容器。

Exteral LB(Ingress LB)

看名字就知道,这个负载均衡方式和前面提到的Ingress网络有关。Swarm网络要提供对外访问的服务就需要打开公开端口,并映射到宿主机。Ingress LB就是外部通过公开端口访问集群时做的负载均衡。

上图是一个三个节点的Swarm集群,app服务公开了8000端口,并且有两个副本。虽然服务只有两个副本,分别在A、B两台主机上,但是访问C主机的8000端口,ingress网络会通过IPVS将请求转到A、B之一的容器中处理,依然可以对外提供服务。
(这种负载均衡方式需要Node的docker engine版本尽量一致,我用17.05.0-ce和17.12.1-ce组成的Cluster时,提示load balancing出错。用两个17.12.1-ce版本Node就没有这个问题。)
看一看宿主机的iptables:

90908080分别是上面两个service暴露的端口,在宿主机上都转给了172.18.0.2的相应端口。而这个172.18.0.2又是谁的IP呢?

172.18.0.2正是ingress-sbox的IP地址。ingress-sbox再通过iptables的mangle规则和IPVS将请求转发到合适的容器。具体流程如下图:

要完全弄明白Swarm的负载均衡还需要对iptables和IPVS很熟悉,所以还得多修炼一下内功啊。

docker-compose in Swarm

Docker三剑客之二的docker-compose和Swarm的结合,会让集群管理更加的简单。甚至一键就可以在集群中部署应用。前面提到的docker stack命令,就是主要用在这里的。docker-compose用到Swarm里,需要用到它的第三版,因为第三版才加入了Swarm相关的命令:

Version 3:
Designed to be cross-compatible between Compose and the Docker Engine’s swarm mode

docker-compose 在上一篇blog中已经提到过了。这里就记一下不同的地方吧。

  1. deploy

    在第三版中,加入了针对Swarm的deploy命令,它主要的作用是:选定部署节点(placement)、副本数量(REPLICAS)、资源限制(RESOURCES)、部署模式(mode)...

  2. networks

    networks会出现在量个地方,一个是顶格命令,用于定义整个服务用到的网络信息,包括网络名和网络类型。另一个是在services命令之下,用于定义,某一个service用到的网络和它在网络中的一些信息。

  3. build

    Swarm模式下,之前的build命令就不可用了。也就说Swarm模式下,不能通过Dockerfile制作并启动镜像。镜像必须之前就存在于本地或者存在于hub中通过网络拉取。

demo

最后还是用一个demo来当例子吧,就用上一篇的demo稍微改了一下,地址在这里。还是一个Web应用,包括Nginx、Flask后端、MongoDB数据库。事先得准备一个包含两个节点的Swarm Cluster,注意这两个节点得能够搭起overlay网络。
在docker-compose.yml中可以看到:

networks:
  swarm_demo:
    driver: overlay

这里是顶格的networks命令,先定义了一个名为swarm_demo的overlay网络。

services:
  web:
    networks:
      swarm_demo:
        aliases:
        - demo_web
    deploy:
          mode: replicated
          replicas: 1
          placement:
            constraints:
              - node.role == manager

这里我们把web服务放到了swarm_demo网络中,定义了别名demo_web。在这个overlay网络中的其他服务就可以用demo_web这个网络名访问到web服务。比如/nginx/default.conf中直接以demo_web作为反代的地址:

location /api {
        proxy_pass http://demo_web:5678;
        ...
    }

而deploy命令定义了副本数量和service运行的位置。
我们来测试一下,在manager节点上执行:

git clone https://github.com/liverpoolpjy/docker_swarm_demo.git
cd docker_swarm_demo
docker build -t demo_web:latest web/.   # 先制作好镜像
docker build -t demo_nginx:latest nginx/.
docker stack deploy --compose-file=docker-compose.yml app  # 部署

因为这个demo中的Nginx镜像和python镜像都是自己用Dockerfile制作的,所以我们先在manager节点上制作好镜像,并限制web和nginx服务只运行在manager节点。

root@vultr:/tmp/docker_swarm_demo# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
z8fae79ld53k        app_db              replicated          1/1                 mongo:3.4           
rzlz56bg1aa0        app_nginx           replicated          1/1                 demo_nginx:latest   *:80->80/tcp
wvnlymts6hh0        app_web             replicated          1/1                 demo_web:latest     
root@vultr:/tmp/docker_swarm_demo# docker service ps app_db app_nginx app_web
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE          
y2u9gk5yvdm4        app_db.1            mongo:3.4           vultr.guest2        Running             Running 22 minutes ago                       
h05gavtnladf        app_nginx.1         demo_nginx:latest   vultr.guest1        Running             Running 22 minutes ago                       
fdls6o6lurnh        app_web.1           demo_web:latest     vultr.guest1        Running             Running 22 minutes ago 

和预期一样,db服务运行在worker节点,web和nginx服务运行在manager节点。现在访问任意一个节点的80端口,都可以访问到demo页面了。

summary

总的来说,Docker Swarm的使用是比较简单的,文档也挺详细,功能也算是覆盖了容器集群管理的方方面面。但是又因为Swarm为我们做了太多了,把很多的底层工作为我们屏蔽了,想要学一下它的各种原理反而显得要困难一些。所以我感觉它算是学习容器集群不错的开端,相比之下Kubenetes这种安装配置超级麻烦的giant可能更能学习到集群管理的细节吧。

reference

Getting started with swarm mode
Docker Networking Internals: Container Connectivity in Docker Swarm and Overlay Networks
Compose file versions and upgrading
docker swarm mode的服务发现和LB详解

Comments
Write a Comment
  • tuhao reply

    kubernets现在已经是事实上的“容器编排之王”了,swarm和mesos我觉得其实多研究意义不是很大。

    • Melw00d reply

      @tuhao 嗯,但是二者的功能和用到的技术有很多想通的地方。用swarm入门,kubernetes深入也是不错的选择。

  • fanjieqi reply

    问个问题啊,在Swarm集群中,一个mongo service在各个节点当中作为容器启动起来后,另外一个service A也各个节点上作为容器启动,然后A在各个节点上操作mongo service,那么这个mongo service在各个节点上的容器具有数据一致性吗?

    • Melw00d reply

      @fanjieqi 没有实践过,但想了下mongo service应该是没有数据一致性的。swarm用的raft一致性算法作用在保持节点状态的一致性。而数据库的数据一致性需要另外的机制来保证,比如MongoDB Replica Set

  • 你好,遇到一个问题,在通过docker service create ... --publish 8080:80 nginx创建时,通过浏览器访问不到创建的nginx

    root@manager:~# iptables -t nat -n -L | grep -A 4 'Chain DOCKER-INGRESS'

    Chain DOCKER-INGRESS (2 references)

    target prot opt source destination

    DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.18.0.2:8080

    RETURN all -- 0.0.0.0/0 0.0.0.0/0