kube-controller-manager

时间:Aug. 2, 2020 分类:

目录:

kube-controller-manager

kube-controller-manager是controller的合集

所有的controller的启动都是在Run方法中完成初始化和启动的

  1. 调用NewControllerInitializers初始化所有controller
  2. 调用StartControllers启动所有controller

node controller

在v1.16版本NodeController已经分成了NodeIpamController和NodeLifecycleController

NodeLifecycleController主要是监控node的状态并根据node的condition添加对应的taint或者驱逐node上的pod

NodeLifecycleController主要依赖taint

  • PreferNoSchedule 避免pod调度到节点,节点上的pod不会被驱逐
  • NoSchedule 不容忍pod调度到节点,节点上的pod不会被驱逐
  • NoExecute 不容忍pod调度到节点,节点上的pod会被驱逐

NodeLifecycleController使用了很多feature-gates

  • NodeDisruptionExclusion,在v1.16引入,默认为false,node.kubernetes.io/exclude-disruption,网络中断pod不会被驱逐
  • LegacyNodeRoleBehavior,在v1.16引入,默认为true,在创建loadbalancer和网络中断处理时,不会忽略有node-role.kubernetes.io/master的node
  • TaintBasedEvictions,在v1.13引入,默认为true,node为NotReady和Unreachable状态时为node添加对应的taint为NoExecute,会直接驱逐node上的pod
  • TaintNodesByCondition,在v1.12引入,默认为true,基于节点状态添加taint,节点处于NetworkUnavailable,MemoryPressure,PIDPressure和DiskPressure状态时会加NoSchedule
  • NodeLease,在v1.12引入,默认为true,减少node的心跳

startNodeLifecycleController启动方式

  1. 调用lifecyclecontroller.NewNodeLifecycleController对lifecycleController进行初始化,TaintBasedEvictions和TaintNodesByCondition两个feature-gates
  2. 调用lifecycleController.Run启动lifecycleController

启动参数

  • NodeMonitorPeriod,NodeController同步NodeStatus的周期,--node-monitor-period设置,默认为5s
  • NodeStartupGracePeriod,node启动完成标记节点unhealthy的允许无响应的时间,--node-startup-grace-period设置,默认为60s
  • NodeMonitorGracePeriod,node标记为unhealthy前,允许node无响应时间,--node-monitor-grace-period设置,默认为40s
  • PodEvictionTimeout,强制删除node上的pod时间,--pod-eviction-timeout设置,默认为5min
  • NodeEvictionRate,每秒剔除node的数量,--node-eviction-rate设置,默认为0.1
  • SecondaryNodeEvictionRate
  • LargeClusterSizeThreshold
  • UnhealthyZoneThreshold
  • EnableTaintManager
  • TaintBasedEvictions
  • TaintNodesByCondition

NewNodeLifecycleController的逻辑

  1. 初始化controller对象
  2. 为podInformer注册与taintManager相关的EventHandler
  3. 为nodeInformer注册与taintManager相关的EventHandler
  4. 检查是否启用NodeLease的feature-gates
  5. daemonSet默认不会注册对应的EventHandler

Run方法

  1. 等待Informer的cache同步完成
  2. 调用nc.taintManager.Run启动taintManager
  3. 异步调用nc.doNodeProcessingPassWorker处理nc.nodeUpdateQueue中的node
  4. 如果启用TaintBasedEvictions,异步调用nc.doNoExecuteTaintingPass处理nc.zoneNoExecuteTainter队列中的node,如果没有启用,调用nc.doEvictionPass处理nc.zonePodEvictor队列的node
  5. 异步调用nc.monitorNodeHealth定期监控node状态

nc.taintManager.Run

  1. 处理nodeUpdateQueue中的node并发往nodeUpdateChannels
  2. 处理podUpdateQueue中的pod并发往podUpdateChannels
  3. 调用tc.worker处理nodeUpdateChannels和podUpdateChannels的数据,使用tc.handleNodeUpdate和tc.handlePodUpdate

tc.handleNodeUpdate的主要逻辑

  1. 通过nodeLister获取node对象
  2. 获取node的effect为NoExecute的taint
  3. 调用tc.getPodsAssignedToNode获取node上的所有pods
  4. 如果node上的taint为空直接返回,遍历每个pod调用tc.processPodOnNode检查pod是否需要被驱逐

tc.handlePodUpdate的主要逻辑

  1. 通过podLister获取pod对象
  2. 获取pod所在的node的taint
  3. 调用tc.processPodOnNode进行处理

两者都用了tc.processPodOnNode来确定pod驱逐

  1. 检查pod的tolerations是否匹配node的taint,如果无法匹配就加入taintEvictionQueue删除
  2. 如果匹配获取tolerations中的最小容忍时间,如果未设置说明一直容忍,不匹配加入taintEvictionQueue延迟队列等到最小容忍时间再加入中驱逐

但是如果设置了tolerationSeconds就不会立刻驱逐

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

如果pod运行的时候,一个匹配的taint被添加到节点,pod还会运行3600秒,然后被驱逐,如果taint在这期间被删除了,pod就不会被删除

nc.doNodeProcessingPassWorker会处理nodeUpdateQueue中的node,添加合适的NoSchedule的label

如果启动了TaintBasedEvictions,会检测node异常的时候加入nc.zoneNoExecuteTainter队列,然后nc.doNoExecuteTaintingPass会处理队列的node,并且按照一定的速率驱逐

如果未启动TaintBasedEvictions,会检测node异常的时候加入nc.zonePodEvictor队列,然后nc.doEvictionPass会处理队列的node上的pod进行驱逐

deployment controller

启动的deployment controller

func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
    controllers := map[string]InitFunc{}
    ......
    controllers["deployment"] = startDeploymentController
    ......
}

startDeploymentController初始化的时候,传入的deployment,replicaset和pod

dc, err := deployment.NewDeploymentController(
        ctx.InformerFactory.Apps().V1().Deployments(),
        ctx.InformerFactory.Apps().V1().ReplicaSets(),
        ctx.InformerFactory.Core().V1().Pods(),
        ctx.ClientBuilder.ClientOrDie("deployment-controller"),
    )

然后执行异步执行dc.Run()

  1. 等待informer cache同步
  2. 异步通过调用wait.Until(dc.worker)创建多个worker执行syncHandler,即syncDeployment

syncDeployment的流程

  1. 调用getReplicaSetsForDeployment获取deployment和相关的Replicaset,如果匹配没有关联将rs的ownerReferences设置为deployment,关联但是不匹配的删除ownerReferences
  2. 调用getPodMapForDeployment获取deployment关联的pod,并根据rs.UID进行分类
  3. 判断deployment的DeletionTimestamp是否是需要删除
  4. 调用checkPausedConditions判断deployment是否为pause状态,并添加合适的condition
  5. 调用getRollbackTo判断deployment是否有Annotations:"deprecated.deployment.rollback.to"字段,如果有调用dc.rollback回滚
  6. 调用isScalingEvent检查是否处于scaling状态
  7. 检查是否为更新操作,如果是根据更新方式执行Recreate或RollingUpdate

对于deployment的操作的优先级就是delete > pause > rollback > scale > rollout

  • 对于delete,调用dc.syncStatusOnly获取新旧的rs,调用dc.syncStatusOnly同步更新deployment的状态,删除不会直接删除对象,而是garbagecollector完成
  • 对于pause,根据新旧rs,判断是否需要scale,是否处于暂停状态且没有回滚则更新spec.revisionHistoryLimit清理多余的rs,最后更新status
  • 对于rollback,调用getRollbackTo获取,判断revision对应的rs是否存在,存在调用rollbackToTemplate讲rs.Spec.Template赋值给d.Spec.Template
  • 对于scale,获取activeRS中desired和deployment.Spec.Replicas不相等的ds,调用scale进行扩容
  • 对于rollout,调用getAllReplicaSetsAndSyncRevision获取rs,如果没有newRS则进行创建,调用reconcileNewReplicaSet和reconcileOldReplicaSets是否要对rs进行操作,如果都不是检测deployment是否达到期望状态

对于滚动更新,还有更粗暴的操作,删除旧rs的pod,等pod数变为0,然后创建新的rs

replicaset controller

deployment控制是replicaset,replicaset控制的pod创建和删除

replicaset controller启动流程中,重要的参数BurstReplicas(一个syncloop的过程rs可以创建最多的pod数默认为500)和ConcurrentRSSyncs(启动多少协程去处理informer)

  1. 调用NewBaseController初始化ReplicaSetController
  2. 在rsInformer注册EventHandler
  3. 在podInformer注册EventHandler
  4. 通过Run方法等待informer的cache的同步
  5. 启动协程执行rsc.syncHandler

expectations机制

rs本身是有informer缓存和expectations缓存,expectations会记录所有rs需要add和del的pod数量

  • 如果两者都为0,说明期望的创建和删除的pod满足
  • 如果有不为0的,说明在某次syncLoop中创建或删除pod失败,需要等待expectations过期再同步

而并不是所有的rs事件都会触发rs的sync操作,只有rs.Spec.Replicas的操作需要更新,spec的其他字段和status字段更新不需要创建和删除pod,expectations的作用是减少sync操作

在执行sync操作之前,也是需要通过expectations机制验证,验证的方式

  1. 当expectations中不存在rsKey时,说明首次创建rs
  2. 当expectations中del和add 值都为0时,说明rs所需要创建或者删除的pod数都已满足
  3. 当expectations过期时,即超过5分钟未进行sync操作

当触发addPod的时候

  1. 判断pod是否为删除状态
  2. 获取pod关联的rs,如果pod与rs关联,并更新expectations的rsKey-1,将rs加入队列
  3. 如果pod没有与rs关联,遍历rsList中rs.Namespace和pod.Namespace相等且rs.Spec.Selector匹配pod.Labels的将pod与rs关联,将匹配的rs加入队列

当触发updataPod的时候

  1. 判断pod是否为删除状态
  2. 如果pod的OwnerReference改变,oldRS需要创建pod,将oldRs加入队列
  3. 获取pod关联的rs,加入rs,如果pod为ready且非available状态,则会放入延迟队列,因为ready到available还会触发状态更新
  4. 如果没有关联rs,尝试查找rs,如果有就将rs加入队列

当触发delPod的时候

  1. 判断pod是否为删除状态
  2. 获取pod关联的rs,如果pod与rs关联,更新expectations的rsKey-1,将rs加入队列

当触发addRS和deleteRS的时候

  1. 直接将rs加入队列

当触发updateRS的时候

  1. 打印日志,将rs加入队列

然后通过syncReplicaSet驱动rs达到目的状态

  1. 根据ns获取rs对象
  2. 调用expectations.SatisfiedExpectations判断是否需要同步
  3. 获取pod的list
  4. 根据pod的label获取属于rs的pod,如果孤儿pod也会进行关联
  5. 调用manageReplicas同步pod操作
  6. 调用calculateStatus计算并更新rs的当前状态
  7. 如果rs设置了MinReadySeconds则放入延迟队列

SatisfiedExpectations判断expectations的rsKey存在,且不满足期望状态,并且在同步周期(5min),或者为新的rs

manageReplicas用于计算Replicaset需要创建或删除多少个pod并调用apiserver创建

  1. 计算已存在pod数与期望pod数的差异
  2. diff<0说明需要创建,将pod数记录到expectations,调用slowStartBatch以指数方式创建pod
  3. diff>0说明需要删除,如果需要删除所有pod就进行删除,如果不需要删除所有pod就通过getPodsToDelete筛选需要删除的pod,进行删除,数量记录到expectations

slowStartBatch如果超时创建失败会进行忽略,如果真的创建失败expectations超时后,下一个syncLoop会重新创建,如果创建成功后informer中watch到会更新expectations

getPodsToDelete的筛选方式是

  1. 判断是否绑定node:Unassigned < assigned
  2. 判断pod phase:PodPending < PodUnknown < PodRunning
  3. 判断pod状态:Not ready < ready
  4. 根据pod运行时间排序,运行时间最短会被删除:empty time < less time < more time
  5. 根据pod重启次数排序:higher restart counts < lower restart counts
  6. 根据pod创建时间进行排序:Empty creation time pods < newer pods < older pods

calculateStatus计算的字段

status:
  availableReplicas: 10
  fullyLabeledReplicas: 10
  observedGeneration: 1
  readyReplicas: 10
  replicas: 10

daemonset controller

daemonSet和statefulSet都是靠controllerRevision保留历史版本信息,删除支持级联删除和非级联删除

$ kubectl get controllerrevision

startDaemonSetController方法用于启动

  1. 调用daemon.NewDaemonSetsController初始化daemonSetController
  2. 调用dsc.Run启动
dsc, err := daemon.NewDaemonSetsController(
        ctx.InformerFactory.Apps().V1().DaemonSets(),
        ctx.InformerFactory.Apps().V1().ControllerRevisions(),
        ctx.InformerFactory.Core().V1().Pods(),
        ctx.InformerFactory.Core().V1().Nodes(),
        ctx.ClientBuilder.ClientOrDie("daemon-set-controller"),
        flowcontrol.NewBackOff(1*time.Second, 15*time.Minute),
    )

Run方法会调用两个方法

  1. 等待informer的cache同步
  2. 调用dsc.runWorker执行syncDaemonSet操作
  3. 调用dsc.failedPodsBackoff.GC完成gc操作,主要是检查daemon的pod为failed状态进行重新启动

syncDaemonSet操作的流程

  1. 通过key获取ns和name
  2. 从dsList获取ds
  3. 从nodeLister获取node列表
  4. 获取dsKey
  5. 判断ds是否为删除状态
  6. 调用constructHistory获取current和oldControllerRevision
  7. 调用dsc.expectations.SatisfiedExpectations是否满足expectations
  8. 调用dsc.manage执行同步操作
  9. 判断是否为更新操作,如果是apps.RollingUpdateDaemonSetStrategyType进行更新
  10. 调用dsc.cleanupHistory根据spec.revisionHistoryLimit清理过期的controllerrevision
  11. 调用dsc.updateDaemonSetStatus更新ds状态

manage用于保证ds的pod正常运行在匹配的node

  1. 调用dsc.getNodesToDaemonPods获取daemon和node映射关系
  2. 遍历node,调用dsc.podsShouldBeOnNode确定node上需要创建和删除pod
  3. 判断是否启动了feature-gates特性,如果启动就删除通过默认调度器创建到已删除node的pod
  4. 调用dsc.syncNodes为node创建或删除pod

podsShouldBeOnNode确认逻辑

  1. 调用dsc.nodeShouldRunDaemonPod判断node是否需要运行pod和pod是否调度成功
  2. 生成需要创建pod的node列表和删除的pod列表

nodeShouldRunDaemonPod的逻辑

  1. 调用NewPod为node构建daemon pod object
  2. 判断pod是否执行.Spec.Template.Spec.NodeName字段
  3. 调用dsc.simulate执行GeneralPredicates预选算法检查pod是否能调度成功

syncNodes为node创建和删除pod

  1. 比较createDiff,deleteDiff和burstReplicas,burstReplicas默认是250,一个syncLoop只支持250个
  2. 将createDiff和createDiff写入expectations
  3. 并发创建createDiff中的pod,如果pod直接指定了pod.Spec.NodeName而不使用调度器,如果开启了feature-gates,使用nodeAffinity保证每个节点只运行一个pod
  4. 并发删除deleteDiff中的pod

升级的方式有OnDelete和RollingUpdate两种

RollingUpdate的主要逻辑是

  1. 获取daemon pod和node的关系
  2. 根据controllerrevision的hash获取未更新的pod
  3. 获取rollingUpdate的maxUnavailable(默认为1), numUnavailable
  4. 通过oldPods获取oldAvailablePods和oldUnavailablePods的pod列表
  5. 将oldUnavailablePods中的pod加入oldPodsToDelete
  6. 将oldAvailablePods的pod遍历,根据maxUnavailable确认是否需要删除,需要删除追加到oldPodsToDelete
  7. 调用dsc.syncNodes删除oldPodsToDelete中的pod

被删除的pod在下一个syncLoop会进行创建

status字段

status:
  currentNumberScheduled: 1  // 已经运行了 DaemonSet Pod的节点数量
  desiredNumberScheduled: 1  // 需要运行该DaemonSet Pod的节点数量
  numberMisscheduled: 0      // 不需要运行 DeamonSet Pod 但是已经运行了的节点数量
  numberReady: 0             // DaemonSet Pod状态为Ready的节点数量
  numberAvailable: 1          // DaemonSet Pod状态为Ready且运行时间超过                                                                                                      // Spec.MinReadySeconds 的节点数量
  numberUnavailable: 0             // desiredNumberScheduled - numberAvailable 的节点数量
  observedGeneration: 3
  updatedNumberScheduled: 1  // 已经完成DaemonSet Pod更新的节点数量

garbage controller

controller删除数据不会直接删除,而是由garbage controller根据删除策略进行删除

kubernetes的删除机制有三种

  • Orphan 非级联删除,删除对象,子对象和依赖的对象依然存在
  • Foreground 删除对象会先将deletionTimestamp设置,并且metadata.finalizers为foregroundDeletion,垃圾回收的时候会删除对象依赖的对象,最后删除对象本身
  • Background 直接删除对象,由垃圾回收器回收依赖的对象

在v1.9之后,v1/apps的资源都使用的Background模式

finalizer机制会在删除对象设置hook,让对象再删除前确认子对象是否已经完全被删除,有两种finalizer

  • OrphanFinalizer
  • ForegroundFinalizer

当对象的ObjectMeta有finalizer,apiserver是不会删除这个对象的,只有当一个对象的依赖对象被删除,这个对象的finalizer才会消失,被apiserver删除

也可以通过删除时指定删除方式

$ curl -k -v -XDELETE  -H "Accept: application/json" -H "Content-Type: application/json" -d '{"propagationPolicy":"Foreground"}' 'https://192.168.1.101:8443/apis/apps/v1/namespaces/default/daemonsets/nginx-ds'

GarbageCollectorController通过startGarbageCollectorController启动

  1. 初始化discoveryClient获取集群中的所有资源
  2. 调用garbagecollector.GetDeletableResources获取所有可删除资源对象,即支持delete,list和watch的操作的resource
  3. 调用garbagecollector.NewGarbageCollector初始化garbageCollector对象
  4. 调用garbageCollector.Run启动garbageCollector
  5. 调用garbageCollector.Sync监听集群中的DeletableResources
  6. 调用garbagecollector.NewDebugHandler注册debug的handler

garbagecollector.NewGarbageCollector主要功能

  1. 初始化GarbageCollector和GraphBuilde
  2. 调用gb.syncMonitors方法初始化deletableResources对应的controller的 informer

gb.syncMonitors

  1. 调用gb.controllerFor初始化资源的eventHandler
  2. add,update和delete的方法会将对应的event传到graphChanges队列

garbageCollector.Run

  1. 调用gc.dependencyGraphBuilder.Run启动informer
  2. 等待informers的cache同步完成
  3. 异步调用gc.runAttemptToDeleteWorker和gc.runAttemptToOrphanWorker处理attemptToDelete事件

gc.dependencyGraphBuilder

  1. 监控集群中可删除的资源
  2. 基于informer中资源在uidToNode数据结构中维护所有对象的依赖关系
  3. 调用gb.runProcessGraphChanges处理graphChanges中的事件并放到attemptToDelete和attemptToOrphan队列中

gb.runProcessGraphChanges

  1. 从graphChanges队列中获取item(event)
  2. 获取event的accessor
  3. 通过获取uid,判断uidToNode是否存在
  4. 如果不存在就是addEvent或updateEvent,为其创建node,并将node加入到uidToNode和node的owner的dependents,然后判断是否处于删除状态,如果在删除状态根据删除模式,如果是orphan模式,加入attemptToOrphan队列,如果是foreground模式,将该对象和dependents加入到attemptToDelete
  5. 如果存在且事件为addEvent或updateEvent,大概率为update操作,如果有被删除的owner,将node从owner的dependents删除
  6. 如果存在为deleteEvent,从uidToNode删除对象,然后获取node的owner的dependents删除对象,将node的dependents加入attemptToDelete队列,最后检查node的所有owner,如果owner有处于删除阻塞状态等待node的删除,并将owner加入到attemptToDelete

这里uidToNode

  • pod的owner是rs
  • rs的owner是deployment,dependents是关联的所有pod
  • deployment的dependents是关联的所有rs

gc.runAttemptToDeleteWorker直接调用gc.attemptToDeleteItem删除node,如果删除失败就放入attemptToDelete队列重试

gc.runAttemptToOrphanWorker则是处理orphan模式删除node

  1. 调用gc.orphanDependents删除owner所有dependents中的owner字段
  2. 调用gc.removeFinalizer删除owner的orphanFinalizer
  3. 如果有失败重试