SwarmKit知多少——来自源码世界的深入解读

今天是数人云容器三国演义Meetup嘉宾演讲实录第二弹。数人云工程师春明为大家奉送了一盘干货的大餐,让我们读读源码,深入了解一下SwarmKit的世界吧!

小数前方预警:有大量代码出现!

今天与大家分享一下数人云对于SwarmKit的尝试和探索。Swarm早在2014年就出来了,和Docker Compose几乎是同一时期。Docker解决的是单机上容器的问题,但如何在一个集群一组的硬件资源上去调度容器?Swarm可以解决。SwarmKit是在Swarm的基础上研发出来的,只不过Docker公司对SwarmKit联系得更紧密。SwarmKit的主要代码提交在2016年4、5月份, Docker1.12出来以后正式把它release出来。

我个人比较看好SwarmKit的原因在于它很简单。在生产环境部署Mesos或者Kubernetes,需要安装的组件非常多。Mesos为例,首先要装Zookeeper,然后装master、 slave,它们之间配置、连线都很复杂,更不用说每条连线后面大量的工作,最终cluster才能跑起来,并且有很复杂的API。相比而言,SwarmKit非常简单,一个Binary解决所有问题。

今天分享的第一部分会和大家说一下什么是SwarmKit,第二部分聊聊ServiceScheduler,从一个程序员的角度思考如何构造一个调度器。这个调度器, Service Scheduler,类似于SwarmKit、Kubernetes、Mesos加Marathon。第三部分通过几段代码片段了解SwarmKit的关键点。

SwarmKit的概念

SwarmKit、Swarm、Swarm Mode这三个词,对刚开始接触的人来说可能有很多困惑。SwarmKit是Swarm这个项目的升级版。Swarm和SwarmKit最主要的区别在于Swarm是单独运行的,它需要一个第三方的分布式存储,它支持三种存储方式,即主流的三种分布式存储——Zookeeper、ETCD和Counsul。

SwarmKit在Swarm的基础上精进了一步,不再需要有第三方存储,也不需要做Leader选举。它的发布方式,一种是独立的,另一种是直接和DockerEnginet混搭放在一起。所以大家安装新Docker1.12版本之后,实际上也拥有了SwarmKit。你有多台机器安装了Docker1.12版本,就已经拥有了一个Swarm的cluster,在上面就可以把任务负载到不同的机器上,不需要再去安装一堆组件。另外一个词叫Swarm Mode,如果你开启了Swarm模式的Docker Engine,用Docker的集群功能的时候 ,它实际上就是进入了Swarm Mode。

构造服务调度

接下来聊一聊从一个程序员写代码的角度理解如何去构造一个Service Scheduler,服务调度。程序员其实不太关心底层的硬件资源或者Saas层是怎么来的,更多是考虑如何实现一个任务或者一组任务去分发、放在不同的一组机器上。如果想做好这个事情,无论是公有云、私有云或者虚拟机,首先要做的应该是把所有的资源进行抽象。如果是Mesos Framework,第一件事情是去Mesos申请一块资源,不用关心资源到底来自于哪里,你申请一个offer、要两块CPU或者200M的内存, Mesos如果满足你就会反馈OK,如果满足不了你就告诉你等一下。首先把一组资源抽象,比如池子有多少个CPU、有多少内存,把它抽象。第二步分配,如果有一个请求过来,就从池子里面分配资源,然后release。

服务可能分很多个进程,最终负载在不同的机器上。第二部分,是对服务这个概念上有一个抽象,服务应该有它的生命周期、健康检测。服务下面应该有不同的进程,这在不同的Service Scheduler有不同的叫法,比如Marathon把它叫做instance, Mesos中叫task,SwarmKit也叫task,实际上它是一个运行中的实例,包含了刚才从资源池里申请的一块资源,并且有自己的生命周期。其中最重要的应该是健康检查,不同人对一个服务的健康状态有着不同的定义。

以前我们用Docker Daemon,那现在如何判断一个服务是不是健康的?在DockerEgine加入了健康检测之前,我们主要看它的容器是否起来。一个容器起来,能够对外服务,这时就看下一步的负载均衡、服务发现以及编排。服务之间其实有一个依赖,服务A在依赖服务B的情况下,只有服务B起来,服务A才能起。所以这一步很重要,对应用具体的实例抽象,这里面其实是一个状态机,专门做了状态的切换。

第三部分,在做一个服务编排的时候,应该有一定的策略、算法去做服务的分发以及服务的编排。某些服务可能对特定资源有一些特别的需要,比如对网络的需要比较强,对存储、对运算能力可能有一些特别的需求;两个服务之间有一定的亲缘性,比如希望web服务跑在离开我更近的缓存上面;服务有几种分类,举例来说,Web的应用和数据库类型的应用其实有一些区别,数据库类型的应用对弹性的需求没有那么高,而Web服务对弹性的需求比较高。所以第三件事情应该是做好这一层面策略以及分发。

第四部分,把一堆服务都分到下面不同的机器上,有不同的分发策略以及不同的网络模型后,如何让服务真正的对外服务?即如何解决服务发现、负载均衡还有Proxy这层的问题。市面上服务发现的方案非常多。比如SwarmKit通过DNS实现,IPVS也是它的一种。新浪微博提出的NginxModule以及更早期的一个开源项目叫Bamboo,一个刷HA的工具,如果容器的状态有变化,它会通过Bamboo去刷HA的配置,最终把HA重启。还有Registratorr、confd、 Counsul Template等一些项目,其实都是着力解决服务发现、LoadBalance以及Proxy。

对于服务发现,DNS、SRV、 IPVS都是非常好的解决方案。它们有不同的应用场景,比如IPVS倾向于四层的负载均衡。DNS不单是负载均衡,它同时解决了服务发现和负载均衡两个点。

我们的场景非常需要Proxy层,对它有很多期望:比如流量分发、限制、统计以及灰度发布等。最近我做的一件事情是在所有的应用前面加一层Proxy,大家可以理解为一层Nginx或者是一层HA,但实现HA这种性能其实是很难做到的。

如何做好一个Service Scheduler,除了上述几点,接下来几个方面也很重要。第一,HA的需求,即客户对ServiceScheduler的高可用性的要求,数人云有很多金融方面的客户,他们对HA要求更高,比如提到的“两地三中心”,归根结底是HA的需求。

第二个,安全方面, SwarmKit支持分布在不同的地方,那么解决安全的问题就非常重要。Docker的安全问题很严重,因为实际上Docker给外部的人有权限去执行任何程序。

解决HA问题无非是要布多个,布单个可能有单点的问题。SwarmKit从中借鉴了很多,它把Mesos的几个部分合在一起,这就引出一个问题,比如它要记录状态,那么如何在一个分布式的环境下去记录这个状态,分布式的存储。

这是开启一个SwarmKit的管理节点的一行命令,相当于安装一个Mesosmaster和一个Zookeeper。第二个命令是把当前Docker agent加入到一个Swarm集群里面,相当于装了一个slave的节点。刚才这两条命令其实就构建了一个两节点的Swarm集群。 这张图描述了Swarm的工作模式。有三层,这是一个二进制,它们充当不同的角色。这些线彼此连接,可以看到Manager和Manager之间是可以交互的,Manager和Worker之间也可以交互。Manager和Manager节点之间交互是raft协议在做Leader的选举,和Worker之间的这条线表示把一个任务分发到不同的Worker上。在SwarmKit里面,Worker换了一个名字叫叫agent。Worker听起来像纯粹干活的东西,agent则还能做一些其它事情,比如做健康检测、做主机、主机资源的收集。

在图上大家会看到每一个Worker和三个Manager同时通信的,但事实上不是这样, SwarmKit在同一时间只和raft选举出的一个leader去交互。

SwarmKit的关键组成

接下来展示SwarmKit的代码结构,来了解它们各自的工作。第一个是agent,即刚才说的Worker,它做的事情是SwarmKit节点作为agent的时候要做的事情,代码写在agent这个地方。第二个是API,API不是通过HTTP REST Service或者通过命令行跟它交互,API实际上是Manager和Worker之间交互的那些命令,它用gRPC协议,通过protobuf协议来交互。第三个目录叫CA,CA解决安全问题。SwarmKit号称安全做得很好,它的公钥和私钥可以ratate,即它的公钥和私钥有一个过期时间,然后再不同的循环,所以私钥被compromise的时候不会影响整个系统的安全性,因为会rotate它。CLI和CMD是操作一个SwarmKit时的入口。design是设计文档。integration是集成。

下面是比较重要的两个文件夹,第一个是Manager,和上面的agent对应,一个Swarm node在充当一个Manager的时候,它的逻辑就在这里,即它分发、健康检查及其他代码都在Manager上面。另一个是node的节点,Docker Swarm init的时候就是创建一个node逻辑的概念,其主要的代码在node的下面。

这张截图是打开agent的文件夹,介绍一下每个文件分别做什么。第一个是文件夹,这里的核心逻辑,exec文件夹下核心文件是一个Docker client。大家如果用GoDocker client会发现里面就是这些——如何维护、连一个Docker的agent去update、create、destroy Docker的代码。但它使用的是docker engine-api的库,而不是Godocker client,因为engine-api那个项目是Docker公司的,agent的核心代码都在里面。

接下来比较重要的就是Task、Worker和Session这三个文件,Task是任务的一个抽象。agent下面的数据结构里面会包含一个Worker,它是task真正干活的东西,之后我们会详细的说一说Worker。刚才图中看到Worker和Manager之间那条线用的就是Session的抽象。

另一个比较重要的文件夹是Manager。它的文件夹很多,第一个allocator主要是说资源,要申请哪些资源。它里面对网络有一些抽象,从申请上看对CPU和Manager没有提到,它只是对申请allocator有一个网段。constraint是有哪些限制,大家如果用过Mesos都会知道对任务的开发需要一些label满足SSD、memory等,就是由constraint来做。controlapi是alloctator和外面交互的一个API层。下面的dispatcher和orchestrator和scheduler这三个词很难说它们本质有什么区别,只是多少会有一点。orchestrator更倾向于Swarm的任务,它分两大类, replicate和global的任务,global的任务在每个node上只部署一个节点。replicate是传一个数量,然后部署这个数量。

Node

看了整个代码,我总结出了几点核心概念。第一个是Node的节点,更确切的说是对Dockeragent的一个抽象。然后Manager节点。Manager和Node agent是一个Node,它既可以作为Manager又可以作为agent,或者同时兼有两个。第四个是Task和Service,Service是我们更高一层对应用的抽象;Task是一个进程,更确切地说应该是一个容器。SwarmKit的Task和Service都有自己生命周期的定义。

读SwarmKit的代码比较好的一点是它入口非常简单,每一个核心的概念里面,一个new、一个run。new是初始化的数据结构,run是真正的干活。大家如果想快速的了解代码,去每一个概念里面了解这两个函数基本上就知道它们做了什么。

这是Node节点的new。Node的节点最核心的是初始化了一些channel,在上面创建了文件夹,这基本是Node节点的new,但是它的run做了很多事情。run的函数很长,里面主要做了一些文件夹初始化,以及SwarmKit用了一个在golang社区比较流行的DB叫bolt DB,这里主要初始化文件夹和bolt DB的初始化。run另外一个比较重要的是 Node的节点,Node的节点可以创造Manager的role。Node既可以充当Manager的role,又可以变为Worker的role,这两个角色可以在运行时动态变化。它们在每次变化的时候,比如变成Manager,那作为Manager身份的一些功能就开启,由Manager变成agent这些功能可能就被disable掉。

Manager

第二个关于Node的概念是Manager,这是Manager的数据结构。比较有意思的是中间这一部分它作为一个CAserver,作为一个dispatcher,作为一个replicatedOrchestrator,或者是作为一个global Orchestrator。这些是作为Manager功能的数据结构。 此图是Manager的new,这一屏核心是监听了一个端口,它和Docker Engine非常像,监听一个TCP的端口,或者监听一个unixsock的端口,都是可以的。只监听一个TCP其已经满足大部分场景,那么Docker、agent为什么监听一个unixsock的端口?大家关注过Docker Engine就会知道,有一个Docker in Docker非常适合Docker测试。如何做到Docker in Docker呢?就是把unixsock传到Docker里,相当于在一个Docker容器里控制外面的那个Docker。 这是Manager new的第二个slave,是Manager真正干活的时候,也比较简单,主要是两件事情,第一件事情是作为一个Manager节点,监听了raft的协议一些change的变化,第二是注册了一些API,这些API是Manager节点和agent的节点进行交互的一些API。

注意一下handleLeadershipEvents, Manager实际上是一个小的区别于Node的节点,这几个Manager节点参与raft的选取过程。Manager节点最终干活的只有一个,就是raft协议选出的那个leader。在这个raft协议leader变化的时候,作为Manager节点干的活就不一样了。

在LeadershipEvents发生的时候,当前的Manager就看一下自己是leader还是follower,然后根据不同的角色转换去做不同的事情。

Agent

第三个重点是agent。之前提到做一个Node的agent角色的时候,作为agent的角色它需要做哪些事情——负责Task的分发和执行。Worker这边,它作为一个interface,在agent里则作为agent。它作为interface给大家一个可能性,即SwarmKit这本身可以不只依赖于Docker Engine。我见到开源项目有人叫SwarmKit on Mesos,只要有不同的worker实现,通过Swarm底层是可以运行Mesos的。SwarmKit本身对资源和任务的抽象抽象是固定的。

作为agent,其实多了一个Start, Start的时候支撑了run的函数。核心在于让agent下面由Worker开始干活,以及维护和Manager之间的session—— agent和Worker之间,比如leader的变化、session的变化,有error都会通过session来通知agent做一些相应的事情。比如assign一个task到某个agent或者session处理一些error,大家都可以看到。

还有一个executer。executer内部是一个Docker client,操作Docker,实体化一个Docker,以及删除一个Docker。

Session

session是agent和Worker之间线的抽象。底层是一个gRPC的的client connection,上面有一些Mesos传递方式,有一些channel。初始化一个session,核心在于gRPC去diy一个Manager节点和建立物理上的连接。

Task

这部分代码是TaskSpec的一个描述,并不是真正运行时Task的表达方式。因为Spec其实相当于一个模板, Task第一个field是ContainerSpec,从这里可以看出Task实际上是对container的包装。下面的Resource requirements是需要什么样的资源。第三个是RestartPolicy, Task restart的时候都有哪些策略。Placement对应Manager constraint那一部分,把这个Task负载到一个什么类型的Worker上面。这是Task和Spec运行前的描述。

这是Task的一个结构,它有一个引用是到Taskspec上,上面是一些运行信息,比如Task最终在哪一个Node的ID上,Task最终属于哪一个Service,以及Task slot。我在Google Borg也见到这个slot的概念,它是一个逻辑概念,相当于对资源是一个预留。如果一个Task在slot上失败了,你会发现slot还在,这个Task历史也会在那儿, Task不断的在slot上重启、重启、重启,它实际上是对资源的一个reserve。

这是前面提到的Task life circle,Task有这么多状态,这些状态其实是对一个Task的抽象。作为Dockercontainer,大家会发现状态没有那么多,无非是running和非running。但作为一个Task,它抽象的状态就非常多,可想而知这些状态都是一个状态机,它们之间可能有各种互相的迁移,情况比较复杂。

Service

这是一个Service,很多个stack构成Service。Service mode会分Replicated Service和Global Service,Manager下发一个Service的时候分这两种模式。下面一个字段叫EndpointSpec,是Service对外服务的时候选择哪一种服务发现的方式,目前有两个选项, DNS和VIP。DNS相当于为每一个运行时的Task生成一个DNS SRV结构;VIP的表现形式是Task,因为Docker inspect Task的时候,Task会有一个自己Task的IP,然后Task IP每次请求都打到这个Task IP上,通过IPVS负载到后面每个容器上。这是运行时Service的概念。 SwarmKit目前代码较少,是一个上升的社区,值得关注。今天的分享就到这里,谢谢大家!