Kubernetes是Google开源的Docker容器集群管理系统,是Docker生态圈中的重要一员。随着Kubernetes社区及各大厂商的不断改进、发展,Kuberentes已经成为容器管理领域的领导者之一。华为高级工程师王泽锋日前为大家带来了「Kubernetes在华为全球IT系统中的实践」的分享,以下是对其演讲内容的整理:
华为作为一个全球性的公司,其IT系统规模非常庞大,部署在全球各个地区,业务量大,应用数量急剧增长。随着规模的愈发庞大,问题也越来越突出:
面对业务压力,Cloud Container Engine(以下简称CCE)的出现很好的解决了以上问题。华为的CCE可以理解为是华为的Kubernetes商业版本(图2),整体上是希望通过一套核心技术,去打通IaaS和PaaS平台,一套技术支撑多种业务场景。
2015年底CCE在华为IT系统上线(图3),资源利用率提升了3到4倍,当前的规模达到2000多个虚机。下面重点讲下近期的两大重点技术实践:Kubernetes多集群联邦、应用间的亲和/反亲和调度。
图4是Kubernetes非常简单的架构图,图中可以看到如下组件:
其基本调度单位是Pod,具体应用下面实例会提到。
从华为内部IT系统的某项业务估算(图5),其机器数量在2016年整体容器化之后,会达到3万虚机和10万容器。华为IT系统的典型场景是通过多数据中心统一管理部署应用,提供跨域的应用服务发现。但是跨域的网络限制与差异较大,难以通过k8s单集群支持。所以如何提供跨域应用的大规模集中部署成为目前需要解决的问题。
图6是一个简单的多集群联邦架构示意图,图中的3个黑块的是在集群联邦层面加出来的三个关键组件,底下白色对应的是一个集群,这个架构跟Kubernetes原来的架构非常相似。原来Kubernetes的架构也是一个Master管理多个节点,这里是一个Master管理多个集群。如果把图中的集群换成节点,其实就是Kubernetes的架构。用户通过统一的API入口创建应用,集群联邦所做的就是把联邦级别对象应用拆分到各个子集群分别部署。Cluster controller会维护各个集群的健康状态、监控负载情况,而Service controller做一个提供跨集群的服务发现的打通。
总的来说,现在集群管理架构都比较相似,都有Controller、Scheduler和Agent(Kubelet),这里是利用 List-Watch(图7)机制实现的组件间交互的解耦,体现了Everything talk to API的设计理念。比较类似SOA架构中的消息总线,但跟消息总线的差别在于对数据的存储和事件通知做了统一化处理,当集群收到新的事件的同时,可以拿到这一份新的数据,包括数据的变化是新增还是更新了某个对象。
实例说明:这时要创建一个ReplicaSet,第一步是集群起来后,Controller-manager(包含多个Controller)、Scheduler和Kubelet都会去发起watch,但是watch的对象都是不一样的。ReplicaSet Controller会watch ReplicaSet,Scheduler watch的是一个Pod(也可以理解为一个应用的实例)。这里的watch是带一个条件的,destNode为空,就是未调度的Pod;而Kubelet watch的Pod只是destNode为Node自身的Pod,也就是这个Pod调度到相应的节点才会做相应的处理。在创建应用的时候,k8s会先将对象保存到etcd中,同时存完之后会上报已创建的事件。与此同时ReplicaSet Controller已经事先watch这个事件,它就会收到通知。ReplicaSet Controller发现有一个新的RS被创建,这个时候它会去做拆分,因为RS对应的就是一个多实例无状态的应用,所以RS有多少个副本,Controller就创建多少个相同的Pod。
这样第一阶段创建的过程已经完成。因为Scheduler watch了Pod,新创建的Pod是没有被调度过的,destNode是空的,这个时候它会收到Pod创建的通知,并执行调度算法(把这个集群中所有可用节点的数据根据Pod的调度需求做一个综合计算,选出一个Node),然后将Pod绑定到这个Node上(更新这个Pod的destNode),这样Scheduler的流程就结束了。然后在相对应被绑定的Node上,Kubelet会发现有新的Pod调度过来,就会按照Pod的定义创建并启动容器。
经过上述的流程可知,各个组件通过List-Watch不同的对象(即便是相同的对象,状态也是不一样的),实现天然的解耦,各个组件处理一个应用生命周期的不同阶段,同时保证了在一个异步的分布式系统里面多组件间处理流程的先后顺序问题。同时List-Watch每次只是做事件的监控,所以每次watch比如创建一个Pod获取的是增量信息,获取的数量交互是非常少的。我们知道通过消息总线传一个消息/事件,是有可能丢失的,这就需要有一个容错机制,来保证最终一致性。Kubernetes内部是通过List来解决的。比如ReplicaSet Controller会周期性地对ReplicaSet做全量List,拿到的数据就是当前系统需要起的应用数据,它会检查当前各个应用的实例运行情况,做相应处理,多余的删除,不足的补齐。
图8是集群联邦下的应用创建流程。如图所示,用户向联邦API Server发请求创建应用,它会首先将对象保存起来。同时Controller会周期性获取各集群的监控数据,同步到API Server。在Scheduler watch到创建应用之后,它会根据调度算法,按照联邦里面所有的集群监控信息包括健康情况等指标筛选出合适的集群,对应用做拆分(当前主要是各个集群的容量和监控负载情况)。这个例子里面应用被拆分到了两个集群,cluster A是2个实例、cluster B是3个实例,拆分后仍是把这个对象保存到API Server,跟Kubernetes原生在单集群下创建的流程是一致的。这个时候Controller会watch到sub RS创建(实际上是RS对象的一个属性更新),它会去对应的集群创建实际的应用实体,实例数量为前面应用拆分时的运算值。
图9呈现了联邦调度器的关键机制,它watch的是两类对象,一个是副本集,一个是Cluster(联邦里面有多少可用集群,它们负载的情况等)。当RC/RS有变化时,会将它们保存到一个本地队列,worker会从这个本地队列中逐个读取RC/RS并处理。处理时根据加载的调度策略筛选出合适的目标集群,然后去计算出分别应该在这几个集群创建多少个实例。最后写回到API Server的数据格式如图所示,是更新RS的一个字段destClusters,未调度时是空值,现在变成[{cluster: “clusterA”, replicas: 6}, … ]这样的内容。
集群联邦中包含多个Controller,这个例子(图 10)里主要涉及两个,一个是Cluster Controller,一个是Replication Controller。Cluster Controller watch的是cluster对象,维护联邦中所有Cluster状态信息,包括周期性的健康检查、负载情况等。如果配置了安全访问,还需要确保配置(比如访问集群所用的证书文件)是正确可用的。Replication Controller则watchRS,刚才提到联邦调度器也会watch RS,这里watch的是已经过调度器拆分处理的RS,并负责到各个集群创建指定数量的实例。
接着前面的例子,拆分到Cluster的A是2个实例,拆分到Cluster的B是3个实例,这个实际上可以理解为一个控制面的处理,解决了一个应用在联邦下部署到多个集群的问题。接下来要解决数据面的问题,部属下去之后如何实现应用跨集群的访问(图11)。我们知道在单集群里面是全通的网络,但是跨集群的我们还希望适配混合云的场景,这个时候底层的网络不一定是通的,因此我们直接按照集群间网络不通的场景来设计。从外部流量来说,用户请求会通过全球分布式路由被分摊到各个集群,每个集群有一个负载均衡,再把流量导到各个Node上去。跨集群访问的时候,集群内的访问仍可以按原来的服务发现机制处理。跨集群的时候因为是跨网络访问,网络是不通的,这里有个约束是每个集群的负载均衡器需要有public IP,可以被所有集群内的所有Node访问。当应用的访问请求需要跨集群的时候,流量会被转发到对端集群的负载均衡器上,由它再做反向代理,再转到后端应用实例所在的Node上去。
图12是Service Controller的关键机制:一个是watch Service变化、一个是watch Cluster变化。如果某个Cluster挂了流量就不应该转发过去,因此要watch它的变化,在服务里面刷新每个服务后端(Cluster)的联通性,保证它的每一个后端都是通的。同时要 watch 服务的变化,可能是新创建服务,服务后端的实例被刷新,或者说是服务后端可能有LB挂掉,对于这个服务来说都是有变化的,这个时候要去做相应处理。
Cluster处理的就是Cluster的状态发生变化的情况。这里简单介绍下新增服务时的处理流程。在联邦Service Controller中,每个集群都有服务对象,服务包含了Endpoint,这里有两类,一个是当前集群内服务的Pod实例所在的Node,另外跨集群的时候,需要访问到其它集群所在的LB,它是把这两组信息都写到kube-proxy里面去。同时为了对外提供访问支持,需要按集群分别将本集群中服务实例的Endpoint写到对应集群的LB中,做反向代理。这样就支持了单集群内的通信和跨集群通信。对于外部流量,因为最前面是全球分布式路由,用户在访问的时候,可以根据用户的地域或访问时延等因素,选择一个就近的集群的LB,以获得较为理想的访问速度。
容器化和微服务化的改造会出现亲和性和反亲和性(图13)的疑问,原来一个虚机上会装多个组件,进程间会有通信。但是在做容器化拆分的时候,往往直接按进程拆分容器,比如业务进程一个容器,监控日志处理或者本地数据放在另一个容器,并且有独立的生命周期。这时如果他们分布在网络中两个较远的点,请求经过多次转发,性能会很差。所以希望通过亲和性实现就近部署,然后增强网络能力实现通信上的就近路由,减少网络的损耗。反亲和性主要是出于高可靠性考虑,尽量分散实例,某个节点故障的时候,对应用的影响只是N分之一或者只是一个实例。
先来看一下单应用的情况(图14)。在这个例子里,我们希望所有的实例部署到一个AZ里面(即在AZ级别互相亲和),同时在Node级别互相反亲和,每个Node部署一个实例。这样某个Node挂的时候,只会影响一个实例。但是每个不同的企业,在不同的平台上,对于这种AZ、Rack、Node等failure domain的理解和命名都不一样。比如有人会希望应用在机框级别做反亲和,因为有可能一个机框都会down掉。但本质上它们都是Node上的一个label,调度时只需要按这个特殊的label进行动态分组,处理亲和反亲和的关系即可。
在亲和性、反亲和性的支持上面,会实现硬性和软性两种支持(图15),因为如果是全硬性条件,很容易因为配置不恰当导致应用部署失败。这里实质上是两类算法,但是在实现逻辑上比较接近。硬性算法做过滤,不满足的节点都全部过滤掉,保证最后留下来的一定是满足条件的。在软性的情况下,不需要这么苛刻地要求,只是best effort,条件不满足时仍希望应用调度部署成功,这里用的是评分排序,根据亲和性和反亲和性的符合情况,符合程度高的给高分,符合程度低的给低分,选Node时按分数从高到低进行选择。
亲和性是相互的,这个时候就会考虑对称性的问题(图16)。两个应用互相亲和,在应用的定义里面,比如应用B亲和应用A,实际是在应用B里面写一条亲和的规则去亲和A,但是A不知情。如果A先创建B后创建,是没有问题的,因为B会去找A,但是反过来A不会去找B。因此我们在做算法实现的时候给它加了一个默认行为,在调度的时候,需要自己亲和/反亲和哪些Pod(前面的单向思路);同时又要去检查哪些Pod亲和/反亲和自己,以此实现对称性 。
另外一个常见的问题是被亲和的应用发生迁移的情况。需要说明的有两点,一是在算法设计上做了对称性的考虑,不管是先部署还是后部署,即便这个应用挂了,它在重建并被调度时,依然会检查当前系统里亲和哪些Pod或者被哪些Pod亲和,优先跟他们部到一起去。另外,目前RC/RS(副本集无状态应用)只有Node挂掉的时候才会发生重建Pod的情况,Node没有挂,异常退出是在原地自己重启的。这个从两个层面上,能够保证亲和的应用不在一起、反亲和的应用分开这种需求。
这时做了硬性和软性两种实现,在做软性的时候没问题,因为即便不满足也能调度成功,按照完全对称的思路就可以做了。这里最主要的问题就是硬性亲和的对称性。亲和其他应用的场景,如果被亲和的应用不存在(未调度),调度失败是合理的。但是如果因为不存在其他的Pod亲和当前待调度的Pod而导致失败就不合理。因此硬亲和不是完全对称的,它的反向是一个软亲和。
尽管我们在亲和性/反亲和性的实现上做了对称性考虑,但它只能在涉及调度的阶段起作用,调度后,一个Pod的生命周期内,不会再根据实际的情况进行调整。因此后面我们会从两个方面进一步解决前面的问题(图18)。
第一个是限制异地重启,即Forgiveness。假如Node故障或重启导致Pod被迁移(Pod重建后被调度到其他Node),假如Pod原先保存了一些本地数据,但是因为被迁移到了别的节点,这些数据相当于丢失。所以这个时候给它配一个Forgiveness策略,指定Pod绑定在它所运行的Node上,如果这个Node挂了,不迁移Pod,而是等待Node自动恢复的时候,把这个Pod原地拉起。这样原来存在本地硬盘里的数据,就可以被处理。对于有实效性的本地数据,我们还引入了超时机制。比如本地暂存的日志,超过两天三天之后,这个日志再做后续处理也没有意义。这个时候Pod需要在两三天之内绑定在这个Node上,等到Node恢复再处理任务。如果超过两三天Node还没有恢复,但这些本地数据已经失去意义,这时候,我们希望Pod可以被迁移。在做驱逐检查的时候,没有超时的Forgiveness Pod就继续绑定在Node上,超时的正常驱逐进行迁移就可以了。
第二个是运行时迁移。在运行的过程中,集群负载、应用间亲和性的满足程度、Node的健康度或者服务质量时时刻刻都在变化,系统应该能够根据这个当前的情况去动态地调整应用实例的分布,这就是Rescheduling的意义。Rescheduling最终体现的行为是周期性地检查集群状态,并做迁移的调整。但在迁移之前,有许多要考虑的因素,比如应用的可用性问题(不能因为迁移导致某个应用的所有实例在某个时刻不可用),迁移的单向性(被迁移的Pod如果回到原来的Node,就变成多此一举)和收敛性(不能因为迁移某个Pod而引发大量其他Pod被迁移)。
关于:中科研拓
深圳市中科研拓科技有限公司专注提供软件外包、app开发、智能硬件开发、O2O电商平台、手机应用程序、大数据系统、物联网项目等开发外包服务,十年研发经验,上百成功案例,中科院软件外包合作企业。通过IT技术实现创造客户和社会的价值,致力于为用户提供很好的软件解决方案。联系电话400-0316-532,邮箱sales@zhongkerd.com,网址www.zhongkerd.com