当前位置: 首页 > news >正文

dockerkubernets篇(二十六)

controller manager–kubernet组件

Kubernetes controller manager运行在集群的master节点上,是基于pod API上的一个独立服务,它管理着Kubernetes集群中的各种控制器,包括读者已经熟知的replication controller和node controller。相比之下,APIServer负责接收用户的请求,并完成集群内资源的“增删改”,而controller manager在系统中扮演的角色是在一旁默默地管控这些资源,确保它们永远保持在用户所预期的状态。这里,同样从它的启动过程开始分析。

Contorller Manager启动过程

随着Kubernetes版本的更迭,系统支持的生产场景日益丰富,controller manager负责的资源管控工作也更为繁复(如v1.2.0版本中,就有超过15个子控制器),其启动流程大致分为如下几个步骤。(1) 根据用户传入的参数以及默认参数创建kubeconfig和kubeClient。前者包含了controller manager在工作中需要使用的配置信息,如同步endpoint、rc、node等资源的周期等;后者是用于与APIServer进行交互的客户端。
(2) 创建并运行一个http server,对外暴露/debug/pprof/、/debug/pprof/profile、/debug/pprof/symbol和/metrics,用作进行辅助debug和收集metric数据之用。
(3) 按顺序创建以下几个控制管理器:服务端点控制器、副本管理控制器、垃圾回收控制器、节点控制器、服务控制器、路由控制器、资源配额控制器、namespace控制器,horizontal控制器、daemon set控制器、job控制器、deployment控制器、replicaSet控制器、persistent volume控制器(可细分为persistent volume claim binder、persistent volume recycler及persistent volume provision controller)、service account控制器,再根据预先设定的时间间隔运行。特别地,垃圾回收控制器、路由控制器仅在用户启用相关功能时才会被创建,而horizontal控制器、daemon set控制器、job控制器、deployment控制器、replicaSet控制器仅在extensions/v1beta1的API版本中会被创建。

controller manager控制pod、工作节点等资源正常运行的本质,就是靠这些controller定时对pod、工作节点等资源进行检查,然后判断这些资源的实际运行状态是否与用户对它们的期望一致,若不一致,则通知APIServer进行具体的“增删改”操作。理解controller工作的关键就在于理解每个检查周期内,每种资源对象的实际状态从哪里来,期望状态又从哪里来。接下来,我们以服务端点控制器、副本管理控制器、垃圾回收控制器、节点控制器和资源配额控制器为例,分析这些controller的具体工作方式。

服务端点控制器(endpoint controller)

解读了Kubernetes中的service及与之相关联的endpoint资源设计思路。这两类资源的存在降低了基于Kubernetes平台构建分布式应用的难度。毕竟,自己给集群搭一个自带动态服务发现能力的负载均衡组件可能是费力不讨好的。Kubernetes中的endpoint controller负责维护endpoint及其与对应service的关系,会周期性地进行检查,以确保它们始终运行在用户所期望的状态。

type EndpointController struct {  
	 client *clientset.Clientset   
 	serviceStore cache.StoreToServiceLister   
 	podStore     cache.StoreToPodLister 
	 queue *workqueue.Type   
	 serviceController *framework.Controller 
	 podController     *framework.Controller
}

当用户在Kubernetes中创建一个包含label selector的service对象时,系统会随之创建一个对应的endpoint对象,该对象即保存了所有匹配service的label selector后端pod的IP地址和端口。可以预见,endpoint controller作为endpoint对象的维护者,需要在service或者pod的期待状态或实际状态发生变化时向APIServer发送请求,调整系统中endpoint对象的状态。

顺着这条思路,可以发现endpoint controller维护了两个缓存池,其中serviceStore用于存储service, podStore用于存储pod,并且使用controller的reflector机制实现两个缓存与etcd内数据的同步。具体而言,就是当controller监听到来自etcd的service或pod的增加、更新或者删除事件时,对serviceStore或podStore做出相应变更,并且将该service或者该pod对应的service加入到queue中。也就是说,queue是一个存储了变更service的队列。endpoint controller通过多个goroutine来同时处理service队列中的状态更新,goroutine的数量由controller manager的ConcurrentEndpointSyncs参数指定,默认为5个,不同goroutine相互之间不会相互干扰。

每个goroutine的工作可以分为如下几个步骤。
(1) 从service队列中取出当前处理的service名,在serviceStore中查找该service对象。若该对象已不存在,则删掉其对应的所有endpoint;否则进入步骤(2)。
(2) 构建与应该service对应的endpoint的期望状态。根据service.Spec.Selector,从podStore获取该service对应的后端pod对象列表。对于每一个pod,将以下信息组织为一个新的EndpointSubset对象:pod.Status.PodIP、pod.Spec.Hostname、service.spec中定义的端口名、端口号、端口协议、和pod的资源版本号(ResourceVersion,同样作为endpoint对象的资源版本号),并且将所有EndpointSubset对象组成一个slice subset,这是期望的endpoint状态

说明 Kubernetes努力使得pod等资源的实际状态与期望状态一致,会在etcd中保存两份数据,分别对应期望状态和实际状态。然而这里也有例外,由Kubernetes自动维护的endpoint对象,即当用户创建一个包含label selector的service对象时,系统随之自动创建的endpoint对象,并非由用户显式创建,endpoint的期望状态可以认为是从service和pod两类对象动态构建出来,当前Kubernetes并未将其存储在etcd中。

(3) 使用service名作为检索键值,调用APIServer的API获取当前系统中存在的endpoint对象列表currentEndpoints,即endpoint的实际状态。如果找不到对应的endpoint,则将一个新的Endpoint对象赋值给currentEndpoints,此时它的ResourceVersion为0。将步骤(2)中endpoint期望状态与实际endpoint对象列表进行比较,包括两者的pod.beta.kubernetes.io/hostname的annotation、subset(包含端口号、pod IP地址等信息),以及service的label与目前endpoint的label,如果发现不同,则调用APIServer的API进行endpoint的创建或者更新。如何判断需要进行的是创建还是更新呢?这就与ResourceVersion分不开了。如果ResourceVersion为0,说明需要创建一个新的endpoint,否则,则是对旧的endpoint的更新。

通过上述分析可以看到,controller的一般处理逻辑是先获取某种资源对象的期望状态。期望状态可能是存储在etcd里的spec字段下的数据,也可能是类似endpoint这样的动态构造。然后将之与实际状态对比。controller对这两者做比较之后,就能够向APIServer发请求弥补两者之间可能存在的差别。

副本管理控制器(replication controller)

replication controller负责保证rc管理的pod的期望副本数与实际运行的pod数量匹配。可以预见,replication controller需要在rc或者pod的期待状态发生变化时向APIServer发送请求,调整系统中endpoint对象的状态。同样地,先来通过数据结构大致了解一下它的工作模式。

type ReplicationManager struct {
    kubeClient clientset.Interface
    podControl controller.PodControlInterface
    burstReplicas int
    syncHandler func(rcKey string) error
    expectations *controller.UIDTrackingControllerExpectations
    rcStore cache.StoreToReplicationControllerLister
    rcController *framework.Controller
    podStore cache.StoreToPodLister
    podController *framework.Controller
    podStoreSynced func() bool
    lookupCache *controller.MatchingCache
    queue *workqueue.Type
}

它在本地维护了两个缓存池rcStore和podStore,分别同于同步rc与pod在etcd中的数据,同样调用controller的reflector机制进行list和watch更新。一旦发现有rc的创建、更新或者删除事件,都将在本地rcStore中进行更新,并且将该rc对象加入到待更新队列queue中。事实上,读者可能会对此产生一定的疑问——对于更新事件,难道不是仅在.spec字段发生变更时才进行相应的处理就足够了吗?事实上,这是一种更加安全的做法。

对于监听到pod的事件,则相对较为复杂。对于创建和更新pod,都要检查pod是否实际上已经处于被删除的状态(通过其DeletionTimestamp的标记),如果是则触发删除pod事件;对于创建与删除pod,还需要在expectations中写入相应的变更;expectations是replication controller用于记住每个rc期望看到的pod数的TTL cache,为每个rc维护了两个原子计数器(分别为add和del,用于追踪pod的创建或者删除)。对于pod的创建事件,add数目减少1,说明该rc需要期待被创建的pod数目减少了1个;类似地,对于删除事件,则是del的数目减少1。所以说,如果add的数目和del的数目都小于或等于0,我们就认为该rc的期望已经被满足了(即对应的Fulfilled方法返回为true值)。读者们也许会好奇,expectations中add和del的初始值为多少呢?事实上,在replication controller创建时,它们都被初始化为0,直到TTL超时或者期望满足时,该rc才会被加入到sync队列中,此时重新为该rc设置add和del值。最后,不管是哪种事件,都

replication controller的同步工作将处理rc队列queue,对系统中rc中副本数的期望状态及pod的实际状态进行对比,并启用了多个goroutine对其进行同步工作,每个goroutine的工作流程大致如下。
(1) 从rc队列中取出当前处理的rc名,通过rcStore获得该rc对象。如果该rc不存在,则从expectations中将该rc删除;如果查询时返回的是其他错误,则重新将该rc入队;这两种情况均不再进行后续步骤。
(2) 检查该rc的expectations是否被满足或者TTL超时,如果是,说明该rc需要被同步,在步骤(2)执行结束后将进入步骤(3),否则进入步骤(4)。调用APIServer的API获取该rc对应的pod列表,并且筛选出其中处于活跃状态的pod(即.status.phase不是Succeeded, Failed以及尚未进入被删除阶段)。
(3) 调整rc中的副本数。将(2)步骤中获得的活跃pod列表与rc的.spec.replicas字段相减得到diff,如果diff小于0,说明rc仍然需要更多的副本,设置expectations中的add值为diff,并且调用APIServer的API发起pod的创建请求,创建pod完毕后还需要将expectations的add相应减少1。如果diff大于0,说明rc的副本数过多,需要清除pod,将expectations中的del设为diff值,并且调用APIServer的API发起pod的删除请求,删除pod后还需要将expectations的del相应减少1。实际上因为工程的需要,引入了一个burstReplicas,默认为500,限制diff数目小于或等于该值。
(4) 最后,调用APIServer的API更新rc的status.replicas。可以看到,Controller的运作过程依然遵循了旁路控制的原则,真正操作资源的工作是交给APIServer去做的。

垃圾回收控制器(gc controller)

在用户启动pod的垃圾回收功能时,该控制器会被创建。所谓回收pod,是指将系统中处于终止状态的pod删除。读者在后续的8.3.4节中可能还会读到kubelet执行的垃圾回收,注意,前面针对的是容器和镜像的回收,而此处针对的是pod。在Kubernetes的设计中两者并非紧密关联,因此它们的回收流程是分开执行的。
gc controller维护了一个缓存池podStore,用于存储终止状态(即podPhase不是Pending、Running、Unknown三者)的pod,并使用reflector使用list和watch机制监听APIServer对podStore进行更新。
要执行垃圾回收,首先会考察podStore中的pod数量是否已经到达触发垃圾回收的阈值。如果没有到达,不进行任何操作;否则,将所有pod按照创建时间进行排序,最先创建的pod将被优先回收。当然,删除pod的实际操作也是通过调用APIServer的API实现。

节点控制器(node controller)

ode controller是主要用于检查Kubernetes的工作节点是否可用的控制器,它会定期检查所有在运行的工作节点上的kubelet进程来获取这些工作节点信息,如果kubelet在规定时间内没有推送该工作节点的状态,则将其NodeCondition为Ready的状态置成Unknown,并写入etcd中。在介绍node controller的具体职责之前,先明确一下工作节点在Kubernetes的表示方式。
● 工作节点描述方式

Kubernetes将工作节点也看作资源对象的一种,用户可以像创建pod那样,通过资源配置文件或kubectl命令行工具来创建一个node资源对象。当然,真正物理层面的工作节点(物理机或虚拟机)并不是由Kubernetes创建的,创建node资源对象只是为了抽象并维护工作节点的相关信息,并对工作节点是否可用进行持续的追踪。Kubernetes主要维护工作节点对象的两个属性——spec和status,分别被用来描述一个工作节点的期望状态和当前状态。其中,期望状态由一个json资源配置文件构成,描述了一个工作节点的具体信息,而当前状态信息则包含如下一系列节点相关信息。

  • Node Addresses:工作节点的主机地址信息,通常以slice(数组)的形式存在。如果工作节点是由IaaS平台创建的虚拟机,那么它的主机地址通常可以通过调用IaaS API来获取。Addresses的种类可能是Hostname、ExternalIP或InternalIP中的一种。经常被使用到的是后两者,并且通过能否从集群外部访问到进行区分。
  • Node Phase:即工作节点的生命周期,它也由Kubernetes controller manager管理。工作节点的生命周期可以分为3个阶段:Pending、Running和Terminated。刚创建的工作节点处于Pending状态,直到它被Kubernetes发现并通过检查。检查通过后(譬如工作节点上的服务进程都在运行),它会被标记为Running状态。工作节点生命周期结束称为Terminated状态,处于Terminated状态的工作节点不会接收任何调度请求,且本来在其上运行的pod也都会被移除。一个工作节点处于Running状态是可调度pod的必要而非充分条件。如果一个工作节点要成为一个调度候选节点,它还需要满足被称为Node Condition的条件。
  • Node Condition:描述Running状态下工作节点的细分状况,也就是说,一个Running的工作节点,并不一定可以接收pod,还要观察它是不是满足一些列细分要求。在撰写本书时,可用的Condition值包括NodeReady和NodeOutOfDisk,前者意味着工作节点上的kubelet进程处于健康状态,且已经准备好接收pod了;后者表示该工作节点上的可用磁盘空间不足,导致无法接收新的pod。但是在未来,状态值应该会持续增加,已经被提出而尚未实现的状态包括NodeReachable、NodeLive、NodeSchedulable、NodeRunnable等,为不同工作节点划分出不同的健康级别有助于Kubernetes作出更复杂的调度决策。
  • Node Capacity与Node Allocatable:分别标识工作节点上的资源总量及当前可供调度的资源余量,涉及的资源通常包括CPU、内存及Volume大小。
  • Node Info:一些工作节点相关的信息,如内核版本、runtime版本(如docker)、kubelet版本等,这些信息经由kubelet收集。
  • Images:工作节点上存在的容器镜像列表。
  • Daemon Endpoints:工作节点上运行的kubelet监听的端口。

● 工作节点管理机制

与pod和service不同的是,工作节点并不是真正由Kubernetes创建的,它是要么由IaaS平台(譬如GCE)创建,要么就是用户管理的物理机或者虚拟机。这意味着,当Kubernetes创建一个node时,它只是创建了一个工作节点的“描述”。因此在工作节点被创建之后,Kubernetes必须检查该工作节点是否合法。以下资源配置文件描述了一个工作节点的具体信息,可以通过该文件创建一个node对象。

{
	"kind": "Node",
	"apiVersion": "v1",
	"metadata": {
		"name": "10.240.79.157",
		"labels": {
			"name": "my-first-k8s-node"
		}
	}
}

一旦用户创建节点的请求被成功处理,Kubernetes会立即在内部创建一个node对象,再根据metadata.name去检查该工作节点的健康状况,这一字段是该节点在集群内全局唯一的标志。可能读者会有疑惑,工作节点难道不是一直可用的吗?为什么还需要再检查它?其实,在任何时候,一个工作节点都有可能失败的,因此只有那些当前可用的工作节点才会被认为有效,并允许将pod调度到上面运行。注意,在Kubernetes中,工作节点有node和minion两种新老叫法,读者不必刻意区分。工作节点的动态维护过程是依靠node controller(节点控制器)来完成的,它是Kubernetes controller manager下属的一个控制器。简单地说,controller manager中一直运行着一个循环,负责集群内各个工作节点的同步及健康检查。这个循环周期由传入参数node-monitor-period控制

● node controller

检查工作节点的循环node controller维护了3个缓存podStore、nodeStore、daemonSetStore,分别存储pod、node、daemonSet资源,同时有3个相应的controller podController、nodeController、daemonSetController来应用list/watch机制同步etcd中相应资源的状态。有趣的是,它们对于监控到的资源变化非常地不积极——podController只响应pod创建和更新事件,此时将检查该pod是否处于终止状态或者没有被成功调度到一个正常运行的工作节点上,如果是的话,则调用APIServer的API将其强行删除。而nodeController和daemonSetController则对这些变化不做任何操作。

node controller的主要职责是负责监控由kubelet发送过来的工作节点的运行状态,这个监控间隔是5秒钟。它将其维护的已知工作节点列表记录在knownNodeSet中,并由kubelet推送的信息判断其是否准备好接收pod的调度(即处于Ready状态),如果工作节点的不Ready状态超过了一定时限,还会调用APIServer的API将其上运行的pod删除。此外,工作节点是否处于OutOfDisk状态,也同样被关心。在这一工作流程中,也会处理新工作节点的注册和旧工作节点的删除。node controller还会每隔30秒进行一次孤儿pod的清除。所谓的孤儿进程,是指podStore中缓存的pod中被bind到一个不再处于nodeStore中的工作节点的pod删除。

至此,node controller的主要工作流程就已经全部完成了。也许读者会产生疑问,它维护的daemonSetStore缓存用来做什么呢?实际上,它将在删除工作节点上的pod时用作判断,如果被删除的pod是被daemonSet管理的,那么将会跳过该pod,不进行删除工作。

node controller和其他controller最大的不同在于,事实上的工作节点资源并不由Kubernetes系统产生和销毁——而是依靠底层的物理机器资源或者云服务提供商的IaaS平台。etcd里存放的node资源只是一种说明它是否正常工作的描述性资源,而它是否能够提供服务的信息则由kubelet来提供。

明KubeletServer的/healthz端点同样提供工作节点的运行状态信息,如果访问该端点返回ok,则代表工作节点运行正常,否则代表工作节点运行发生错误。这是一种主动探查的方式,不过目前node controller没有依赖这种方法。

资源配额控制器(resource quota controller)

集群资源配额一般以一个namespace为单位进行配置,它的期望值(即集群管理员指定的配额大小)由集群管理员静态设置,而它的实际使用值会在集群运行过程中随着资源的动态增删而不断变化,resource quota controller用于追踪集群资源配额的实际使用量,每隔——resource-quota-sync-period时间间隔就会执行一次检查,如果发现使用量发生了变化,它就会调用APIServer的API在etcd中进行使用量的动态更新。它支持的资源包括pod/service/replication controller/persistent volume/secret和configMap/cpu/memory,当然还有resource quota本身。为了完成resource quota的同步工作,resource quota controller维护一个队列,所有需要同步的resource quota都将入队。

首先,对创建、删除以及有.spec.hard更新resource quota,将其加入队列中。注意,这些变更事件是采用了list/watch机制从APIServer监听获得的,并且将缓存在rqIndexer里。其次,每隔一段时间(默认为5分钟),会进行一个full resync,此时所有的resource quota会全部被加入到队列中。

其次,每隔一段时间(默认为5分钟),会进行一个full resync,此时所有的resource quota会全部被加入到队列中。另外,对于其他支持的资源(pod、serivce、replication controller、persistent volume claim、secret、ConfigMap),分别设置了对应的replenishmentController,同样使用了list/watch机制监听资源,并对这些资源的更新或删除做出响应,即将这些资源所在的namespace下对其进行了规定的resource quota入队。通俗地讲,即当某个resource quota对pod的数量进行了规定时,那么当同一个namespace下的pod发生了更新或删除时,将该resource quota入队。

需要被同步的resource quota资源将被加入队列中后,将采用先入先出的方式进行处理,与其他的controller一样,负责处理的worker不止一个,而且它们是并发工作的。同步资源的处理函数可以归结为,使得resource quota的状态(status)与其期望值(spec)保持一致。如果出现了以下情况中的任意一种,都将调用APIServer的API对resource quota进行更新。❏ resource quota的.spec.hard与.status.hard不同。❏ resource quota的.status.hard或.status.used为空,意味着这是第一次进行同步工作。

其中,APIServer负责接收并处理用户的管理请求,controller manager负责各类控制器的定义和管理,scheduler则专门负责调度工作,三者之间分工明确且没有直接依赖。这也从侧面体现了Kubernetes的设计简洁之处,相比之下,大多数经典PaaS的控制节点就要复杂得多。

相关文章:

  • 9.synchronized的三把锁
  • 为什么开发人员正在成为供应链攻击中的最薄弱环节
  • MySQL之事务、锁
  • 项目第二天
  • Windows与网络基础-10-windows用户管理
  • 计算机网络笔记(王道考研) 第三章:数据链路层
  • apifox 提取cookie字段添加自动鉴权
  • ATF启动(一):整体启动流程
  • 25. Python 字符串的切片方法
  • 接口测试自动化脚本框架4
  • HadoopSpark
  • 51单片机4位抢答器_倒计时可调仿真设计
  • 设计模式之模板方法模式的理解
  • 小型功率放大器的设计与制作——功率放大器电路总结
  • 接口测试自动化脚本框架5
  • 【Leetcode】101. 对称二叉树
  • [NodeJS] 关于Buffer
  • css选择器
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • JavaScript实现分页效果
  • Java知识点总结(JavaIO-打印流)
  • js操作时间(持续更新)
  • Rancher如何对接Ceph-RBD块存储
  • Redux 中间件分析
  • vue 配置sass、scss全局变量
  • 初探 Vue 生命周期和钩子函数
  • 给第三方使用接口的 URL 签名实现
  • 聊一聊前端的监控
  • 码农张的Bug人生 - 初来乍到
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 微信小程序--------语音识别(前端自己也能玩)
  • 一些关于Rust在2019年的思考
  • ​​​​​​​​​​​​​​Γ函数
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • ###C语言程序设计-----C语言学习(3)#
  • #微信小程序:微信小程序常见的配置传值
  • (26)4.7 字符函数和字符串函数
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (ZT)出版业改革:该死的死,该生的生
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (第61天)多租户架构(CDB/PDB)
  • (十一)手动添加用户和文件的特殊权限
  • (未解决)macOS matplotlib 中文是方框
  • (新)网络工程师考点串讲与真题详解
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ***原理与防范
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • /bin/bash^M: bad interpreter: No such file or directory
  • @ComponentScan比较
  • @NestedConfigurationProperty 注解用法
  • @拔赤:Web前端开发十日谈
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?