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
: 容器状态变为Running
ContainerDied
: 容器状态变为Exited
ContainerRemoved
: 容器消失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 进行清理,默认值是2s
plegCh
负责获取 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 检测周期,默认为1s
runtime
是 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 源码解析