源码分析:kubelet PLEG实现分析
什么是PLEG?
kublet 中的 PLEG 模块通过获取每一个 pod 级的事件来调整容器运行时状态,并实时更新 pod cache。
PLEG 在 kubelet 中的位置如下所示:

“PLEG is not healthy”是怎么发生的?
kubelet 在 SyncLoop() 函数中通过间歇性的调用 Healthy() 函数 来检查 PLEG 状态健康。
Healthy() 函数会检查 relist 过程是否在3分钟内完成,这个函数被作为 "PLEG" 添加到 runtimeState 并且会被 "SyncLoop" 间歇性(默认10s)的调用。如果 ”relist“ 过程超过3分钟,"PLEG is not healthy" 的信息会被上报到日志里。

Relist 过程
接下来我们看看 relist 过程的细节,你必须非常关注这个远程调用以及如何处理拉取到的数据,因为这部分很容易遇到瓶颈。

尽管 relist 被调用的间隔是1s,但是这个过程可能超过1s才完成,当容器运行时响应速度变慢或者这次循环过程中有很多容器发生改变。因为下一次 relist 会在前一次完成之后被调用,所以如果一次 relist 5s才完成,下一次 relist 时间就是6s之后。

relist 刚开始的会记录一些 metrics(例如 kubelet_pleg_relist_latency_microseconds)然后使用 CRI 接口从容器运行时获取所有 Pods 列表用于得到当前 Pods 状态,这个 Pods 列表会和前一次 Pods 列表对比来检查变化然后生成 pod 级别的状态改变事件。
GetPods 函数的详细调用栈如下:

当拿到 Pods 列表以后,前一次 relist 时间就会被更新为当前时间戳,换句话说 Healthy() 就会使用这个更新的时间戳计算了。
如前所述,当对比当前和前一次 Pods 列表数据,会为每一个 pod 生成前后两个时间点容器差异/更新信息的事件。
computeEvents() 函数会为每一个 pod 生成事件(例如:ContainerStarted,ContainerDied 等状态),然后时间会被 updateEvents() 函数更新。
computeEvents() 函数的调用堆栈如下所示:
最后一部分检查是否有与 pod 关联的事件并更新 podCache。
updateCache() 会检查每一个 pod 然后在一个循环里依次更新缓存,所以如果在这一个 relist 周期里大量 pod 发生变化,这个函数处理速度将会出现瓶颈。最后更新的 pod 生命周期事件发往 eventChannel 通道中。
跟踪函数调用栈对于理解这里的处理逻辑不是很直观,这里会通过 CRI 接口远程调用容器运行时接口以获取 pod 的详细信息,这会增加与 pod 数量成比例的延迟,因为 GetPodStatus() 函数获取 pod 详细信息的时候会调用多次 CRI 接口。
updateCache() 函数调用过程如下, GetPodStatus()会多次调用远程接口获取 pod 详情。


我们已经把 relist 过程相关的源码和调用栈看完了,相信大家应该对 PLEG 也有了一定的理解。
结论
以我的经验来看,"PLEG is not healthy" 可能会因为多种原因导致,并且很多潜在风险的情况我们也很难遇到。以下介绍几种常见的原因:
当执行远程调用时容器运行时延迟高或者超时(性能问题、死锁、bugs...)
节点上运行 Pod 数量过多或者在一个
relist周期内发生状态改变的 pod 数量过多。通过源码分析我们知道事件和延迟都是和 pod 数量成比例增长的。PLEG relist过程出现死锁问题在 Kubernetes 1.14被修复。
当获取 pod 网络状态时发生CNI bugs。
参考链接:
1、https://developers.redhat.com/blog/2019/11/13/pod-lifecycle-event-generator-understanding-the-pleg-is-not-healthy-issue-in-kubernetes#how_does__pleg_is_not_healthy__happen_
Last updated