【k8s kubelet 源代码阅读(一)】-Pod管理
关键组件介绍
probeManager
当一个pod被kubelet感知到之后,就会调用probeManager
的AddPod
来为各个容器启动探针,监控状态变化。具体执行如下:
获取所有的pod.Spec.Containers以及pod中支持重新启动的initContainers,然后逐个处理
如果这个容器配置了StartupProbe或者ReadinessProbe或者LivenessProbe,那么就会为其创建对应类型的newWorker,并启动协程来运行
newWorker协程会通过一个循环不断运行,其在实际检查前会有一些专门的处理,例如看容器是否是已经重启的新容器(即ContainerID是否发生改变),或者是否处在
onHold
的重启状态,容器是否是Running状态,是否超过了InitialDelaySeconds初始等待时间,检查通过后才会去具体执行探针。如果当前状态不是Started,那么就不会执行ReadinessProbe和LivenessProbe,只会执行StartupProbe。如果已经是Started那么就不会去执行StartupProbe。
然后目前支持的探针类型有:
Exec:执行指定的命令
HTTPGet:执行Http请求
TCPSocket:建立TCPSocket
GRPC:调用GRPC
如果连续探测成功或者连续失败超过阈值,就会将结果记录在自己的缓存中(缓存会被PLEG用来计算pod的事件),然后将事件发送到对应的
updates
通道,然后通过syncLoopIteration
函数来继续处理,在处理中会给podWorkers发送一个这个pod的SyncPodSync类型的事件来触发更新。
StatusManager
StatusManager主要职责是批量地将 Kubelet 本地缓存的 Pod 状态同步到 API Server。
其主循环如下,有两类更新的时机:
增量更新:通过
podStatusChannel
触发全量更新:每10s周期性触发一次
1 |
|
其核心的syncBatch(all bool)
执行流程如下:
首先通过与缓存的apiserver中pod的状态与本地维护的pod的状态比较,收集哪些pod需要更新状态:
增量更新:
- 收集当前status version > apiserver中 version的pod
全量更新:
version变大的pod
有deletionTimestamp并且是终态的的pod
逐condition进行比较收集有不同的pod
然后会对这些收集到的pod通过
syncPod
来向APIServer更新状态首先get到原本的pod
然后比较uid是否相同,如果相同说明是同名新建的pod,需要跳过这次更新,并删除这旧的缓存
然后进行状态合并
mergePodStatus
,状态合并而不是替换的原因在于有些status字段可能不是kubelet管理的,具体合并规则为:对于condition,保留不由kubelet管理的旧的condition,对于由kubelet管理的采用最新的condition,目前由kubelet管理的condition type有:
1
2
3
4
5
6
7v1.PodScheduled,
v1.PodReady,
v1.PodInitialized,
v1.ContainersReady,
v1.PodResizeInProgress,
v1.PodResizePending,
v1.PodReadyToStartContainers,(如果开启相关特性)ResourceClaimStatuses
字段还是采用旧字段,因为其不属于kubelet管理如果pod进入了终态但是还有容器在运行,对于
Phase
、Reason
、Message
字段还是采用原本的旧字段,防止出现明明状态是终态但是还有容器在运行的假死状态如果pod的phase是终态,就强制把
containerReady
和podReady
字段设置为false,以防止上报一个逻辑上矛盾的状态。
得到merge后的状态后会通过Patch的方式来更新status,根据patch返回的新pod来更新缓存
最后还会处理有
deletionTimestamp
并且是终态的的pod,其会调用Apiserver的Delete
去删除对应的pod,并删除对应的缓存。
PLEG
目前最新的k8s支持两类PLEG:
GenericPLEG:
传统的PLEG,核心的执行循环是:
go wait.Until(g.Relist, g.relistDuration.RelistPeriod, g.stopCh)
,默认间隔是1s。在g.Relist函数中,它会通过容器运行时获取到当前节点的所有pod的容器的状态,然后逐个pod检查,通过与新旧容器状态的比较来获取事件,主要的事件类型有ContainerStarted、ContainerDied、ContainerRemoved、ContainerChanged,比较的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25func generateEvents(logger klog.Logger, podID types.UID, cid string, oldState, newState plegContainerState) []*PodLifecycleEvent {
if newState == oldState {
return nil
}
logger.V(4).Info("GenericPLEG", "podUID", podID, "containerID", cid, "oldState", oldState, "newState", newState)
switch newState {
case plegContainerRunning:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerStarted, Data: cid}}
case plegContainerExited:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}}
case plegContainerUnknown:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerChanged, Data: cid}}
case plegContainerNonExistent:
switch oldState {
case plegContainerExited:
// We already reported that the container died before.
return []*PodLifecycleEvent{{ID: podID, Type: ContainerRemoved, Data: cid}}
default:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}, {ID: podID, Type: ContainerRemoved, Data: cid}}
}
default:
panic(fmt.Sprintf("unrecognized container state: %v", newState))
}
}- 如果有事件发生了,那么就会更新kubelet的cache中pod的状态,缓存的pod的状态如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67// PodStatus represents the status of the pod and its containers.
// v1.PodStatus can be derived from examining PodStatus and v1.Pod.
type PodStatus struct {
// ID of the pod.
ID types.UID
// Name of the pod.
Name string
// Namespace of the pod.
Namespace string
// All IPs assigned to this pod
IPs []string
// Status of containers in the pod.
ContainerStatuses []*Status
// Statuses of containers of the active sandbox in the pod.
ActiveContainerStatuses []*Status
// Status of the pod sandbox.
// Only for kuberuntime now, other runtime may keep it nil.
SandboxStatuses []*runtimeapi.PodSandboxStatus
// Timestamp at which container and pod statuses were recorded
TimeStamp time.Time
}
// Status represents the status of a container.
//
// Status does not contain VolumeMap because CRI API is unaware of volume names.
type Status struct {
// ID of the container.
ID ContainerID
// Name of the container.
Name string
// Status of the container.
State State
// Creation time of the container.
CreatedAt time.Time
// Start time of the container.
StartedAt time.Time
// Finish time of the container.
FinishedAt time.Time
// Exit code of the container.
ExitCode int
// Name of the image, this also includes the tag of the image,
// the expected form is "NAME:TAG".
Image string
// ID of the image.
ImageID string
// The digested reference of the image used by the container.
ImageRef string
// Runtime handler used to pull the image if any.
ImageRuntimeHandler string
// Hash of the container, used for comparison.
Hash uint64
// Number of times that the container has been restarted.
RestartCount int
// A string explains why container is in such a status.
Reason string
// Message written by the container before exiting (stored in
// TerminationMessagePath).
Message string
// CPU and memory resources for this container
Resources *ContainerResources
// User identity information of the first process of this container
User *ContainerUser
// Mounts are the volume mounts of the container
Mounts []Mount
// StopSignal is used to show the container's effective stop signal in the Status
StopSignal *v1.Signal
}- 最后会将event(除ContainerChanged)发送给对应的管道,通过
g.eventChannel <- events[i]
eventedPleg:
这是基于事件实现的PLEG,通过 CRI 事件直接获取状态更新,相比依赖轮询的传统方法减少了节点资源消耗,但是这需要 CRI 运行时支持 CRI 事件。
其主循环有两个:
go wait.Until(e.watchEventsChannel, 0, e.stopCh)
和go wait.Until(e.updateGlobalCache,
globalCacheUpdatePeriod
, e.stopCacheUpdateCh)
。对于
e.watchEventsChannel
,其会向容器运行时发起请求,要求运行时将所有容器的生命周期事件(如创建、启动、停止、删除)通过一个 channel (
containerEventsResponseCh
) 发送过来。如果多次连接失败会停止eventPleg,转而启动GenericPLEG。它还会不断从
containerEventsResponseCh
读取事件,然后获取对应pod的status,更新自身的cache,然后将事件发送给eventChannel
主循环syncLoopIteration
其主循环处理为syncLoopIteration
,其核心是使用一个 select 语句同时监听来自多个不同源的事件通道,一旦任何一个通道有事件到达,它就会立即处理,从而驱动节点上的 Pod 达到其期望状态。代码如下。
1 |
|
监控的事件来源有:
configCh
(配置变更)来源: API Server、静态 Pod 文件目录、HTTP 端点。当有 Pod 的增、删、改、查(ADD, UPDATE, REMOVE, RECONCILE, DELETE)等期望状态变更时,事件会发送到这个通道。
处理: 函数会根据事件类型(
u.op
),调用SyncHandler
对应的处理方法(如HandlePodAdditions
,HandlePodUpdates
,HandlePodRemoves
,HandlePodReconcile
),将变更分发下去,最终触发 podWorkers 去执行具体的 Pod 操作。
plegCh
(Pod 生命周期事件)来源: PLEG (Pod Lifecycle Event Generator)通过监控底层的容器运行时(如 containerd)获取的事件,它反映容器的实际状态变化,例如容器启动、死亡、创建失败等。
处理: 当收到值得同步的 PLEG 事件时(
isSyncPodWorthy
),Kubelet 会为对应的 Pod 触发一次同步(HandlePodSyncs
),以更新其在 API Server 上的状态,使其与节点的真实情况保持一致。
syncCh
(周期性同步)来源: 一个定时器,在
syncLoop
中定义,每秒触发一次。处理: 调用
getPodsToSync()
获取所有需要同步的 Pod(比如之前同步失败的、或有内部模块请求同步的),然后调用HandlePodSyncs
对它们进行批量同步。这是一个“兜底”机制,确保即使有事件丢失,Pod 状态最终也能被校准。
livenessManager
,readinessManager
,startupManager
的Updates()
通道 (探针结果)来源: 健康探针(Liveness/Readiness/Startup Probes)探测到某个容器的探针结果发生变化(如从成功变为失败)后会发送事件到通道中。
处理: 当收到探针失败或成功的更新时,Kubelet 会立即为该 Pod 触发一次同步(
HandlePodSyncs
),以更新 Pod 的status.conditions
。例如,Readiness 探针失败会导致 Pod 的Ready
条件变为False
。
housekeepingCh
(内务管理)来源: 一个定时器,每隔
housekeepingPeriod
(默认2秒)触发一次。处理: 调用
handler.HandlePodCleanups()
执行清理工作,比如垃圾回收已终止的 Pod 和容器所占用的资源(如网络、挂载目录等)。这个操作只有在所有配置源都就绪后才会执行,以防误删。
关键事件处理
SyncHandler
主要包含以下的一些处理
1 |
|
HandlePodAdditions
处理由API Server、静态 Pod 文件目录、HTTP 导致的pod添加的事件,流程如下:
对所有添加的pod按创建时间进行排序,再逐个处理
把pod加入podManager用来表示pod以及被kubelet感知到
如果是静态pod:
- kubelet 不会走常规的创建流程,而是会找到它对应的真实 Pod(由 API Server 管理),并向 podWorkers 发送一个 SyncPodUpdate 请求。这通常意味着要根据静态 Pod 文件的变化来更新正在运行的 Pod。
如果是普通的pod:
进行准入检查:检查pod是否已经被标记为需要终止。例如,Kubelet 可能刚重启,就收到了一个已经被删除的 Pod 的 ADD 事件,这时需要忽略掉对其的处理。
进行准入控制:获取到所有的allocated pods,然后通过各个admitHandlers来计算当前的资源是否可以提交这个pod。下面简单介绍predicateAdmitHandler和evicition_manager的检查逻辑:
predicateAdmitHandler:
首先检查一些就算抢占其他pod也无法解决的限制,如系统、主机名;
根据节点基本信息、已有pod、额外插件资源得到完整的节点信息;
执行与调度器中filter类似的检查,检查节点的 CPU、内存、存储、Pod 数量等是否足够, Pod 请求的主机端口是否已被占用,节点的标签是否满足 Pod 的节点亲和性/选择器要求, Pod 是否能容忍节点上的 NoExecute 污点(Taint)等
如果当前检查没有通过,但这个pod是关键函数则会尝试执行抢占,抢占的逻辑大致为通过贪心找到能满足需要的资源且已占有的资源最少的pod。
如果还是不成功就会返回第一个不成功的原因。
evicition_manager:
如果没有condition就说明节点目前没有压力,直接放行
查看当前pod是否是关键pod,如果是就直接放行。判断的依据是查看其是否是静态pod或者优先级是否大于等于 2 * HighestUserDefinablePriority = 2000000000。
如果condition中只有内存压力,那么就会放行非 BestEffort Pod,然后对于 BestEffort Pod会检查它的容忍度,如果配置了对“内存压力”污点(Taint)的容忍度(Toleration)也会放心。
此外在有压力的情况下就不会放行了。
最后调用podWorkers去执行UpdatePod函数,并传入pod及kubetypes.SyncPodCreate事件去继续处理,UpdatePod函数介绍如下:
获取或创建
podSyncStatus
处理状态转化,对于要终止的pod,会设置
status.terminatingAt
字段为当前时间会启动或通知
PodWorker
Goroutine。
PodWorker循环podWorkerLoop
podWorkerLoop
是 Kubelet 中每个 Pod 专属的生命周期管理者。当 UpdatePod
函数第一次接收到关于某个新 Pod 的更新时,它就会为这个 Pod 启动一个独立的 podWorkerLoop
goroutine。这个 goroutine 的职责就是串行地、顺序地处理这个 Pod 从生到死的所有状态转换,确保每一步都正确执行。
这个函数的核心是一个 for range podUpdates
循环。podUpdates
是一个 channel,每当 UpdatePod
函数接收到关于这个 Pod 的新指令时,就会向这个 channel 发送一个信号,从而唤醒这个循环。
等待并获取工作 (
for range podUpdates
):- 循环会阻塞在这里,等待新的更新信号。
准备同步 (
p.startPodSync(podUID)
):一旦被唤醒,它首先会调用
startPodSync
。这个函数负责:从
podSyncStatus
中取出待处理的更新任务 (pendingUpdate
)。检查 Pod 是否可以开始工作(例如,对于静态 Pod,要确保没有同名的旧 Pod 还在运行)。
如果不能立即开始,
startPodSync
会返回canStart: false
,podWorkerLoop
就会continue
,继续等待下一次更新。如果 Pod 已经被标记为“永远无法启动”(例如,在启动前就被删除了),
startPodSync
会返回canEverStart: false
,podWorkerLoop
就会直接return
,彻底退出。如果一切正常,
startPodSync
会返回canStart: true
和canEverStart: true
,以及包含了具体工作内容的update
对象。
执行核心同步逻辑 (
err := func() error { ... }
):这是一个匿名函数,主要是为了方便地处理
err
。获取最新状态:
status, err = p.podCache.GetNewerThan(...)
。在执行任何操作前,它会尝试从 PLEG(Pod Lifecycle Event Generator)的缓存中获取 Pod 的最新运行时状态(kubecontainer.PodStatus
)。这确保了接下来的决策是基于最新的容器状态做出的。根据工作类型分派任务:
switch { case update.WorkType == ... }
case TerminatedPod
: 如果工作类型是“已终止”,则调用p.podSyncer.SyncTerminatedPod
。这个函数负责最后的清理工作,比如卸载卷、清理网络等。case TerminatingPod
: 如果工作类型是“正在终止”,则调用p.podSyncer.SyncTerminatingPod
。这个函数的核心任务是杀死 Pod 中的所有容器,并遵循优雅终止的宽限期。default
(即SyncPod
): 这是最常见的类型,调用p.podSyncer.SyncPod
。这个函数是 Kubelet 的核心同步逻辑,它会:对比 Pod 的期望状态(
pod.Spec
)和实际状态(从容器运行时获取)。创建缺失的容器。
杀死多余或不健康的容器。
设置沙箱(Pod 的网络命名空间等)。
SyncPod
会返回一个布尔值isTerminal
,如果 Pod 的所有容器都已运行完毕并且根据重启策略(RestartPolicy
)不需要再重启,isTerminal
就会是true
。
处理同步结果:
switch { case err == context.Canceled: ... }
: 如果错误是context.Canceled
,说明UpdatePod
函数因为收到了更紧急的指令(如立即终止)而取消了这次同步。podWorkerLoop
会直接等待下一次被唤醒。case err != nil
: 如果发生了其他错误,会打印错误日志,并通过completeWork
函数将这个 Pod 加入到工作队列中,以便稍后进行退避重试(backoff retry)。case update.WorkType == TerminatedPod
: 如果SyncTerminatedPod
成功完成,说明 Pod 的生命周期彻底结束。调用completeTerminated
来清理podWorkers
的内部状态,然后podWorkerLoop
就可以return
,这个 goroutine 也就退出了。case update.WorkType == TerminatingPod
: 如果SyncTerminatingPod
成功完成,说明所有容器都已被杀死。调用completeTerminating
将 Pod 状态更新为“已终止”,并立即触发一次新的更新,以便进入TerminatedPod
阶段进行清理。case isTerminal
: 如果SyncPod
返回isTerminal: true
,说明 Pod 自然运行结束。调用completeSync
将 Pod 状态更新为“正在终止”,并立即触发一次新的更新,以便进入TerminatingPod
阶段。
完成工作并准备下一次循环 (
p.completeWork(...)
):这个函数会根据同步结果(成功、失败、阶段转换)来决定下一次重试的时间间隔,并将 Pod 的 UID 重新加入到
workQueue
中。它还会检查
podSyncStatus
中是否已经有新的pendingUpdate
,如果有,就立即向podUpdates
channel 发送信号,触发下一次循环,实现状态转换的无缝衔接。
1 |
|
Kubelet中的SyncPod
SyncPod
是 Kubelet 中负责创建和维持 Pod 运行状态的核心工作函数。当 podWorkerLoop
决定要对一个 Pod 进行同步时,就会调用这个函数。它的核心目标是:将 Pod 的实际状态(Actual State)驱动为其期望状态(Desired State)。
这个函数是可重入的(reentrant),这意味着它可以被反复调用,并且每次调用都会尝试让 Pod 更接近其最终的期望状态。如果中途发生可恢复的错误,下一次调用 SyncPod
时会从失败的地方继续尝试。
SyncPod
的核心工作流程:
这个函数执行一个非常长的、事务性的操作序列,以确保一个 Pod 被正确地建立起来。如果其中任何一步失败,函数会返回错误,podWorkerLoop
会在稍后重试整个流程。
前置准备和状态生成:
记录延迟: 如果是第一次创建 Pod (
SyncPodCreate
),会记录从 Kubelet 首次看到 Pod 到podWorker
开始处理之间的延迟,用于性能监控。处理资源伸缩 (Resize): 如果启用了
InPlacePodVerticalScaling
特性,它会先检查并处理 Pod 的资源伸缩请求。生成 API 状态 (
generateAPIPodStatus
): 这是非常关键的一步。它会结合 Pod 的spec
、statusManager
中缓存的状态以及从容器运行时获取的最新podStatus
,生成一个最终要上报给 API Server 的v1.PodStatus
对象。检查是否已终结: 如果生成的
apiPodStatus
显示 Pod 已经处于Succeeded
或Failed
状态,说明 Pod 已经运行结束。SyncPod
会将这个最终状态更新到statusManager
,然后返回isTerminal: true
,通知podWorkerLoop
进入终止流程。
状态更新和前置检查:
更新状态管理器 (
statusManager.SetPodStatus
): 将上一步生成的apiPodStatus
更新到 Kubelet 的状态管理器中。这是 Kubelet 内部对 Pod 状态的权威记录。检查网络插件: 如果网络插件还没准备好,并且 Pod 不是
hostNetwork
模式,那么就报错并退出,等待网络就绪。注册 Secret/ConfigMap: 通知
secretManager
和configMapManager
,这个 Pod 依赖了某些 Secret 和 ConfigMap,以便它们可以开始监视和挂载这些资源。
资源准备和创建:
创建 Cgroup (
pcm.EnsureExists
): 如果启用了cgroups-per-qos
,它会为 Pod 创建对应的 Cgroup,并应用资源限制(如 CPU、内存限制)。这里有一个特殊逻辑:如果 Kubelet 重启后发现一个已存在的 Pod 没有 Cgroup,它会先杀死这个 Pod 的所有容器,然后再创建 Cgroup 并重新拉起容器,以确保 Pod 运行在正确的 Cgroup 控制下。创建 Mirror Pod (
tryReconcileMirrorPods
): 如果这是一个静态 Pod(Static Pod),并且还没有对应的镜像 Pod(Mirror Pod),SyncPod
会负责创建它。镜像 Pod 是静态 Pod 在 API Server 中的一个只读映射,目的是让集群的其他组件(如调度器)能够看到这个 Pod 并计算其资源占用。创建数据目录 (
makePodDataDirs
): 为 Pod 创建所需的数据目录,例如/var/lib/kubelet/pods/<pod-uid>/volumes
等。等待卷挂载 (
volumeManager.WaitForAttachAndMount
): 这是非常重要的一步。它会阻塞在这里,直到 Pod 所需的所有存储卷(Volume)都已经被成功地附加(Attach)到节点并挂载(Mount)到 Pod 的数据目录中。
容器运行时同步:
获取拉取镜像的密钥 (
getPullSecretsForPod
): 从 Secret Manager 中获取拉取 Pod 镜像所需的imagePullSecrets
。启动探针 (
probeManager.AddPod
): 通知probeManager
开始对这个 Pod 进行存活探针(Liveness Probe)和就绪探针(Readiness Probe)。调用容器运行时 (
kl.containerRuntime.SyncPod
): 这是整个流程中最核心的调用。Kubelet 将 Pod 的spec
、podStatus
、拉取密钥等所有信息打包,传递给底层的容器运行时(如 containerd 或 CRI-O)。容器运行时会负责:创建或更新 Pod 的沙箱(Sandbox)。
拉取容器镜像。
创建和启动容器。
应用容器级别的配置。
containerRuntime.SyncPod
会返回一个结果,包含了同步过程中发生的任何错误。
收尾工作:
更新 Reason 缓存: 将容器运行时的同步结果更新到
reasonCache
,这有助于调试。处理原地伸缩结果: 如果有容器原地伸缩的操作,根据结果更新 Pod 的
PodResizeInProgress
状况。返回结果: 将
containerRuntime.SyncPod
的错误返回给podWorkerLoop
。如果返回nil
错误,则表示本次同步成功,Pod 已经处于其期望的运行状态。
总结
SyncPod
是一个精心设计的、健壮的事务性脚本。它像一个建筑工头,按照一份详细的蓝图(Pod Spec),一步一步地协调各种资源(Cgroup、网络、存储、Secret),并最终指挥容器运行时这个“施工队”来完成 Pod 的“建造”工作。它的可重入性和详细的步骤确保了即使在复杂的环境中,Pod 也能够被可靠地创建和维护。
1 |
|
kubeGenericRuntimeManager的syncPod
这个函数是 Kubelet 与底层容器运行时(如 containerd, CRI-O)交互的核心。当 Kubelet 的 podWorkerLoop
决定要同步一个 Pod 的状态时,最终就会调用这个函数。它的职责是将 Pod 的实际状态(Actual State)调整为期望状态(Desired State)。
整个函数可以看作一个精心编排的、包含多个步骤的事务性过程。如果中途某一步失败,函数会返回错误,上层逻辑(podWorkerLoop
)会在稍后重试整个 SyncPod
流程。
SyncPod
的 8 个核心步骤:
函数注释中清晰地列出了它的工作流程,我们来逐一解析:
步骤 1: 计算 Pod 的变更 (m.computePodActions
)
这是 SyncPod
的“大脑”。它会比较 Pod 的期望配置 (pod
spec) 和从容器运行时获取的当前实际状态 (podStatus
),然后计算出需要执行的一系列具体操作,封装在 podContainerChanges
对象里。这些操作包括:
KillPod
: 是否需要杀死整个 Pod(包括它的网络沙箱)。CreateSandbox
: 是否需要创建一个新的网络沙箱。ContainersToKill
: 一个列表,包含需要被杀死的容器(比如:健康检查失败、定义已变更等)。ContainersToStart
: 一个列表,包含需要被启动或重启的应用容器。InitContainersToStart
: 需要启动的 Init 容器。EphemeralContainersToStart
: 需要启动的临时容器(Ephemeral Container)。ContainersToUpdate
: 需要原地更新资源的容器(用于垂直扩缩容)。
步骤 2: 如有必要,杀死整个 Pod (if podContainerChanges.KillPod
)
如果步骤 1 计算出需要杀死整个 Pod(通常是因为网络沙箱的配置变了,或者所有容器都已终止且无需重启),这一步就会执行。它会调用 killPodWithSyncResult
来:
停止 Pod 内所有的容器。
停止并销毁 Pod 的网络沙箱。 如果此步骤失败,
SyncPod
会立即返回。
步骤 3: 杀死不需要的容器
如果不需要杀死整个 Pod,这一步会遍历步骤 1 计算出的 ContainersToKill
列表,并调用 m.killContainer
来逐个停止那些不再需要的容器。
步骤 4: 如有必要,创建 Pod 沙箱 (if podContainerChanges.CreateSandbox
)
如果步骤 1 计算出需要创建新的沙箱,这一步就会执行。它会调用 m.createPodSandbox
,通过 CRI (Container Runtime Interface) 请求底层容器运行时创建一个新的网络环境(即 Pod Sandbox)。创建成功后,会获取并记录沙箱的 IP 地址。
步骤 5: 创建临时容器 (Ephemeral Containers)
遍历 EphemeralContainersToStart
列表,启动所有需要的临时容器。临时容器通常用于调试,它们可以在 Pod 运行后动态添加进来。
步骤 6: 创建 Init 容器
遍历 InitContainersToStart
列表,按顺序启动 Init 容器。这是关键的一步:
对于不可重启的 Init 容器,如果任何一个启动失败,
SyncPod
会立即中止并返回错误,因为后续的容器都不能再启动。对于可重启的 Init 容器(Sidecar),如果启动失败,可能会跳过并继续尝试启动其他容器。
步骤 7: 调整运行中容器的资源
如果启用了原地垂直扩缩容(In-Place Pod Vertical Scaling)功能,并且步骤 1 计算出了需要更新资源的容器列表 (ContainersToUpdate),这一步会调用 CRI 接口去更新正在运行容器的资源限制(如 CPU、内存)。
步骤 8: 创建应用容器
这是最后一步,遍历 ContainersToStart
列表,启动所有常规的应用容器。与 Init 容器不同,如果某个应用容器启动失败,SyncPod
通常不会立即中止,而是会记录下这个失败,然后继续尝试启动其他容器。
总结
SyncPod
是 Kubelet 中一个非常健壮和核心的函数。它通过一个定义清晰的、可重试的步骤序列,确保了 Pod 的状态能够可靠地从任何当前状态收敛到其期望状态。它处理了从网络、存储、配置到容器生命周期的方方面面,是 Pod 得以在节点上正确运行的根本保障。
1 |
|