本文共 5753 字,大约阅读时间需要 19 分钟。
kubernetes作为容器化应用集群管理系统,为容器化应用提供了便利的资源调度
,部署运行
,服务发现
,扩容缩容
,自动运维
等贴心功能。也正因为其强大且不断丰富的功能,让kubernetes在容器云系统领域越来越受到大家关注。而做为kubernetes系统的设计开发人员,更关注kubernetes系统的设计理念层面,从而可以更好的增强和优化kubernetes。kubernentes的主要设计理念如下:
Level Trigger
而非Edge Trigger
本文通过分析kubernetes整体运行流程和list-watch机制,来说明上述的设计理念1和理念2。而理念3和理念4在后续的文章中详述。
我打算用最常用的ReplicationController
(副本控制器)为依托来说明整体运行机制。
副本控制器: 望名识意,就是可以精确控制Pod的运行数量
上图中流程1~7为用户创建ReplicationController
副本控制器来运行Pod的整体运行流程
流程1(RC创建)
用户通过WebUI或者kubectl工具调用kube-apiserver
提供的标准REST API
接口创建一个ReplicationController
。假定在ReplicationController数据结构中定义为2个Pod。用户相当执行了下面的curl
命令。 curl -XPOST -d "v1.ReplicationController" -H "Content-Type: application/json" http://ip:port/api/v1/namespaces/{namespace}/replicationcontrollers
而此处的v1.ReplicationController
即用户的声明式数据,其中会指定应用的实例数,使用镜像等信息
kube-controller-manager
会通过list-watch机制获取到新建中的v1.ReplicationController
数据,并驱使ReplicationController控制器
工作。kube-controller-manager
中各个控制器模块的工作就是让集群现状趋于用户期待。假定用户声明实例是2,而当前集群中对应的Pod数量为0,则控制器就会创建2个Pod.*当然后续controller-manager也会通过list-watch机制获取到新创建的两个Pod,因为和用户期待值一致,ReplicationController的控制逻辑就收敛在用户期待状态了。*
kube-scheduler
也会通过list-watch机制获取到新创建的v1.Pod
,然后根据调度算法为Pod选择最合适的节点。假定调度结果是两个Pod选择的是minion1和minion2.即刷新v1.Pod
数据的spec.nodeName
字段为minion1和minion2的nodeName。kubelet
也会通过list-watch机制获取到调度完的v1.Pod
数据,然后通过container.Runtime
接口创建Pod中指定的容器。并刷新ETCD中Pod的运行状态。流程/Pod状态 | PodPhase | PodCondition | 组件 |
---|---|---|---|
流程1 | - | - | 用户声明RC数据: 创建RC,无Pod |
流程2~3 | Pending | - | kube-controller-manager: RC控制器创建Pod |
流程4~5 | Pending | PodScheduled=true | Kube-scheduler: 调度器调度Pod成功 |
流程6~7 | Running | PodScheduled=true; PodInitialized=true; PodReady=true | kubelet: Work node上成功运行容器 |
除ETCD外
其他组件都是无状态的。因此从架构设计上对kubernetes系统高可用部署提供了支撑。v1.ReplicationController
)而非用户输入各种命令来工作,这是kubernetes最核心的设计理念。由于kubernetes系统的采取Level Trigger而非Edge Trigger的设计理念,所以各组件只需要感知数据最新的状态,而不需要担心错过数据的变化过程。而作为kubernentes系统消息通知机制(或者说数据实时通知机制),我想应该满足下面几点要求:
kubernetes组件间主要通过http/2协议(kubernetes1.5之前采用http1.1,另Go1.7之后开始支持http/2)进行数据交互,满足实时性主要下面2种方案。
方案缺点: 通信消耗大一些(每个response多一个request)
方案2缺点: 需要对返回数据做定制
上面两种方案都有自己的优缺点,在kubernetes中选择了方案2,且在kubernetes中的http streaming请求我们称为watch请求(其实就是一个http get请求)
注: ETCD2中watch功能选用的是方案1
kubernetes中为每一个REST数据加了一个ResourceVersion字段,并且该字段的值由ETCD来保证全局单调递增(当ETCD中写入一个数据时,全局ResourceVersion就加1)。这样就保证了不同时刻的数据ResourceVersion不同,并且后产生数据的ResourceVersion较之前数据的ResourceVersion大。这样客户端发起watch请求时,只需要带上请求数据在本地缓存中的最新ResourceVersion,而服务端就根据ResourceVersion从小到大把大于
客户端ResourceVersion的数据按顺序推送给客户端即可。这样就保证了推送数据的顺序性。
// ResourceVersion字段就在REST资源结构体的ObjectMeta中。具体如下: type ObjectMeta struct { Name string GenerateName string Namespace string SelfLink string UID types.UID ResourceVersion string <-- 保证顺序就靠它了。 Generation int64 CreationTimestamp Time DeletionTimestamp *Time DeletionGracePeriodSeconds *int64 Labels map[string]string Annotations map[string]string OwnerReferences []OwnerReference Initializers *Initializers Finalizers []string ClusterName string}
具体如下图所示:
因为ETCD保证全局单调+1,所以某类数据的RV可能不会逐步+1变化
基于需求1和需求2的解决方案,需求3主要是对异常状况处理的完善。kubernetes中结合watch请求增加了list请求
,主要做如下两件事情:
kubernetes中的list-watch流程如下:
具体代码参见: kubernetes/vendor/k8s.io/client-go/tools/cache/reflector.go#ListAndWatch()
watch处理中的ResourceVersion更新是在watchHandler()中实现的。
综合上面的方案分析,我们可以综合总结一下解决方案: kubernetes中基于ResourceVersion信息采用list-watch(http streaming)机制来保证组件间的数据实时可靠传送。从今往后,我们就统称该方案为list-watch机制*
level trigger
而非edge trigger
的设计理念,因此在kubernetes中没有像其他的分布式系统中额外引入MQ,降低了系统的整体复杂度(如openstack中的rabbitmq, cloudFoundary中的nuts等)。而只是简单通过http/2 + protobuffer的方式实现了一套list-watch机制来解决各个组件间的消息通知(满足了系统需求)。转载地址:http://jzlgo.baihongyu.com/