【云驻共创】华为云原生之Kubernetes技术架构深度剖析
前言
《云原生王者之路集训营》是华为云云原生团队精心打磨的云原生学习技术公开课,分为黄金、钻石、王者三个阶段,帮助广大技术爱好者快速掌握云原生相关技能。本课程为钻石课程的第二课,华为云容器服务架构师Leo主讲,将详细讲解kubernetes核心机制的实现原理和设计精髓,包括List-Watch机制和Informer模块,以及kubernetes controller机制原理。
适用对象:计算机、软件工程等专业的大学生,涉及Kubernetes、Istio等技术的应用开发,其他的云原生技术兴趣爱好者。
学完本课程后,您将能够:了解Kubernetes系统架构controller控制器原理详解;了解list-watch机制原理。
一 Kubernetes系统架构详解
1.1 Kubernetes总体架构
- Kubernetes是Google开源的容器集群管理系统,它构建在容器技术之上,为容器化的应用提供资源测度。部署运行,服务发现。扩容缩容等一整套功能,本质上是基于容器技术的Micro- Paas平台。Kubernetes的灵感来源于Googlec内郎的Borg系统。
- 将容器本主机组成集群,统进行资源调度,自动管理容器生命周期,提供跨节点服务发现和负载均衡,更好的支持微服务理念,划分,细分服务之间的边界,比如lablel,pod等概念的引入。
- 轻量,迁移方便,部署快捷插件化,可扩展,框架越来越轻量化。
1.2 Kubernetes核心组件
kubernetes核心组件主要包含存储数据的etcd,和接受和处理请求的kube-apiserver,处理pod调度的kube-schedule,控制器管理的kube-controller-manager,及运行在node节点上的为pod提供网络的kube-proxy和容器运行时,欢迎各种丰富的插件等。
- Etcd:是 Kubernetes 集群中的一个十分重要的组件,用于保存集群所有的网络配置和对象的状态信息。
- kube-apiserver:kubernetes 接收用户创建容器等请求的是 Kubernetes Cluster,那么它对外提供服务的接口就是一个 API 接口 ,这个接口需要编程来访问,或者通过编写好的客户端程序来访问,Kubernetes Master 上有一个组件就是 ApiServer,来接收客端请求,解析客户端请求,其主要功能包括认证授权、数据校验以及集群状态变更,以及负责其他模块直接的相互通讯和数据交互,只有api server才能操作etcd,其他模块想要获取数据需要通过api server提供的接口进行相关数据操作。
- kube-schedule: scheduler watch apiserver,接受系统或用户请求是运行,如何要运行一个pod,那么 Master 会使用调度器(scheduler)根据请求来分配一个能够运行容器的 nodes 节点,例如:根据用户对资源要求,CPU、内存、来评估哪个 nodes 最合适运行。大概的过程就是:首先是预选,从 nodes 中挑选出符合用户容器运行要求的,然后在这些预选结果中进行优选,选出最佳的适配 node。
- kube-controller-manager:在 Master 内置组件中有一个控制器管理器,它负责监视着每一个控制器,如果控制器不健康无法工作,那么由控制器管理器来确保控制器的健康,由于 Master 有多个,所以具有冗余性。
- cloud-controller-manager:云控制管理器是指嵌入到特定云的控制逻辑的特定控制平面组件,云控制器允许链接聚合到云服务提供商的应用编程接口中,并分理处相互作用的组件与您的集群交互的组件。
- kube-proxy:在 node 节点上运行的一个守护进程,它负责随时与 apiserver 进行通信,因为每个 pod 发生变化后需要保存在 apiserver 中,而 apiserver 发生改变后会生成一个通知事件,这个事件可以被任何关联的组件接收到,例如被 kube-proxy 一旦发现某个 service 后端的 pod 地址发生改变,那么就由 kube-proxy 负责在本地将地址写入 iptables 或者 ipvs 规则中。所以 service 的管理是靠 kube-proxy 来实现的,当你创建一个 service ,那么就靠 kube-proxy 在每个节点上创建为 iptables 或者 ipvs 规则,每个 service 的变动也需要 kube-proxy 反应到规则上。
- 容器运行时(CRI):负责具体后端容器运行时的软件,kubernetes支持多个容器运行环境:Docker,Containerd,CRI-O以及任何实现kubernetes CRI(容器运行环境接口)。
插件(Addons):DNS,集群DNS是一个DNS服务器,和环境中其他DNS服务器一起工作,他为集群提供DNS记录,kubernetes启动的容器自动将此DNS服务器包含在DNS搜索列表中。
本次主要针对Api-server,etcd,Controller-manager三个组件的流程交换,从而理解k8s资源创建的过程,深入学习kubernetes技术核心架构及list-watch机制。
二 controller控制器原理详解
Controller Manager是集群内部的管理控制中心,负责统-管理与运行不同的 Controller,实现对集群内的Node、Pod 等所有资源的管理。
比如当通过Deployment创建的某个Pod发生异常退出时,RS Controller便会接受并处理该退出事件,并创建新的Pod来维持预期副本数。
2.1 Controller-manager 功能
2.1.1 功能
k8s内部几乎每种特定资源都有特定的Controller维护管理,而Controller Manager的职责便是把所有的Controller聚合起来:
- 提供基础设施降低Controller的实现复杂度;
- 启动和维持Controller的正常运行,watch api-server,然后对不同的Controller分发事件通知。
2.1.2 控制器类型
k8s中有几十种Controller,这里列举一些相对重要的Controller:
- 部署控制器(Deployment Controlier) :负责pod的滚动更新、回滚以及支持副本的水平扩容等。
- 节点控制器 (Node Controller) :负责在节点出现故障时进行通知和响应。
- 副本控制器(Replication Controller) : 负责为系统中的每个副本控制器对象维护正确数量的Pod。
- 端点控制器(Endpoints Controller) : 填充端点(Endpoints)对象(即加入Service与Pod)。
- 服务帐户和令牌控制器(Service Account & Token Controllers) : 为新的命名空间创建默认帐户和API访问令牌。
1 Replication Controller
2 Node Controller
3 CronJob Controller
4 Daemon Controller
5 Deployment Controller
6 Endpoint Controller
7 Garbage Collector
8 Namespace Controller
9 Job Controller
10 Pod AutoScaler
11 RelicaSet
12 Service Controller
13 ServiceAccount Controller
14 StatefulSet Controller
15 Volume Controller
16 Resource quota Controller
2.2 Controller 工作流程
Controller Manager主要提供了一个分发事件的能力, 而不同的Controller只需要注册对应的Handler来等待接收和处理事件。
在Controller Manager的帮助下,Controlller的逻辑可言做的非常存粹,只需要实现相应的EventHandler即可,以Deoloyment controller为例。
2.2.1 List&watch
- Controller manager与api-server的通信主要通过两种方式: List 和Watch;
- List是短连接实现,用于获取该资源的所有object;
- Watch是长连接实现,用于监听在List中获取的资源的变换;
- api-server检测到资源产生变更时,会主动通知到Controller manager (利用分块传输编码)。
controller主要通过list&watch机制进行全量后后期的根据资源事件对k8s资源做出响应,首先通过list全量获取到关注的资源,存储到本队缓存中,之后通过最新的resourceversion来对自由进行watch,获取资源的add/update/delete的事件。
2.2.2 client-go
- client-go实现统,管理每种Controller的List和Watch.
- 将收到的event事件放到缓存中,异步分发给每个Controller的注册的eventHandler。
后期通过事件的类型,回调用户注册进来的eventHandler进行对应事件类型的业务逻辑处理。
2.3 controller的eventHandler是如何注册的
controller是在pkg/controller/deployment/deplloyment_controller.go的NewDeploymentController方法中,包含了event Handler的注册,对于AddEvenetHandler背封装成ProcessListerner并添加到数组中,并且调用了ProcessorListerner的run方法。
我看可以看到在processListerner的run方法中,对p.nextCh中的元素进行add/update/delete事件的处理。
2.4 client-go informer机制
上图为client-go组件用户自定义controller的一个示意图。
2.4.1 client-go组件
- Reflector: reflector用来watch特定的k8s API资源。具体的实现是通过ListAndWatch的方法,watch可以是k8s内建的资源或者是自定义的资源。当reflector通过watch API接收到有关新资源实例存在的通知时,它使用相应的列表API获取新创建的对象,并将其放入watchHandler函数内的DeltaFifo队列中。
- Informer: informer从Delta Fifo队列中弹出对象。执行此操作的功能是processLoop. base controller的作用是保存对象以供以后检索,并调用我们的控制器将对象传递给它。
- Indexer: 索引器提供对象的索引功能。典型的索引用例是基于对象标签创建索引。Indexer可以根据多 个索引函数维护索引。Indexer使用线程安全的数据存储来存储对象及其键。在Store中定义了- 个名为MetaNamespaceKeyFunc的默认函数,该函数生成对象的键作为该对象的<namespace> / <name>组合。
2.4.2 自定义controller组件
- Informer reference:指的是Informer实例的引用,定义如何使用自定义资源对象。自定义控制器代码需要创建对应的Informer。
- Indexer reference:自定义控制器对Indexer实例的引用。自定义控制器需要创建对应的Indexer。
client-go中提供Newlndexerlnformer函数可以创建Informer和Indexer。
- Resource Event Handlers:资源事件回调函数,当它想要将对象传递给控制器时,它将被调用。编写这些函数的典型模式是获取调度对象的key,并将该key排入工作队列以进行进一步处理。
- Workqueue: 任务队列。编写资源事件处理程序函数以提取传递的对象的key并将其添加到任务队列。
- Process ltem:处理任务队列中对象的函数,这些函数通常使用Indexer引用或Listing包装器来重试与该key对应的对象。
三 list-watch机制原理详解
3.1 Informer 封装list-watch
K8S的informer模块封装list-watch API,用户只需要指定资源,编写事件处理函数, AddFunc,UpdateFunc和DeleteFunc等。
Informer是Client-go中的一个核心工具包。为了让Client go更快地返回List/Get请求的结果、减少对Kubenetes API的直接调用,Informer被设计实现为一 个依赖Kubernetes List/Watch API、可监听事件并触发回调函数的二级缓存工具包。
3.2 informer 实现
Informer组件:
- Controller用于处理收到的请求,触发Processor中的回调函数;
- Reflector:通过Kubernetes Watch API监听resource下的所有事件;
- Lister: 用来被调用List/Get方法;
- Processor: 记录并触发回调函数
- DeltaFIFO:存储事件操作类型及事件本身。
- LocalStore:本地缓存。
- DeltaFIFO和LocalStore是Informer的两级缓存。
- DeltaFlFO: 用来存储Watch API返回的各种事件。
- LocalStore: Lister的List/Get方法访问。
当用户删除一个pod,通过kube-apiserver存储到etcd中,reflector 通过watch api-server获取到Pod deleted事件,将其存储到DeletaFIFO中,一部分通过localStore将其在本地缓存中删除,后期Lister就可以直接从LocalStore中查询数据,减轻kube-apiserver的压力,另外一部分,通过DeltaFIFO.Pop()函数从队列弹出元素进行消费,调用用户注册的ResourceEventHandler。
3.3 kubernetes核心机制list-watch
3.3.1 list-watch目标
List-watch是K8S统-的异步消息处理机制,各组件间协同都采用该机制进行通信。List-watch机制保证了消息的实时性,可靠性,顺序性,性能等,为声明式风格的API奠定了良好的基础,它是优雅的通信方式,是K8S架构的精髓。对系统的性能、数据-致性起到关键性的作用。
3.3.2 主要步骤
list-watch操作主要完成以下几个事情:
- Watch核心数据存储是etcd,是典型的发布订阅模式。但不直接访问etcd,通过apiserver发起请求,在组件启动时进行订阅。
- 可以带条件向apiserver发起的watch请求。例如,scheduler想要watch的是所有未被调度的Pod来进行调度操作;而kubelet只关心自己节点上的Pod列表。apiserver向etcd发起的watch是没有条件的,只能知道某个数据发生了变化或创建、删除,但不能过滤具体的值。也就是说对象数据的条件过滤必须在apiserver端而不是etcd端完成。
- list是watch失败, 数据太过陈旧后的弥补手段,这方面详见基于list-watch的Kubernetes异步事件处理框架详解客户端部分。list本身是一 个简单的列表操作。
3.4 通过curl模拟watch
$ kubectl proxy
# 进行listwatch default名称空间下的pods
$ curl "127.0.0.1:8001/api/v1/namespaces/default/pods?watch=true"
# list出defaullt名称空间下的pods
$ curl "127.0.0.1:8001/api/v1/namespaces/default/pods"
# 创建pod进行观察
$ kubectl run nginx --image=nginx
3.5 watch是如何实现的
List的实现容易理解,那么Watch是如何实现的呢? Watch是如何通过HTTP长链接接收apiserver发来的资源变更事件呢?
秘诀就是Chunked transfer. encoding(分块传输编码),它首次出现在HTTP/1.1。
HTTP分块传输编码允许服务器为动态生成的内容维持HTTP持久链接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
当客户端调用watch API时, apiserver 在response的HTTP Header中设置Transfer Encoding的值为chunked,表示采用分块传输编码,客户端收到该信息后,便和服务端该链接,并等待下一个数据块,即资源的事件信息,直到客户主动断链。
3.6 List-watch设计理念
一个异步消息的系统时,对消息机制有至少如下四点要求
3.6.1 消息可靠性
首先消息必须是可靠的,list和watch一起保证了消息的可靠性,避免因消息丢失而造成状态不一致场景。具体而言,list API可以查询当前的资源及其对应的状态(即期望的状态),客户端通过拿期望的状态和实际的状态进行对比,纠正状态不一致的资源。 Watch API和apiserver保持-个长链接,接收资源的状态变更事件并做相应处理。如果仅调用watch API,若某个时间点连接中断,就有可能导致消息丢失,所以需要通过list API解决消息丢失的问题。从另一个角度出发,我们可以认为list API获取全量数据,watch API获取增量数据。虽然仅仅通过轮询list API,也能达到同步资源状态的效果,但是存在开销大,实时性不足的问题。
3.6.2 消息实时性
消息必须是实时的,list- watch机制下,每当apiserver的资源产生状态变更事件,都会将事件及时的推送给客户端,从而保证了消息的实时性。
3.6.3 消息顺序性
消息的顺序性也是非常重要的,在并发的场景下,客户端在短时间内可能会收到同一个资源的多个事件,对于关注最终-致性的K8S来说, 它需要知道哪个是最近发生的事件,并保证资源的最终状态如同最近事件所表述的状态-样。 K8S在每 个资源的事件中都带一个resourceVersion的标签, 这个标签是递增的数字,所以当客户端并发处理同一个资源的事件时,它就可以对比resourceVersion来保证最终的状态和最新的事件所期望的状态保持一致。
3.6.4 高性能
List-watch还具有高性能的特点,虽然仅通过周期性调用list API也能达到资源最终一致性的效果, 但是周期性频繁的轮询大大的增大了开销,增加apiserver的压力。
而watch作为异步消息通知机制,复用一条长链接,保证实时性的同时也保证了性能。
3.7 list-watch机制实现
List-watch的AP处理, kube-apiserver AP注册代码在pkg/apiserver/api_installer.go
- rest.Storage对象会被转化为watcheer和lister对象;
- 提供list和watch服务的入口是同一个,在API接口中通过GET /xxx/services?watch=ture来区分;
- API处理函数是统一通过ListResource完成。
ListResource的实现
每次有一个watch的url请求过来,都会调用rw.Watch)创建一个watcher, 然后使用serveWatch来处理这个请求。watcher的生命周期是每个http请求的,这-点非常重要。
响应http请求的过程serveWatch()代码在/pkg/apiserver/watch.go里面
watcher的结果channel中读取一个event对象,然后持续不断的编写写入到http_response的流当中。
四 list-watch实现机制总结
- 首先在控制器启动时,通过运行Informer的run函数,执行Reflector中的run,全量的list kubernetes中的资源对象到deltaFIFO中。
- Reflector 通过 ListAndWatch 首先获取全量的资源对象数据,然后通过 syncWith 函数全量替换(Replace) 到 DeltaFIFO queue/items 中,如果设置了定时同步,则定时更新Indexer中的内容,之后通过持续监听 Watch(目标资源类型) 增量事件,并去重更新到 DeltaFIFO queue/items 中,等待被消费。
- sharedIndexInformer的HandleDeltas处理从deltaFIFO pod出来的增量时,先尝试更新到本地缓存cache,更新成功的话会调用processor.distribute方法向processor中的listener添加notification,listener启动之后会不断获取notification回调用户的EventHandler方法。
- 当用户注册的EventHandler收到事件后,将其添加进workqueue中。
- 之后运行用户自定义的processNextItem,不断从workqueue中获取对象,并对其进行业务逻辑处理,如果处理发生异常,则重新入队列,如果处理成功,则forget调改对象。
总结
kubernetes基于list-watch机制的控制器架构,我们来看一个具体replicaset创建流程来回顾下Kubernetes整体的架构。
首先在controller-manager/scheduler/kubelet启动的时候都会去watch自己关注的资源。
controller-manager对api-server发起ReplicaSet的watch,scheduler发起对Pod desNode=""的watch,kubelet发起对pod destNode="myNode"的watch。
用户kubectl创建一个ReplicaSet请求调用Api-server,api-server将创建replicaSet存储到etcd中,api-server同时也获取到创建replicaset的created事件,controller-manager此刻watch到replicaset创建时间后通过pod模版进行pod创建并持久化到etcd中,scheduler watch到创建的pod创建事件通过预选和优选策略为pod选定运行的node,并更新pod的nodeName字段,之后kubelet watch到在自己上需要运行pod,调用cri/cni/csi创建pod 。
K8s控制器是一主动的调协过程,watch一些资源对象期望状态,也会watch实际状态,之后通过控制器发送执行动作指令,让对象的当前状态往期望状态逼近。
本文整理自华为云社区【内容共创】活动第13期。
https://bbs.huaweicloud.com/blogs/33 0939
任务19.华为云云原生钻石课程02:Kubernetes技术架构深度剖析
- 点赞
- 收藏
- 关注作者
评论(0)