Kubelet PLEG 源码解析
PLEG (pod lifecycle event generator) 是 kubelet 中一个非常重要的模块,它主要完成以下几个目标:
- 从 runtime 中获取 pod 当前状态,产生 pod lifecycle events
- 从 runtime 中获取 pod 当前状态,更新 kubelet pod cache
本文我们通过分析 PLEG 模块的源码,来加深对 Kubernetes 的理解,也可以加速在使用过程对一些疑难问题的排查和处理,同时后期可以对一些问题源码进行优化,来解决一些 Kubernetes 本身的坑。
PLEG 初始化
PLEG 模块在 kubelet 实例创建时初始化,在 pkg/kubelet/kubelet.go 文件中:
1 | func NewMainKubelet(...) (*Kubelet, error) { |
我们简单看看 NewGenericPLEG 的实现,见 pkg/kubelet/pleg/generic.go:
1 | // NewGenericPLEG instantiates a new GenericPLEG object and return it. |
NewGenericPLEG 函数有几个重要的参数:
runtime实参为
klet.containerRuntime,负责容器运行时的管理,对 pod 或 container 状态的获取、同步和删除都通过runtime来操作。channelCapacity实参为
plegChannelCapacity,是eventChannel有缓冲 channel 的大小,默认值1000,也就是单节点最大支持 1000 个 pod lifecycle event 同时触发。relistPeriod实参为
plegRelistPeriod,是 PLEG 检测的周期,默认值1s。cache实参为
klet.podCache,保存着所有 pod 状态的缓存,kubelet 通过 container runtime 更新 pod 缓存。
plegChannelCapacity 和 plegRelistPeriod 这两个常量的定义在 pkg/kubelet/kubelet.go 文件里:
1 | const ( |
PLEG 接口定义
NewGenericPLEG 返回的类型 *GenericPLEG 实现了 PodLifecycleEventGenerator 接口,我们暂且忽略 GenericPLEG 结构体的具体实现,先分析一下 PodLifecycleEventGenerator 接口,这个接口在 pkg/kubelet/pleg/pleg.go 文件中定义,包含三个方法:
1 | // PodLifecycleEventGenerator contains functions for generating pod life cycle events. |
Start启动 PLEG。Watch返回一个 channel,pod lifecycle events 会发送到这个 channel 里,kubelet 通过这个 channel 来获取事件,执行处理动作。Healty返回 PLEG 的健康状态。kubelet 通过这个函数判断 PLEG 是否健康。
我们再看看 pod lifecycle event 的定义,见 pkg/kubelet/pleg/pleg.go 文件:
1 | // PodLifeCycleEventType define the event type of pod life cycle events. |
PodLifecycleEvent 结构保存着以下信息:
ID: pod IDType: 事件类型PodLifecycleEventType有以下几种:ContainerStarted: 容器状态变为RunningContainerDied: 容器状态变为ExitedContainerRemoved: 容器消失PodSync: PLEG 中未使用ContainerChanged: 容器状态变为Unknown
Data: 容器 ID(源码注释是 container name,应该是错误)
PLEG 接口调用
下面我们看看 kubelet 是在哪里使用 PodLifecycleEventGenerator 接口里的三个方法的。
启动
kubelet 在 Run 函数中执行 Start,启动 PLEG。
1 | // Run starts the kubelet reacting to config updates |
事件处理
最后在 syncLoop 中执行 Watch,获取到这个关键的 channel plegCh,然后在 syncLoopIteration 函数中从 channel 中获取事件,进行处理。
1 | // syncLoop is the main loop for processing changes. It watches for changes from |
syncLoopIteration 是 kubelet 事件处理的核心函数,它的职责是从多个不同类型的 channel 中获取事件,然后分发给不同的 handler 去处理。
1 | // syncLoopIteration reads from various channels and dispatches pods to the |
configCh负责获取 pod 配置更新事件。syncCh是一个定时器,定时获取 pod sync 事件,对需要的 pod 进行同步,默认是1s。housekeepingCh也是一个定时器,定时获取 pod Cleanup 事件,对需要的 pod 进行清理,默认值是2splegCh负责获取 pod lifecycle 事件livenessManager.Updates负责获取 liveness probe 事件handler是个事件处理接口 (SyncHandler),获取到上面的时间后调用对应的事件处理方法,kubelet 主类本身默认实现了这个接口。
在这里我们只关心对 pod lifecycle 事件的处理:从代码上看,kubelet 收到 pod lifecycle 事件之后,首先判断事件类型是不是值得触发 pod 同步,如果是 ContainerRemoved,则忽略该事件。如果是其他事件,且 pod 信息还没有被删除,调用 HandlePodSyncs 产生 UpdatePod 事件,交给 kubelet pod Worker 进行异步更新。最后,如果是 ContainerDied 事件,为了防止退出容器堆积,会按照一定的策略移除已退出的容器。
健康检测
kubelet 对 PLEG 模块的健康检测,通过 runtimeState 来管理,kubelet 在初始化 PLEG 后通过 addHealthCheck 将 klet.pleg.Healthy 健康监测方法注册至 runtimeState,runtimeState 定时调用 Healthy 方法检查 PLEG 的健康状态。参见 pkg/kubelet/kubelet.go:
1 | func NewMainKubelet(...) (*Kubelet, error) { |
addHealthCheck 实现在 pkg/kubelet/runtime.go 中:
1 | func (s *runtimeState) addHealthCheck(name string, f healthCheckFnType) { |
然后在 syncLoop 中定时执行 runtimeErrors,这里 syncLoop 采用了简单的 backoff 机制,如果 runtimeState 各个模块状态都正常,则每次循环默认 sleep 100ms,如果出现异常状态,则 sleep duration * 2,最大变为 5s,参见 pkg/kubelet/kubelet.go:
1 | func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) { |
runtimeErrors 实现在 pkg/kubelet/runtime.go 中:
1 | func (s *runtimeState) runtimeErrors() error { |
这里也是依次执行各个模块事先注册的 healthy check 函数,如果任何一个模块返回 false,则认为整个 runtimeState 的状态为 unhealthy。
Generic PLEG 实现
我们再回到 PodLifecycleEventGenerator 接口的实现 —— GenericPLEG 的定义,见 pkg/kubelet/pleg/generic.go 文件:
1 | type GenericPLEG struct { |
relistPeriod是 PLEG 检测周期,默认为1sruntime是 container runtime,负责获取 pod 和 container 的状态信息podRecords缓存 pod 以及 Container 的基本信息cache缓存 pod 的运行时状态eventChannel是 PLEG 通过对比 pod 缓存信息和当前信息,生成 pod lifecycle events 的 channelrelistTime是上一次执行完 PLEG 检测的时刻podsToReinspect保存 PLEG 检测失败的 Pod,以便下次再次检测clock是一个时间管理对象,作用是获取当前时间
然后我们基于接口方法,来分析 GenericPLEG 的实现:
1 | // Start spawns a goroutine to relist periodically. |
Start 启动了一个 goroutine,以 1s 的间隔无限执行 relist 函数。这里要注意 wait.Until 的行为,如果 relist 执行时间大于 period 设置的值,则时间窗会滑动至 relist 执行完毕的那一时刻。也就是说如果 period 是 1s,relist 从第 0s 开始,花了 10s,结束时是第 10s,那么下一次 relist 会从第 11s 开始执行。
relist 函数的实现如下:
1 | // relist queries the container runtime for list of pods/containers, compare |
relist 中 export 了两个监控指标:relist_interval 和 relist_latency,它们俩的关系是:
1 | relist_interval = relist_latency + relist_period |
整个 relist 的流程大致为:
- 从 container runtime 获取所有 Pod,更新至 podRecords 的 current state
- 遍历 podRecords,对比 current state 和 old state,产生 lifecycle events 并按照 pod 分组
- 遍历 pod 和 对应的 events,从 container runtime 获取 pod status 更新 cache(记录失败的 Pod,准备下次重试),并将 PLEG event (除了 ContainerChanged 事件)放入 eventChannel
- 遍历上次 relist 更新 cache 失败的 Pod,尝试再次获取 pod status 更新 cache
relist 函数通过访问 container runtime 将 pod 和 container 的实际状态更新至 kubelet 的 pod cache。其他模块 (pod worker) 使用的 pod cache,都由 PLEG 模块更新。
pod lifecycle event 的生成通过 generateEvents 函数比较 old state 和 new state 来实现:
1 | func generateEvents(podID types.UID, cid string, oldState, newState plegContainerState) []*PodLifecycleEvent { |
顺便看看 Container Runtime 接口,对于 Container Runtime,我们主要关注 PLEG 用到的两个方法 GetPods 和 GetPodStatus,参照 pkg/kubelet/container/runtime.go 文件:
1 | // Runtime interface defines the interfaces that should be implemented |
GetPods 主要是获取 pod 列表和 pod/container 的基本信息,GetPodStatus 则获取单个 pod 内所有容器的详细状态信息(包括 pod IP 和 runtime 返回的一些状态)。
关于事件通知,上面提到 PLEG 会将 pod lifecycle events 放入一个 channel,Watch 方法返回了这个 channel。
1 | // Watch returns a channel from which the subscriber can receive PodLifecycleEvent |
那么 PLEG 如何判断自己工作是否正常呢?通过暴露 Healthy 方法,GenericPLEG 保存了上一次开始执行 relist 的时间戳,Healthy 方法判断与当前时间的间隔,只要大于阈值,则认为 PLEG unhealthy。
1 | // Healthy check if PLEG work properly. |
这个阈值在 pkg/kubelet/pleg/generic.go 中定义:
1 | const ( |
默认是 3m,也就是说只要 relist 执行时间超过 3 分钟,则认为 PLEG unhealthy。
总结
最后我们总结一下整个流程:
- kubelet 创建并启动 PLEG 模块,watch pod lifecycle event
- PLEG 模块每隔
1s执行 relist,relist 完成两个目标:- 获取 pod list,对比 pod 的 old state 和 new state,产生 PLEG events
- 依次获取 pod status,并更新 pod cache
- kubelet watch 到 pod lifecycle events,产生 update pod 事件通知 pod worker 执行 sync pod 操作
- kubelet 持续检查 runtime state (PLEG) 的健康状态
本文对下面几个方面没有深入介绍,后面有空会写单独的文章将源码解析分享出来:
- kubelet sync loop iteration
- pod worker 的 sync pod 机制
- container runtime
- node status 节点状态控制
Kubelet PLEG 源码解析