2.2 使用SwarmKit
2.2.1 SwarmKit综述
SwarmKit是Docker现代集群的基础,它的代码开源在GitHub的独立仓库中,主要提供了基于Docker容器构建集群并进行节点管理和服务管理的能力。
在节点管理方面,SwarmKit能够将普通的虚拟机赋予集群角色,并承担节点增加、退出以及故障失联时自动处理的职责,确保在高可用的SwarmKit集群里任意一个节点故障都不会影响集群的整体功能。这个能力得益于在SwarmKit中使用了Etcd项目的Raft模块。
Raft是一种分布式一致性协议,目的在于解决在不可信任的网络集群中进行状态确认的问题。Raft协议把集群中的节点分为三种角色:Leader、Follower、Candidate。
在任何时刻,集群中只存在一个Leader角色的节点,它保存整个集群的完整状态,其余节点都作为Follower角色,从Leader节点同步状态信息。在集群刚刚创建时,第一个进入集群的节点通常会将自己标记为Leader,此后进入的节点都是Follower。这样的角色划分并没有什么奇特之处,不过Raft协议的高明之处在于,集群中的角色并非是一成不变的。Leader节点依靠定时向所有Follower发送心跳数据包来保持其地位,当Follower节点在一定的时间周期内没有收到来自Leader节点的心跳数据包时(通常是发生了网络分区或是节点故障),就会发起新的一轮“Leader选举”。此时,当前在Raft集群中的所有正常节点都将进入Candidate角色,并在一段随机的延时后向其他节点发出“给我投票”的请求,每个处于Candidate角色的节点都会把票投给第一个向自己发送“投票”请求的节点。若有Candidate节点在此轮投票中获得总节点数量一半以上的投票,则它会将自己标记为新的Leader。否则重新进入下一轮投票,直到有节点获得半数以上的票数。每成功选举一次,新Leader的Term(任届)值都会比之前Leader的增加1。当集群中由于网络或其他原因的故障出现分裂又重新合并时,集群中可能会出现多于一个的Leader节点,此时,Term值更高的一个节点将成为真正的Leader。Raft集群中的角色转换如图2-2所示。
图2-2 Raft三种角色的转换
在服务管理方面,SwarmKit能够将用户创建的服务以Task为单元,依据当前整个集群各节点的负荷情况,自动地分配到适当的节点上运行,并对这些服务的运行状态进行持续跟踪。所有的这些信息都将存储到Raft集群中,确保了服务状态不受节点故障的影响。除了服务的调度,SwarmKit还提供了服务路由、负载均衡、故障处理和在线升级等能力。通常来说,在集群里创建的服务单元,其后端可以由多个运行在各个节点上的同种容器组成分担负载压力的逻辑单元,如图2-3所示。当有外部请求该服务时,SwarmKit负责将这些请求均匀地分担到每个执行业务的容器里。当因部分容器意外崩溃而无法提供正常服务时,SwarmKit会检测到这些问题,并自动地创建新的容器以确保每个服务后端的容器数量与预期保持一致。
图2-3 SwarmKit集群中的服务
SwarmKit依赖Raft协议保持集群状态的高可用,在Raft协议中,为了确保集群的状态和信息一致性,Leader节点每次对存储的数据进行修改时,都需要告知所有Follower节点,并在获得半数以上Follower确认后,才能够将数据的修改持久化。随着节点数量的增加,确认消息的成本将成倍增长。因此,SwarmKit又将所有节点划分成了Manager和Worker两种角色类型,这两类节点都会参与服务的调度,但只有Manager节点真正保存集群信息,并且参与Raft的选举过程。
这里的两种“角色”十分容易被混淆,它们在各自的术语中都被称为“Role”,因此需要通过上下文来识别。
·SwarmKit(以及Swarm Mode)集群的角色:用来区分Manager或Worker,它们的关键差异在于是否存储Raft数据以及是否接收用户的操作请求。
·Raft集群的角色:指的是Leader、Follower或Candidate,它们只存在于SwarmKit的Manager角色节点,各角色的关键差异在于是否主导Raft协议中的数据一致性协商。
如未特别说明,本书以下内容中提到的“角色”一般都是指SwarmKit(以及Swarm Mode)集群中的角色。
SwarmKit节点作为Manager还是Worker,是在节点进入集群时人为指定的,它同样可以在节点创建之后再进行转换,只不过这种转换不会自动发生,需要人工指派。下个小节会介绍这个过程。
2.2.2 创建SwarmKit集群
SwarmKit没有发布编译过的二进制版本,因为通常用户都会用Docker的Swarm Mode来间接地使用它。如果想直接使用SwarmKit工具,最直接的方式就是获取源代码进行编译。首先通过Git下载它的代码仓库,如下所示。
$ git clone https://github.com/docker/swarmkit.git Cloning into 'swarmkit'... ... ...
编译SwarmKit需要一个Golang的SDK和相应的开发工具链,若手头上没有这样的环境,一种比较简便的办法是使用提供了这种环境的Docker镜像,例如官方的golang:1.8镜像。执行以下命令将SwarmKit代码目录挂载到Docker容器里进行构建。
$ docker run --rm -it \ -v 'pwd'/swarmkit:/go/src/github.com/docker/swarmkit \ -w /go/src/github.com/docker/swarmkit/ golang:1.8 \ make binaries bin/swarmd bin/swarmctl bin/swarm-bench bin/protoc-gen-gogoswarm binaries
构建完成的文件存放在SwarmKit代码目录的“bin”文件夹内,将其中的swarmctl和swarmd两个文件拷贝到系统PATH变量指定的目录中,例如“/usr/local/bin”,如下所示。
$ sudo mv swarmkit/bin/swarmctl swarmkit/bin/swarmd /usr/local/bin/
在SwarmKit项目中,swarmd是负责管理和维护集群的后台进程,swarmctl是用户进行集群交互式操作的工具。不带任何参数的swarmd命令将用默认配置创建一个集群,并将当前节点作为集群的第一个Manager节点。默认配置会使用用户当前的目录存放SwarmKit运行过程中产生的各种数据文件,这并不是一种推荐的做法,下面这个命令会在系统后台启动swarmd进程,并指定集群的状态数据存放目录、监听的Socket文件位置、节点名字以及日志文件位置。
$ swarmd --state-dir /tmp/node-mgmt-01--listen-control-api\ /tmp/mgmt-01.sock --hostname mgmt-01 >/tmp/mgmt-01.log 2>&1 &
这样就得到了只有一个Manager节点的最小化SwarmKit集群。从严格意义来说,它还算不上一个真正的集群,但在这个单节点集群中已经可以执行SwarmKit的各种管理操作,并且它也是创建更大规模集群的基础。
此时,使用swarmctl命令可以查看集群的信息。为了让swarmctl能够连接到SwarmKit集群,需要使用--socket参数或SWARM_SOCKET环境变量指定通信的Socket文件位置。swarmctl node ls命令将列出集群节点的信息,如下所示。
$ export SWARM_SOCKET=/tmp/mgmt-01.sock $ swarmctl node ls ID Name Membership Status Availability Manager Status -- ---- ---------- ------ ------------ -------------- ea0b612... ACCEPTED READY ACTIVE REACHABLE *
为了向集群中添加更多的节点,还需要查询出当前Manager节点的IP地址和集群的Join Token,如下所示。集群的Join Token相当于新节点加入集群的密码,只有提供了正确Token的请求才会被接受。此外,Join Token还可被用来区分新节点角色是Manager还是Worker。
$ ip addr show eth0 eth0: <BROADCAST, MULTICAST, UP, LOWER_UP> mtu 9001 ... inet 172.31.27.16/20 brd 172.31.31.255 scope global eth0 ... ... $ swarmctl cluster inspect default ID : fe9vsdtfjco9vxjipwor1nj8x Name : default Orchestration settings: Task history entries: 5 Dispatcher settings: Dispatcher heartbeat period: 5s Certificate Authority settings: Certificate Validity Duration: 2160h0m0s Join Tokens: Worker: SWMTKN-1-...-29kxz34bcz8gvzdwjpimutxvp Manager: SWMTKN-1-...-34npzkvzoxss2s6tx7q1ss11s
注意swarmctl cluster inspect default这个命令的输出,它显示了名为“default(SwarmKit的默认集群名字)”集群的两个“Join Token”。这两个Token分别对应了两种节点角色。
接下来向集群添加新的节点。将先前编译出来的swarmctl和swarmd文件拷贝到其他需要加入SwarmKit集群的节点上,同样放到PATH环境变量的目录里。这次在启动swarmd进程时,除了指定状态数据目录等信息,还需要提供--join-addr和--join-token参数表示加入已有集群(而不是新建集群),这里使用集群的Worker Join Token将该节点作为Worker角色。
$ MANAGER_IP=<Manager节点IP> $ JOIN_TOKENS=<Worker Join Token> $ swarmd --state-dir /tmp/node-work-01--hostname work-01\ --join-addr ${MANAGER_IP}:4242 \ --join-token ${JOIN_TOKENS} >/tmp/work-01.log 2>&1 &
重复该过程,添加更多节点到集群中,然后回到任意一个Manager节点上使用swarmctl node ls命令查看集群的节点信息,如下所示。
$ swarmctl node ls ID Name Membership Status Availability Manager Status -- ---- ---------- ------ ------------ -------------- ea0b612... ACCEPTED READY ACTIVE REACHABLE * lnxa1hn... ACCEPTED READY ACTIVE bhk2f1r... ACCEPTED READY ACTIVE ... ...
需要注意的是,所有swarmctl命令必须连接Manager节点的Socket文件才能使用,通常来说这也意味着只能在Manager节点上使用swarmctl命令。这是因为只有Manager节点才真正存储了集群的状态信息,而Worker节点仅仅用于执行服务容器。
使用swarmctl node promote和swarmctl node demote命令可以对节点的角色进行转换,它们的参数都是节点的ID,前者将一个Worker节点提升为Manager节点,后者反之。
此外,SwarmKit还为节点提供了“停机维护”的功能,命令是swarmctl node drain,如下所示。
$ swarmctl node drain <节点ID>
被指定的节点可用状态会被标记为“DRAIN”,此时如果该节点上运行有SwarmKit托管的容器服务,将被自动迁移到其他节点上运行,如下所示。
$ swarmctl node ls ID Name Membership Status Availability Manager Status -- ---- ---------- ------ ------------ -------------- ea0b612... ACCEPTED READY ACTIVE REACHABLE * lnxa1hn... ACCEPTED READY ACTIVE bhk2f1r... ACCEPTED READY DRAIN
使用swarmctl node activate可将节点恢复为正常运行状态,如下所示。
$ swarmctl node activate <Node-ID>
2.2.3 在SwarmKit集群上运行服务
作为Docker的集群组件,SwarmKit最重要的能力之一便是对服务的调度和管理。这些功能大多可以通过swarmctl service这个命令来操作。
创建服务的操作是swarmctl service create,这个命令会通过SwarmKit的后台进程API在集群中创建使用指定镜像部署的服务,并根据当前集群各节点资源状态和服务的其他约束条件,将服务运行的Task调度到最合适的地方执行,如下所示。
$ swarmctl service create --name nginx --image nginx:1.11.1-alpine t2neubvbyyqc8rynt09drjhl9
在SwarmKit集群中,默认情况下所有节点都会参与服务的调度,包括Manager和Worker节点,这一点与过去的Swarm以及后面几章介绍的Kubernetes、Mesos等都不同。
使用swarmctl service ls命令可以查看当前集群运行的所有服务,如下所示。
$ swarmctl service ls ID Name Image Replicas -- ---- ----- -------- t2neubvbyyqc8rynt09drjhl9 nginx nginx:1.11.1-alpine 1/1
查看单个服务的详细信息可以使用swarmctl service inspect,如下所示。
$ swarmctl service inspect nginx ID : t2neubvbyyqc8rynt09drjhl9 Name : nginx Replicas : 1/1 Template Container Image : nginx:1.11.1-alpine Task ID Service Slot Image Desired State... Node ------- ------- ---- ----- ------------- ... ---- rrk1vhg... nginx 1 nginx:... RUNNING ... mgmt-01
服务部署以后,还可以通过swarmctl service update命令来更新它的一些属性,比如容器副本的个数,如下所示。
$ swarmctl service update nginx --replicas 6 t2neubvbyyqc8rynt09drjhl9
查看更新后的服务状况,可以发现名称是nginx的这个服务副本数变成了“6/6”,表示目标副本数量是6,当前实际运行的副本数也是6,如下所示。
$ swarmctl service ls ID Name Image Replicas -- ---- ----- -------- t2neubvbyyqc8rynt09drjhl9 nginx nginx:1.11.1-alpine 6/6
使用swarmctl service inspect nginx命令将列出其中每个容器副本所对应的Task信息,如下所示。
$ swarmctl service inspect nginx ... ... Task ID Service Slot Image Desired State ... Node ------- ------- ---- ----- ------------- ---- rrk1vhg... nginx 1 nginx:... RUNNING mgmt-01 fmfgcqm... nginx 2 nginx:... RUNNING mgmt-01 5f2vi8d... nginx 3 nginx:... RUNNING work-01 w0s07ie... nginx 4 nginx:... RUNNING work-01 qndf2cv... nginx 5 nginx:... RUNNING work-02 xt2pm9j... nginx 6 nginx:... RUNNING work-02
如果在其中一个节点上执行docker container ps命令,如下所示,会看到在该节点上运行的那些容器。
$ docker container ps CONTAINER ID IMAGE COMMAND ... ... NAMES 1863bb173d97 nginx:... "..." ... ... nginx.1.rrk1vhg... 2ab1d45d0a3b nginx:... "..." ... ... nginx.2.fmfgcqm...
使用swarmctl service update -help可以看到在SwarmKit中允许动态更新的内容,其中--image这个参数经常被使用,它可以用来替换容器的镜像,这实际上可以用于升级服务的版本。例如下面这个命令可以将nginx服务的版本升级到1.11.3-alpine。
$ swarmctl service update nginx --image nginx:1.11.3-alpine t2neubvbyyqc8rynt09drjhl9
执行完下面这个命令,服务的容器会被重启并替换成指定的新镜像。
$ swarmctl service ls ID Name Image Replicas -- ---- ----- -------- t2neubvbyyqc8rynt09drjhl9 nginx nginx:1.11.1-alpine 6/6
还可以加上更多的参数来控制服务升级的过程,例如下面这个命令会每隔四秒钟升级一部分服务,每次并行升级两个容器,直到所有容器都升级到新的镜像版本。
$ swarmctl service update nginx --image nginx:1.11.3-alpine \ --update-parallelism 2--update-delay 4s
2.2.4 SwarmKit集群的其他功能
除了已经介绍的节点和服务管理功能,SwarmKit还提供了Task、Secret以及集群网络的管理。
$ swarmctl --help ... ... Available Commands: node Node management service Service management task Task management version Print version number of swarm network Network management cluster Cluster management secret Secrets management
Task是SwarmKit的最小管理单元,在当前版本里,一个Task实际上就对应一个容器。SwarmKit抽象出Task的概念主要是为了未来能够让这个模型用在除容器外的集群资源管理,比如直接接管虚拟机,甚至是Unikernel的实例。Secret是SwarmKit中对用户密钥信息的封装,这部分功能在讲解Swarm Mode的相应部分时再做介绍。
最后简单说一下SwarmKit中的network命令,它和docker network比较类似,不过在SwarmKit中只能看到后者的一部分网络。实际上,在这个命令中所能创建和管理的正是Docker网络中scope值为swarm的那些网络,它们通常都是建立在SDN之上的跨节点的虚拟网络,例如使用swarmctl network ls命令只显示驱动类型是overlay的那个网络,而不会看到本地节点上驱动类型为bridge的其他网络,如下所示。
$ swarmctl network ls ID Name Driver -- ---- ------ xegwekbeisau53lmr6lyz2gak ingress overlay