kube-controller-manager
目录:
kube-controller-manager
kube-controller-manager是controller的合集
所有的controller的启动都是在Run方法中完成初始化和启动的
- 调用NewControllerInitializers初始化所有controller
- 调用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启动方式
- 调用lifecyclecontroller.NewNodeLifecycleController对lifecycleController进行初始化,TaintBasedEvictions和TaintNodesByCondition两个feature-gates
- 调用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的逻辑
- 初始化controller对象
- 为podInformer注册与taintManager相关的EventHandler
- 为nodeInformer注册与taintManager相关的EventHandler
- 检查是否启用NodeLease的feature-gates
- daemonSet默认不会注册对应的EventHandler
Run方法
- 等待Informer的cache同步完成
- 调用nc.taintManager.Run启动taintManager
- 异步调用nc.doNodeProcessingPassWorker处理nc.nodeUpdateQueue中的node
- 如果启用TaintBasedEvictions,异步调用nc.doNoExecuteTaintingPass处理nc.zoneNoExecuteTainter队列中的node,如果没有启用,调用nc.doEvictionPass处理nc.zonePodEvictor队列的node
- 异步调用nc.monitorNodeHealth定期监控node状态
nc.taintManager.Run
- 处理nodeUpdateQueue中的node并发往nodeUpdateChannels
- 处理podUpdateQueue中的pod并发往podUpdateChannels
- 调用tc.worker处理nodeUpdateChannels和podUpdateChannels的数据,使用tc.handleNodeUpdate和tc.handlePodUpdate
tc.handleNodeUpdate的主要逻辑
- 通过nodeLister获取node对象
- 获取node的effect为NoExecute的taint
- 调用tc.getPodsAssignedToNode获取node上的所有pods
- 如果node上的taint为空直接返回,遍历每个pod调用tc.processPodOnNode检查pod是否需要被驱逐
tc.handlePodUpdate的主要逻辑
- 通过podLister获取pod对象
- 获取pod所在的node的taint
- 调用tc.processPodOnNode进行处理
两者都用了tc.processPodOnNode来确定pod驱逐
- 检查pod的tolerations是否匹配node的taint,如果无法匹配就加入taintEvictionQueue删除
- 如果匹配获取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()
- 等待informer cache同步
- 异步通过调用wait.Until(dc.worker)创建多个worker执行syncHandler,即syncDeployment
syncDeployment的流程
- 调用getReplicaSetsForDeployment获取deployment和相关的Replicaset,如果匹配没有关联将rs的ownerReferences设置为deployment,关联但是不匹配的删除ownerReferences
- 调用getPodMapForDeployment获取deployment关联的pod,并根据rs.UID进行分类
- 判断deployment的DeletionTimestamp是否是需要删除
- 调用checkPausedConditions判断deployment是否为pause状态,并添加合适的condition
- 调用getRollbackTo判断deployment是否有
Annotations:"deprecated.deployment.rollback.to"
字段,如果有调用dc.rollback回滚 - 调用isScalingEvent检查是否处于scaling状态
- 检查是否为更新操作,如果是根据更新方式执行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)
- 调用NewBaseController初始化ReplicaSetController
- 在rsInformer注册EventHandler
- 在podInformer注册EventHandler
- 通过Run方法等待informer的cache的同步
- 启动协程执行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机制验证,验证的方式
- 当expectations中不存在rsKey时,说明首次创建rs
- 当expectations中del和add 值都为0时,说明rs所需要创建或者删除的pod数都已满足
- 当expectations过期时,即超过5分钟未进行sync操作
当触发addPod的时候
- 判断pod是否为删除状态
- 获取pod关联的rs,如果pod与rs关联,并更新expectations的rsKey-1,将rs加入队列
- 如果pod没有与rs关联,遍历rsList中rs.Namespace和pod.Namespace相等且rs.Spec.Selector匹配pod.Labels的将pod与rs关联,将匹配的rs加入队列
当触发updataPod的时候
- 判断pod是否为删除状态
- 如果pod的OwnerReference改变,oldRS需要创建pod,将oldRs加入队列
- 获取pod关联的rs,加入rs,如果pod为ready且非available状态,则会放入延迟队列,因为ready到available还会触发状态更新
- 如果没有关联rs,尝试查找rs,如果有就将rs加入队列
当触发delPod的时候
- 判断pod是否为删除状态
- 获取pod关联的rs,如果pod与rs关联,更新expectations的rsKey-1,将rs加入队列
当触发addRS和deleteRS的时候
- 直接将rs加入队列
当触发updateRS的时候
- 打印日志,将rs加入队列
然后通过syncReplicaSet驱动rs达到目的状态
- 根据ns获取rs对象
- 调用expectations.SatisfiedExpectations判断是否需要同步
- 获取pod的list
- 根据pod的label获取属于rs的pod,如果孤儿pod也会进行关联
- 调用manageReplicas同步pod操作
- 调用calculateStatus计算并更新rs的当前状态
- 如果rs设置了MinReadySeconds则放入延迟队列
SatisfiedExpectations判断expectations的rsKey存在,且不满足期望状态,并且在同步周期(5min),或者为新的rs
manageReplicas用于计算Replicaset需要创建或删除多少个pod并调用apiserver创建
- 计算已存在pod数与期望pod数的差异
- diff<0说明需要创建,将pod数记录到expectations,调用slowStartBatch以指数方式创建pod
- diff>0说明需要删除,如果需要删除所有pod就进行删除,如果不需要删除所有pod就通过getPodsToDelete筛选需要删除的pod,进行删除,数量记录到expectations
slowStartBatch如果超时创建失败会进行忽略,如果真的创建失败expectations超时后,下一个syncLoop会重新创建,如果创建成功后informer中watch到会更新expectations
getPodsToDelete的筛选方式是
- 判断是否绑定node:Unassigned < assigned
- 判断pod phase:PodPending < PodUnknown < PodRunning
- 判断pod状态:Not ready < ready
- 根据pod运行时间排序,运行时间最短会被删除:empty time < less time < more time
- 根据pod重启次数排序:higher restart counts < lower restart counts
- 根据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方法用于启动
- 调用daemon.NewDaemonSetsController初始化daemonSetController
- 调用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方法会调用两个方法
- 等待informer的cache同步
- 调用dsc.runWorker执行syncDaemonSet操作
- 调用dsc.failedPodsBackoff.GC完成gc操作,主要是检查daemon的pod为failed状态进行重新启动
syncDaemonSet操作的流程
- 通过key获取ns和name
- 从dsList获取ds
- 从nodeLister获取node列表
- 获取dsKey
- 判断ds是否为删除状态
- 调用constructHistory获取current和oldControllerRevision
- 调用dsc.expectations.SatisfiedExpectations是否满足expectations
- 调用dsc.manage执行同步操作
- 判断是否为更新操作,如果是apps.RollingUpdateDaemonSetStrategyType进行更新
- 调用dsc.cleanupHistory根据spec.revisionHistoryLimit清理过期的controllerrevision
- 调用dsc.updateDaemonSetStatus更新ds状态
manage用于保证ds的pod正常运行在匹配的node
- 调用dsc.getNodesToDaemonPods获取daemon和node映射关系
- 遍历node,调用dsc.podsShouldBeOnNode确定node上需要创建和删除pod
- 判断是否启动了feature-gates特性,如果启动就删除通过默认调度器创建到已删除node的pod
- 调用dsc.syncNodes为node创建或删除pod
podsShouldBeOnNode确认逻辑
- 调用dsc.nodeShouldRunDaemonPod判断node是否需要运行pod和pod是否调度成功
- 生成需要创建pod的node列表和删除的pod列表
nodeShouldRunDaemonPod的逻辑
- 调用NewPod为node构建daemon pod object
- 判断pod是否执行.Spec.Template.Spec.NodeName字段
- 调用dsc.simulate执行GeneralPredicates预选算法检查pod是否能调度成功
syncNodes为node创建和删除pod
- 比较createDiff,deleteDiff和burstReplicas,burstReplicas默认是250,一个syncLoop只支持250个
- 将createDiff和createDiff写入expectations
- 并发创建createDiff中的pod,如果pod直接指定了pod.Spec.NodeName而不使用调度器,如果开启了feature-gates,使用nodeAffinity保证每个节点只运行一个pod
- 并发删除deleteDiff中的pod
升级的方式有OnDelete和RollingUpdate两种
RollingUpdate的主要逻辑是
- 获取daemon pod和node的关系
- 根据controllerrevision的hash获取未更新的pod
- 获取rollingUpdate的maxUnavailable(默认为1), numUnavailable
- 通过oldPods获取oldAvailablePods和oldUnavailablePods的pod列表
- 将oldUnavailablePods中的pod加入oldPodsToDelete
- 将oldAvailablePods的pod遍历,根据maxUnavailable确认是否需要删除,需要删除追加到oldPodsToDelete
- 调用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启动
- 初始化discoveryClient获取集群中的所有资源
- 调用garbagecollector.GetDeletableResources获取所有可删除资源对象,即支持delete,list和watch的操作的resource
- 调用garbagecollector.NewGarbageCollector初始化garbageCollector对象
- 调用garbageCollector.Run启动garbageCollector
- 调用garbageCollector.Sync监听集群中的DeletableResources
- 调用garbagecollector.NewDebugHandler注册debug的handler
garbagecollector.NewGarbageCollector主要功能
- 初始化GarbageCollector和GraphBuilde
- 调用gb.syncMonitors方法初始化deletableResources对应的controller的 informer
gb.syncMonitors
- 调用gb.controllerFor初始化资源的eventHandler
- add,update和delete的方法会将对应的event传到graphChanges队列
garbageCollector.Run
- 调用gc.dependencyGraphBuilder.Run启动informer
- 等待informers的cache同步完成
- 异步调用gc.runAttemptToDeleteWorker和gc.runAttemptToOrphanWorker处理attemptToDelete事件
gc.dependencyGraphBuilder
- 监控集群中可删除的资源
- 基于informer中资源在uidToNode数据结构中维护所有对象的依赖关系
- 调用gb.runProcessGraphChanges处理graphChanges中的事件并放到attemptToDelete和attemptToOrphan队列中
gb.runProcessGraphChanges
- 从graphChanges队列中获取item(event)
- 获取event的accessor
- 通过获取uid,判断uidToNode是否存在
- 如果不存在就是addEvent或updateEvent,为其创建node,并将node加入到uidToNode和node的owner的dependents,然后判断是否处于删除状态,如果在删除状态根据删除模式,如果是orphan模式,加入attemptToOrphan队列,如果是foreground模式,将该对象和dependents加入到attemptToDelete
- 如果存在且事件为addEvent或updateEvent,大概率为update操作,如果有被删除的owner,将node从owner的dependents删除
- 如果存在为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
- 调用gc.orphanDependents删除owner所有dependents中的owner字段
- 调用gc.removeFinalizer删除owner的orphanFinalizer
- 如果有失败重试