kubernetes源码-kubelet 原理和源码分析(二)
kubelet 对 Pod 的同步流程,源码为 kubernetes 的 release-1.22 分支 .
写在前面
前面我们粗略地梳理了 kubelet 的启动流程后,对 kubelet 也有了大致的了解,这也给我们接下来要看那部分代码起到了指引的作用,我们按照它的启动顺序,先来看看 kubelet 是如何对 Pod 进行同步的。
在开始之前,我们先了解一个概念,静态 pod 和 镜像 Pod ,不然后面会一直看到 kubelet 检查 pod 的类型是否静态 pod 或 镜像 Pod ,如果不清楚这个概念,可能会一头雾水。
静态 Pod 不通过 master 节点上的 apiserver 操作及管理,直接由特定节点上的 kubelet 进程来管理的。
无法与我们常用的控制器 Deployment 或者 DaemonSet 等进行关联,它由 kubelet 进程自己来监控,当 pod 崩溃时重启该 pod ,kubelete 也无法对他们进行健康检查。
静态 pod 始终绑定在某个 kubelet ,并且始终运行在同一个节点上。
kubelet 会自动为每一个静态 pod 在 kubernetes 的 apiserver 上创建一个镜像 pod(mirrorPod),因此我们可以在 apiserver 中查询到该 pod,但是不能通过 apiserver 进行控制(例如不能删除)。
创建静态 pod 有两种方式:配置文件和 HTTP 两种方式。
syncLoopIteration
前面我们分析过, syncLoopIteration() 接口是 kubelet 的核心逻辑所在,所以这次 Pod 的操作流程,包括后面的诸如健康检测、 pleg 、同步管理、状态管理等,都是以这个接口作为出发点的。
configCh
前面我们也大致了解到 configCh 就是获取从 file/http/apiserver 这几个源过来的配置变更,并根据不同的变更类型,下发对应的回调处理函数进行对应的事件处理。
我们可以看到,这一块逻辑会对 Pod 的以下几种操作类型做出响应。
-
kubetypes.ADD
-
kubetypes.UPDATE
-
kubetypes.REMOVE
-
kubetypes.RECONCILE
-
kubetypes.DELETE
-
kubetypes.SET
|
|
Pod 的操作类型
除了 kubetypes.DELETE 删除操作之外,其他几个操作都会调用到 kl.dispatchWork() 接口来对 Pod 进行处理。
kubetypes.ADD
kubetypes.ADD 既是 Pod 的创建,它调用了 handler.HandlePodAdditions(u.Pods) 接口来响应 Pod 的创建操作,我们先看看 Pod 具体是怎么样被创建的。
-
HandlePodAdditions() 接口接收的是 pods 切片,意味着可能会出现多个 pod 同时创建。
-
pods 按创建时间排序,以此对排好序的 pods 进行循环遍历。
-
获取 kubelet 所在节点上现有的所有 pods 。
-
kl.podManager.AddPod(pod) 把当前 pod 信息缓存进 podManager 的 podByUID 这个 map 里面。
-
检查 pod 是否 MirrorPod ,如果是 MirrorPod ,则调用 kl.handleMirrorPod(pod, start) 对 Pod 进行处理 。
-
无论是 MirrorPod 还是正常 Pod ,最终都是调用 kl.dispatchWork() 接口进行处理,他们的区别在于传进去的参数的不同。
-
MirrorPod 是 静态 Pod 在 apiserver 上的一个镜像,kubelet 并不需要在节点上给他创建真实的容器,它的 ADD/UPDATE/DELETE 操作类型都被当做 UPDATE 来处理,更新它在 apiserver 上的状态。
-
这里比较迷惑的一点是,代码的最后,创建正常的 pod 要调用 kl.dispatchWork() 这个接口的时候,为什么还要去获取 mirrorPod 参数并传参进去,即使这个变量的结果可能是 nil ?这里看了一下,在后面的 syncPod() 接口里面会提到。
|
|
kubetypes.UPDATE
handler.HandlePodUpdates() ,先更新 pod 状态到缓存,检查 pod 是否镜像 pod ,之后跟 kubetypes.ADD 一样调用 kl.dispatchWork() 接口对 pod 进行同步,这里不再赘述。
|
|
kubetypes.REMOVE
handler.HandlePodRemoves() 接口,将 pod 状态从缓存中删除,检查 pod 是否镜像 pod ,之后调用 kl.deletePod() 将 pod 删除,这里的 kl.deletePod() 实际上是调用 podWorkers 的 kl.podWorkers.UpdatePod() 接口来完成删除的,跟调用 kl.dispatchWork() 其实相差无几。
-
kl.deletePod()
-
kl.podManager.DeletePod()
|
|
kubetypes.RECONCILE
handler.HandlePodReconcile() 分 3 步走,先将 pod 状态更新到缓存中,然后判断 pod 状态,如果是重新调谐的 pod ,则调用 kl.dispatchWork() 完成同步,如果是被驱逐的,则删掉 pod 中的所有容器。
-
kl.podManager.UpdatePod()
-
kl.dispatchWork()
-
kl.containerDeletor.deleteContainersInPod()
|
|
kubetypes.DELETE
直接调用 handler.HandlePodUpdates() 接口,流程跟 kubetypes.UPDATE 一样,这里不再赘述。
kl.dispatchWork()
-
dispatchWork() 调用 kl.podWorkers.UpdatePod() 接口进行处理。
-
监控指标记录 pod 的容器数量
|
|
kl.podWorkers.UpdatePod()
-
处理被标记为孤儿且处于运行状态的 pod ,把它们标记为即将删除,等后续处理。
-
把 pod 的状态缓存到 p.podSyncStatuses[uid] 这个 map 里面。
-
如果能从缓存中拿到该 pod 的缓存,而且 pod 处于终止状态,而这时候 options.UpdateType == kubetypes.SyncPodCreate ,则说明使用相同 uid 的 pod 需要被重启,会被后续的程序处理。
-
检查 pod 是否转换到终止状态。并根据相关状态组合 podWork{} 结构体。
-
p.managePodLoop() 给每个 pod 都创建一个 goroutine ,用于监听 pod 的更新动作并做出同步。
-
每次更新实际都是用 podWork{} 携带 pod 的相关信息发送到 podUpdates 通道,这个通道的另一端就是 p.managePodLoop() 在监听。
-
接下来,我们看看 p.managePodLoop() 的代码逻辑。
|
|
p.managePodLoop()
-
检查 pod 是否可以启动,并将 pod 标记为启动。
-
从缓存中根据 uid 获取 pod 的状态信息,并根据 update.WorkType 的不同调用不同的接口来同步 pod ,有以下接口。
-
p.syncPodFn() ,对新的 pod 进行同步。
-
p.syncTerminatedPodFn() ,对已退出状态的 pod 进行同步。
-
p.syncTerminatingPodFn() ,对正在退出状态的 pod 进行同步。
-
p.completeTerminated() ,当已退出状态的 pod 完成同步时调用。
-
-
根据同步类型的完成,不同的同步接口回调不同的完成接口。
-
p.completeTerminatingRuntimePod() ,当退出状态的孤儿 pod 完成同步时调用。
-
p.completeTerminating() ,当退出状态的 pod 完成同步时调用。
-
p.completeSync() ,当完成新 pod 的同步时调用。
-
p.completeWork() ,完成同步工作时调用。
-
-
下面我们先从不同的同步接口开始阅读,然后再看看对应的接口完成后的回调接口。
|
|
p.syncPodFn()
-
调用 generateAPIPodStatus() 接口准备 pod 相关状态信息,并把信息更新到缓存中。
-
调用 kl.canRunPod() 对 pod 进行软准入校验,检查扩展资源、节点是否处于正在关闭、security context 白名单、驱逐、拓扑等信息,如果检查不通过则终止掉 pod 。
-
确保对已经正在运行的 pod 进行后台追踪。
-
如果 pod 是静态 pod ,且 mirror pod 不存在,则创建 mirror pod 。
-
如果 pod 的相关数据目录不存在,则为 pod 创建相关数据目录。
-
等待 volumes 挂载/卸载,拉取 secrets 或 configmap ,获取镜像拉取密钥。
-
调用容器运行时的 kl.containerRuntime.SyncPod() 接口创建容器。
-
更新 pod 的 ingress 和 egress 流量限制。
-
如果流程中有任何一部步骤出错,则返回错误,等待下一次的 pod 同步。
|
|
p.syncTerminatedPodFn()
清除 pod ,且该 pod 已经没有任何处于运行状态的容器,dockershim 会后台回收 pod 的相关资源,期间还会持续接收日志,直到 pod 从节点上物理消失。
-
卸载存储卷。
-
注销 secrets 和 configmap 。
-
移除 cgroup 资源限制,回收资源。
-
标注 pod 的最终状态。
|
|
p.syncTerminatingPodFn()
-
调用 kl.killPod() 容器运行时终止该 pod 上的所有容器,该接口会在容器完全终止前阻塞住。
-
停止 iveness 和 startup 探针。
-
更新 pod 状态到缓存中,检查是否所有容器都已经终止。
-
如果收到错误返回,则会在下一次循环继续。
|
|
p.completeTerminated()
这一步是终止 podWorker ,关停 PodUpdates channel 通道,并将 pod 从缓存中移除。
|
|
p.completeTerminatingRuntimePod()
这一步主要是用来清理孤儿 pod 的,更新 pod 相关状态,并确保不被后面的同步逻辑同步,确保能被 kubelet 始别到它已经没有任何容器运行了。
|
|
p.completeTerminating()
这一意味着 pod 上的容器已经全部终止,且容器不会在后面的同步逻辑中继续启动了,更新 pod 相关状态,准备被后续的清理逻辑接管,并确保不被后面的同步逻辑同步,确保能被 kubelet 始别到它已经没有任何容器运行了。
|
|
p.completeSync()
这一步是处理在自然的生命周期内,pod 被终止了。类似驱逐、api驱使删除等非自然生命周期的处理,是走 UpdatePod() 这个接口的。
|
|
p.completeWork()
当 pod 同步出现错误时调用该接口,接口逻辑是检查错误类型,根据错误类型将 pod 立即或者按一定时间间隔重新加入到同步队列,直到 pod 完成同步。
|
|
调用关系图
各个接口函数之间不停穿插,很容易把人搞晕,为了加强理解,我们整理了一下接口之间的调用关系。
总结
-
根据接收到不同的同步类型信号,kubelet 分别对他们做出不同的响应。
-
核心的点在 podWorker ,它为每个 pod 都启动一个 goroutine 来同时对多个 pod 进行同步,直到 pod 生命周期的结束为止。
-
根据 podStatus 也扮演者重要角色,kubelet 根据 pod 的不同状态,对 pod 做出不同的响应。
-
我们这一次仅仅只是数量了 kubelet 对 pod 的同步流程,具体 pod 是怎么被创建的被我们一笔带过,下期我们重点来看看 pod 创建容器的流程。