跳转至

Kubelet:节点上的 Pod 管理

Scheduler 将 Pod 调度到某个节点并完成绑定后,该节点上的 Kubelet 开始按照 Pod 配置准备运行环境并启动容器。

Kubelet 是运行在每个节点上的代理,负责管理 Pod 的生命周期,确保容器按照 PodSpec 中的定义运行。它协调卷挂载、设备分配和容器启停,持续监测容器运行状态,并向 API Server 上报 Pod 状态和节点状态。

整体架构

Kubelet 的主同步循环汇集 Pod 配置、运行时事件、探针结果和定时任务,再通知对应的 Pod Worker。Worker 按 Pod UID 串行推进正常同步、终止和清理,正常同步通过 Runtime Manager 计算并执行容器动作。节点准入与资源准备参与这条执行路径,卷管理、状态上报、节点心跳和垃圾回收由各自的循环推进。

Kubelet 整体架构:初始化、配置与状态、事件分派、Pod Worker、运行时协调、资源管理和后台任务

图:Kubelet 整体架构,包含初始化、配置更新、Pod 执行路径、资源管理、状态反馈与插件接口。

核心模块按配置、执行、资源、反馈和节点状态分组。

Pod 配置管理

Pod 协调与运行

  • syncLoop:Kubelet 的主同步循环,接收配置更新、PLEG 事件、探针结果和定时任务,调用对应的处理函数。
  • Allocation Manager:执行 Pod 的本地准入检查,记录已分配的资源,并处理资源原地调整。
  • Pod Worker:串行处理同一 Pod 的同步、终止和清理任务,通过 Kubelet 调用 Runtime Manager 完成容器操作。不同 Pod 可以并行处理。
  • Runtime Manager:比较 PodSpec 与实际运行状态,确定 Sandbox 和容器需要执行的动作,并通过 CRI 完成创建、启动或停止操作。

节点资源管理

  • Volume Manager:维护卷的期望状态与实际挂载状态,通过后台协调完成挂载和卸载,并为 Pod 同步提供等待接口。
  • Container Manager:管理节点和 Pod 的 cgroup,组织 CPU、内存、设备、拓扑及动态资源管理。
  • CPU Manager:按配置策略分配 CPU,维护共享与独占 CPU 集合,并协调容器的 CPU 绑定。
  • Memory Manager:按配置策略记录内存和 HugePages 的 NUMA 分配,并提供内存亲和性建议。
  • Device Manager:管理 Device Plugin 提供的设备,为容器分配具体设备,保存设备路径、挂载和环境变量等运行配置。
  • Topology Manager:合并 CPU Manager、Memory Manager 和 Device Manager 提供的 NUMA 亲和性建议,按策略决定拓扑准入,并协调后续资源分配。
  • DRA Manager:调用 DRA 节点插件,为 Pod 引用的 ResourceClaim 准备资源,并在本节点不再有 Pod 使用这些资源时,调用插件完成清理。

  • Eviction Manager:观察节点压力,先回收节点资源,再按条件选择 Pod 驱逐。

  • Container GC:清理符合策略的已退出容器和不再使用的 Sandbox。
  • Image GC:跟踪镜像使用情况,按磁盘阈值和年龄回收未使用镜像。

运行状态反馈

  • PLEG:检测 Sandbox 和容器的状态变化,更新运行时状态缓存(Pod Cache),并通过事件通知 syncLoop 同步对应 Pod。
  • Probe Manager:为配置了探针的容器运行探测 worker,保存 Startup、Readiness 和 Liveness 结果。
  • Status Manager:缓存 API PodStatus,通过独立的后台循环更新 Pod 的 status 子资源。

节点状态与心跳

  • Node Status 更新循环:计算节点容量、可分配资源和运行条件等信息,更新 Node 的 status 子资源。
  • Lease Controller:定期续约节点 Lease,让控制面观察节点是否持续保持联系。

源码结构与启动

Kubelet 的启动代码建立依赖,核心对象把配置、Pod 协调、资源管理和状态反馈组织起来。源码目录按这些职责拆分,Lease 等通用逻辑则复用共享库。

Kubelet 源码包与执行关系

图:启动入口创建 Kubelet,主循环与 Pod Worker 串起执行路径,资源与反馈模块分别参与协调。

源码目录

以下是 Kubelet 的主要源码目录与文件。其中,cmd/kubelet/ 提供进程启动入口,pkg/kubelet/ 包含 Pod 生命周期管理、节点资源管理和状态更新等核心实现。

kubernetes/
cmd/kubelet/
├── kubelet.go                         # 进程入口
└── app/server.go                      # 配置、依赖与启动
pkg/kubelet/
├── kubelet.go                         # Kubelet、Run、syncLoop、SyncPod
├── pod_workers.go                     # Pod Worker:同步、终止与清理
├── kubelet_node_status.go             # Node Status 更新循环与节点注册
├── config/                            # PodConfig:合并 API、文件、HTTP 配置源
├── pod/                               # Pod Manager:期望 Pod 与 Mirror Pod 映射
├── allocation/                        # Allocation Manager:Pod 准入与资源分配状态
├── container/
│   ├── cache.go                       # 运行时 Pod Cache
│   └── container_gc.go                # Container GC 接口、策略与运行时委托
├── kuberuntime/
│   ├── kuberuntime_manager.go         # Runtime Manager:容器动作计算与 CRI 调用
│   └── kuberuntime_gc.go              # Container GC:容器、Sandbox 与日志回收
├── pleg/                              # PLEG:运行时事件与 Pod Cache 更新
├── prober/                            # Probe Manager:健康探针与结果缓存
├── status/                            # Status Manager:PodStatus 缓存与 API 写入
├── volumemanager/                     # Volume Manager:卷状态与挂载协调
├── cm/
│   ├── container_manager_linux.go     # Container Manager:cgroup 与资源管理组织
│   ├── cpumanager/                    # CPU Manager
│   ├── memorymanager/                 # Memory Manager
│   ├── devicemanager/                 # Device Manager
│   ├── topologymanager/               # Topology Manager
│   └── dra/                           # DRA Manager
├── eviction/                          # Eviction Manager:节点压力驱逐
└── images/
    ├── image_manager.go               # 镜像拉取与退避
    └── image_gc_manager.go            # Image GC:镜像垃圾回收
staging/src/k8s.io/component-helpers/apimachinery/lease/
└── controller.go                      # Lease Controller:节点 Lease 续约

Kubelet 结构体

Kubelet 结构体中,与 Pod 同步、资源管理和状态更新相关的主要字段如下:

kubernetes/pkg/kubelet/kubelet.go
type Kubelet struct {
    podManager        kubepod.Manager
    podWorkers        PodWorkers
    podCache          kubecontainer.ROCache
    statusManager     status.Manager
    probeManager      prober.Manager
    volumeManager     volumemanager.VolumeManager
    allocationManager allocation.Manager
    containerManager  cm.ContainerManager
    containerRuntime  kubecontainer.Runtime
    runtimeService    internalapi.RuntimeService
    pleg              pleg.PodLifecycleEventGenerator
    // 其余字段省略。
}
  • podManager:保存期望运行的 Pod,并维护 Static Pod 与 Mirror Pod 的映射。
  • podWorkers:按 Pod UID 管理同步、终止和清理任务,同一 Pod 的任务串行执行,不同 Pod 可以并行处理。
  • podCache:缓存从容器运行时取得的 kubecontainer.PodStatus,供 Pod Worker 读取。
  • statusManager:缓存 Pod 的 API 状态(v1.PodStatus),并异步更新 API Server 中的 Pod 状态。
  • probeManager:执行 Startup、Readiness 和 Liveness 探针,保存探测结果。
  • volumeManager:维护卷的期望状态与实际挂载状态,协调挂载和卸载。
  • allocationManager:执行 Pod 的本地准入检查,记录已分配的资源,并处理资源原地调整。
  • containerManager:管理节点和 Pod 的 cgroup,协调 CPU、内存、设备、拓扑及动态资源管理。
  • containerRuntime:Kubelet 内部的运行时接口,由 Runtime Manager 实现,提供 Pod 同步、容器终止和运行时状态查询等操作。
  • runtimeService:CRI RuntimeService 客户端接口,用于向容器运行时发送 Sandbox、容器和运行状态相关的请求。
  • pleg:检测 Sandbox 和容器的状态变化,更新运行时 Pod Cache,并通过事件通知 syncLoop 同步对应 Pod。

PodConfig 保存在启动依赖 Dependencies.PodConfig 中。NewMainKubelet 在尚未提供该对象时创建它;startKubelet 将其 Updates() 返回的 channel 传给 Kubelet.Run,再由 Run 交给 syncLoop 读取配置更新。

启动流程

Kubelet 启动时先加载配置,再创建管理 Pod、容器和节点资源的组件。随后,它启动卷管理、状态上报等后台任务,并进入主同步循环,持续处理 Pod 的配置变化和运行状态变化。

  1. 创建并执行启动命令:进程入口 main 调用 NewKubeletCommand,创建默认配置、注册命令行选项和 RunE 回调。命令构造完成后,main 调用 cli.Run 执行命令,进入下一步的 RunE。
  2. 加载配置与基础依赖:RunE 解析命令行参数,按需加载配置文件和配置目录,应用参数优先级并校验配置。随后组装 KubeletServer,调用 UnsecuredDependencies 准备 TLS、挂载工具和卷插件等基础依赖,再调用 app.Run。
  3. 准备运行依赖:app.Run 完成操作系统初始化后调用内部 run。run 准备 API 客户端、认证授权和 CRI 客户端,创建 cAdvisor、Container Manager,再将配置和依赖传给 RunKubelet。
  4. 准备节点信息:RunKubelet 确定节点名称与 IP,确保事件记录器可用,再将这些信息与依赖传给 createAndInitKubelet,由它依次完成对象创建和垃圾回收任务启动。
  5. 创建 Kubelet:createAndInitKubelet 先调用 NewMainKubelet,初始化 Kubelet 结构体、接入 Pod 配置源,并创建 Pod Manager、Pod Worker、Runtime Manager 等组件。此前创建的 Container Manager 通过依赖传入;构造完成后,NewMainKubelet 将 Kubelet 返回给 createAndInitKubelet。
  6. 启动垃圾回收:createAndInitKubelet 接着调用 Kubelet 的 StartGarbageCollection,启动容器回收任务,并按配置启动镜像回收任务。任务启动后,createAndInitKubelet 将 Kubelet 返回给 RunKubelet,由后者继续启动运行入口。
  7. 启动运行入口:RunKubelet 收到 Kubelet 后调用 startKubelet。startKubelet 将 PodConfig.Updates() 返回的 channel 作为配置更新来源,在独立 goroutine 中执行 Kubelet.Run,并按配置在其他 goroutine 中启动节点服务。
  8. 初始化模块与启动后台循环:上述 goroutine 进入 Kubelet.Run,先调用 initializeModules 初始化基础模块,再启动后台任务,随后继续执行第 9 步。

    • 后台并行任务:包括资源分配、卷管理、Node Status 更新、Lease 续约、Pod 状态发布、PLEG 和运行时状态检查。
    • 运行时状态检查:updateRuntimeUp 查询容器运行时的 RuntimeReady 和 NetworkReady,更新 Kubelet 本地保存的运行时与网络状态。Run 在首次执行 syncNodeStatus 前调用它,避免因尚未检查运行时而将节点上报为 NotReady;另有后台任务按 5 秒间隔持续调用。
    • 运行时就绪分支:updateRuntimeUp 首次确认 RuntimeReady=true 后,调用 initializeRuntimeDependentModules,启动 cAdvisor、Container Manager、Eviction Manager、插件注册等模块,并在初始化返回后记录运行时状态检查的成功时间。
  9. 进入 Pod 同步循环:Kubelet.Run 在同一 goroutine 中调用 syncLoop,与已启动的后台任务并行运行。每轮先调用 runtimeErrors(),检查运行时状态是否已成功更新、距上次成功更新是否达到超时阈值、PLEG 等组件是否健康,以及是否存在已记录的运行时错误。检查通过后,才由 syncLoopIteration 读取事件并分发任务,否则退避后重试。

Kubelet 启动流程:从启动命令到主同步循环的九个步骤

图:Kubelet 启动流程。

Pod 配置管理

Kubelet 持续接收配置源提供的 Pod 信息,维护本节点期望运行的 Pod。主同步循环将配置变化分派给对应的 Handler,由 Handler 更新本地记录,并按需向 Pod Worker 提交处理任务。

  • 配置来源与 PodConfig:API Server、本地文件和 HTTP 配置源分别提交各自的 Pod 集合。PodConfig 保存各来源的配置,比较新旧集合,并通过 PodUpdate channel 发送新增、更新、移除等增量变化。
  • 主循环与事件分派:syncLoop 循环调用 syncLoopIteration 读取配置更新。它根据事件类型调用 HandlePodAdditions、HandlePodUpdates、HandlePodRemoves 或 HandlePodReconcile,分别处理 Pod 的新增、更新、移除和状态协调。
  • Pod Manager 保存期望状态:Handler 通过 Pod Manager 的 AddPod、UpdatePod、RemovePod 更新本节点的期望 Pod,并维护 UID 和名称索引,供 Kubelet 后续按 UID 或名称查找 Pod、读取其期望配置。
  • 本地准入与任务提交:对于普通 Pod,Handler 根据配置事件选择提交给 Pod Worker 的任务。

    • HandlePodAdditions:收到 ADD 后,调用 allocationManager.AddPod 执行本地准入检查,通过后提交 SyncPodCreate。
    • HandlePodUpdates:收到 UPDATE 或 DELETE 后提交 SyncPodUpdate。DELETE 携带 deletionTimestamp;Pod Worker 根据任务中 Pod 的删除标记进入终止阶段。
    • HandlePodRemoves:收到 REMOVE 后移除 Pod Manager 中的记录,再通过 deletePod 向 Pod Worker 提交 SyncPodKill 终止任务。
    • HandlePodReconcile:收到仅状态变化产生的 RECONCILE 后,通过 NeedToReconcilePodReadiness 判断是否需要提交 SyncPodSync:Pod 配置了 readinessGates,且已有 Ready 条件的 Status 或 Message 与重新计算的值不同。例如,容器均已就绪且 Ready 仍为 False 时,控制器将最后一个未满足的 readiness gate 对应条件改为 True,便会触发这条同步路径。
  • UpdatePod 保存更新并通知 Worker:podWorkers.UpdatePod 将待处理配置保存到该 Pod UID 对应的 pendingUpdate,并通知工作循环。尚未取出的更新可被后续更新替换;同一 Pod 的任务串行执行,不同 Pod 可以并行处理。

  • 工作循环执行生命周期任务:podWorkerLoop 收到通知后调用 startPodSync,取出 pendingUpdate,确定本轮工作阶段并检查启动条件。随后,工作循环按阶段调用 Kubelet 的方法:SyncPod 协调运行状态,SyncTerminatingPod 停止容器,SyncTerminatedPod 等待卷卸载并清理资源。

Pod 配置管理:HandlePodAdditions、HandlePodUpdates、HandlePodRemoves 和 HandlePodReconcile 分别处理新增、更新、移除与状态协调

图:Pod 配置管理。新增、更新、移除和状态协调分别由对应 Handler 处理。

PodConfig

PodConfig 将各配置源提交的完整 Pod 集合(快照)转换为增量更新,交给 Kubelet 的主同步循环处理。

  • 配置源提交快照:NewSourceApiserver 监听 API Server 中分配到本节点的 Pod;NewSourceFile 读取本地 manifest;NewSourceURL 读取 HTTP manifest。每个来源通过 sourceUpdate{Pods} 提交自己当前提供的完整 Pod 集合。
  • Channel 提供输入通道:PodConfig.Channel(source) 为每个来源取得独立的输入 channel,对应图中的 api channel、file channel 和 http channel。这些通道传递的是 sourceUpdate,其中保存该来源的 Pod 列表。
  • mux 接收并转交:mux 为每个来源启动独立的监听协程。listen 从对应 channel 读取更新,再调用 podStorage.Merge(ctx, source, update),将来源名称和 Pod 集合一起交给存储层。
  • Merge 比较集合并更新缓存:podStorage.Merge 串行处理各来源的更新,内部调用 merge,从 pods[source][UID] 读取该来源的旧集合,按 Pod UID 与新集合比较,再保存新集合。差异分为新增(ADD)、更新(UPDATE)、请求删除(DELETE)、移除(REMOVE)和状态协调(RECONCILE)五类。
  • 输出通道连接主循环:Merge 将变化写入统一的 updates channel,每条 PodUpdate 包含来源 Source、操作类型 Op 和涉及的 Pods。PodConfig.Updates() 返回这个通道,由主同步循环中的 syncLoopIteration 读取,并按操作类型分派给对应的 Handler。

PodConfig 的配置源、mux 输入通道、Merge 与缓存,以及五种增量更新的输出路径

图:PodConfig 从配置快照生成增量更新,并通过统一通道交给主同步循环。

API Server 配置源

Scheduler 将目标节点写入 spec.nodeName 后,该节点的 Kubelet 通过 LIST/WATCH 读取 Pod。字段选择器直接限定本节点,查询范围覆盖所有命名空间。

NewSourceApiserver中用于构造选择器的关键表达式如下:

kubernetes/pkg/kubelet/config/apiserver.go
fields.OneTermEqualSelector("spec.nodeName", string(nodeName))

例如,Pod 绑定到 k8s-ai-infra-worker 后,对应查询条件为:

spec.nodeName=k8s-ai-infra-worker

这一来源使用 Reflector 与 UndeltaStore 维护观察到的 Pod 集合,并把快照交给 PodConfig。Kubelet 重启后重新读取本节点的 Pod,再结合 CRI 中仍存在的容器状态恢复协调。已经运行的容器能否继续保留,由后续状态比较决定。

本地文件配置源

Kubelet 从指定目录或单个文件读取 Pod manifest,通过文件监听和周期扫描跟踪变化,再将解析出的 Static Pod 交给 PodConfig。读取与监听逻辑见 NewSourceFile 与 listConfig。

  • staticPodPath:指定 manifest 路径,默认为空;kubeadm 的默认配置使用 /etc/kubernetes/manifests。
  • fileCheckFrequency:文件配置的周期检查间隔,默认为 20s;文件监听也会触发更新。

例如,在节点现有的 Kubelet 配置文件中设置以下字段,让 Kubelet 读取 /etc/kubernetes/manifests。配置文件路径以 --config 参数为准,修改后需重启 Kubelet。配置步骤见官方文档 Create static Pods。

KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
staticPodPath: /etc/kubernetes/manifests
fileCheckFrequency: 20s

在该目录中保存一个 Pod manifest,每个文件定义一个 Pod。下面的 static-web.yaml 声明一个运行 Nginx 的 Static Pod:

/etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: static-web
  namespace: default
spec:
  containers:
    - name: web
      image: nginx:1.28.0
      ports:
        - containerPort: 80

此后添加、修改或删除 manifest,由文件监听或下一次扫描发现并提交给 PodConfig,无需为每次 manifest 变更重启 Kubelet。

HTTP 配置源

Kubelet 定期从指定 URL 获取 Pod manifest,支持 YAML 或 JSON 格式的单个 Pod 和 PodList。每次提交给 PodConfig 的是该 HTTP 来源的完整期望集合,其中的 Pod 同样属于 Static Pod。请求与解析逻辑见 extractFromURL。

  • staticPodURL:指定 manifest 地址,默认为空。该地址需要能从节点访问,并返回实际的 Pod 配置。
  • httpCheckFrequency:HTTP 配置的拉取间隔,默认为 20s。

在节点现有的 Kubelet 配置文件中设置以下字段,将示例 URL 替换为实际提供 manifest 的地址,修改后重启 Kubelet:

KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
staticPodURL: https://config.example.com/nodes/worker-01.yaml
httpCheckFrequency: 20s

该 URL 应以 200 OK 返回 manifest。例如,下面的 PodList 声明该来源需要运行一个 Pod;需要配置多个 Pod 时,在 items 中继续添加:

worker-01.yaml(HTTP 响应)
apiVersion: v1
kind: PodList
items:
  - apiVersion: v1
    kind: Pod
    metadata:
      name: static-web-http
      namespace: default
    spec:
      containers:
        - name: web
          image: nginx:1.28.0
          ports:
            - containerPort: 80

修改服务器上的 manifest 后,Kubelet 在后续轮询中读取新内容并更新该来源的 Pod 集合。网络错误、非 200 响应或解析失败时,不提交新集合;返回合法的空 PodList(items: [])则表示移除该来源的全部 Pod。

如果服务器返回 200 OK,但响应体为空(0 字节),Kubelet 会先向 PodConfig 提交空 Pod 列表,再记录 zero-length data received 错误。空列表会触发该 HTTP 来源已有 Static Pod 的终止和清理。

配置更新机制

每个配置源都会向 PodConfig 提交自己当前提供的 Pod 列表。PodConfig 分别保存各来源的列表,将同一来源的新旧配置按 Pod UID 比较,再把发生变化的 Pod 连同操作类型交给 syncLoop。比较与更新逻辑见 podStorage.Merge。

  • 新增(ADD):新列表中出现了此前没有的 Pod UID,表示该来源新增了一个 Pod。
  • 更新(UPDATE):Pod UID 保持不变,但容器镜像、标签等配置信息发生了变化。
  • 请求删除(DELETE):Pod 已设置 deletionTimestamp,表示它正在被删除。此时对象仍可能保留在 API Server 中,Kubelet 需要推进容器终止。
  • 移除(REMOVE):该来源的最新列表中已不再包含这个 Pod,Kubelet 需要移除对应的期望状态。
  • 状态协调(RECONCILE):Pod 配置没有变化,但 API 中的 Pod 状态发生了变化,需要 Kubelet 重新核对状态。

DELETE 与 REMOVE 的区别是:前者表示收到删除请求,后者表示配置源中已经没有这个 Pod。 例如,执行 kubectl delete pod 后,Pod 可以先带着 deletionTimestamp 保留在 API Server 中,产生 DELETE 更新;对象随后被删除,才产生 REMOVE 更新。

Pod Manager

Pod Manager 为节点上的期望 Pod 建立索引,供主循环、卷管理和其他模块查询。它同时保存 Static Pod 与 Mirror Pod 的对应关系,使面向 API 的状态与节点内部使用的 Pod UID 能够关联。

Pod Manager 的索引维护、查询调用与结果返回

图:Pod Manager 的索引维护与查询,查询结果返回发起调用的函数。

Pod 存储与查询

Pod Manager 为普通 Pod 与 Static Pod 建立一组索引,为 Mirror Pod 建立另一组索引。图中的绿色数据框对应 basicManager 保存的字段:

  • podByUID、podByFullName:保存普通 Pod 和 Static Pod,分别按 UID、FullName 查询。
  • mirrorPodByUID、mirrorPodByFullName:保存 Mirror Pod,分别按 UID、FullName 查询。
  • translationByUID:保存 Mirror UID 到 Static UID 的映射,供 UID 转换使用。

FullName 是内部名称索引键,由 name + "_" + namespace 组成。例如,default 命名空间中的 web 对应 web_default,monitoring 命名空间中的同名 Pod 对应 web_monitoring。这样既能区分不同命名空间中的同名 Pod,也能通过相同 FullName 关联 Static Pod 与 Mirror Pod。构造逻辑见 GetPodFullName 与 BuildPodFullName。

索引更新

  • AddPod:由 HandlePodAdditions 调用,将新 Pod 加入索引;内部复用 UpdatePod。
  • UpdatePod:由 HandlePodUpdates 调用,更新对应的 UID 和 FullName 索引;找到配对的 Static Pod 与 Mirror Pod 时,建立 UID 映射。
  • RemovePod:由 HandlePodRemoves 调用,移除对应索引;移除 Mirror Pod 时,也删除其 UID 映射。

这三个方法均无返回值。图中的“返回 Pod”来自查询方法,与写入、移除操作分开表示。

查询与返回

查询结果直接返回发起调用的函数。Handler、syncLoop、Kubelet 请求入口和 Status Manager 都可以调用 Pod Manager;各查询方法读取的索引和返回值如下:

方法 查询方式 返回结果
GetPodByUID 使用 UID 查询 podByUID 普通 Pod 或 Static Pod,以及是否找到
GetPodByName 将 namespace、name 组成 FullName,查询 podByFullName 普通 Pod 或 Static Pod,以及是否找到
GetMirrorPodByPod 根据 Static Pod 的 FullName 查询 mirrorPodByFullName 对应 Mirror Pod,以及是否找到
GetPodAndMirrorPod 保留传入的 Pod,再按 FullName 查找另一类对象 pod、mirrorPod 和表示输入是否为 Mirror Pod 的 wasMirror;未找到配对对象时,相应字段为 nil
TranslatePodUID 查询 translationByUID 找到映射时返回 Static UID,否则返回原 UID

例如,HandlePodAdditions 先调用 AddPod 保存配置,再调用 GetPodAndMirrorPod 获取 Pod 与对应的 Mirror Pod。查询结果返回同一个 Handler,由 Handler 根据结果继续处理准入和任务提交,并通过 podWorkers.UpdatePod 通知 Pod Worker。

其他调用者也各自接收查询结果:syncLoop 的 PLEG 事件处理分支 使用 GetPodByUID 查找需要同步的 Pod;Kubelet 请求入口 使用 GetPodByName 按名称查询;Status Manager 使用 GetMirrorPodByPod 获取 Mirror Pod 并读取其状态,在 GetPodStatus 中通过 TranslatePodUID 转换传入的 UID。

Static Pod 与 Mirror Pod

Static Pod(静态 Pod)

Static Pod 是由指定节点上的 Kubelet 直接管理的 Pod。Kubelet 从本地文件或 HTTP 配置源读取它的 manifest,在本节点创建和维护容器,无需 Scheduler 调度。

例如,前文保存在 /etc/kubernetes/manifests/static-web.yaml 中的 Pod 就是 Static Pod。更新它需要修改原始 manifest;停止它需要从配置源中移除对应定义。Static Pod 不能引用 Secret、ConfigMap、ServiceAccount 等其他 API 对象,使用限制见 Static Pods。

Mirror Pod(镜像 Pod)

Mirror Pod 是 Kubelet 为 Static Pod 在 API Server 中创建的 Pod 对象,用于展示该 Static Pod 的配置和运行状态,供 kubectl get pods 等命令查询。它对应节点上已有的 Static Pod,不会另外启动一组容器。官方定义见 Mirror Pod。

通过 kubectl delete pod 删除 Mirror Pod,只会删除 API Server 中的对象,节点上的 Static Pod 仍会运行。只要 Static Pod 仍存在,Kubelet 就会在后续同步中尝试重建 Mirror Pod,具体逻辑见 tryReconcileMirrorPods。

Static Pod 与 Mirror Pod 的名称和命名空间相同,但 UID 不同。Pod Manager 根据相同 FullName 建立两者的 UID 映射,具体实现见 updatePodsInternal。

Pod 协调与运行

syncLoop 选择需要处理的 Pod,Pod Worker 保证同一 Pod 的操作有序,Runtime Manager 把一次正常同步转换为运行时动作。同步可能因新事件、周期任务或前次失败再次发生;已有容器满足期望时可以继续运行。

事件分派、Pod Worker 生命周期与运行时操作

图:主循环提交任务,Worker 按阶段选择同步方法;状态缓存与重试队列分别提供观察结果和后续执行时机。

syncLoop

syncLoop 汇集所有需要协调 Pod 的信号,并选择对应的处理函数。创建容器、拉取镜像和等待卷挂载等操作交给 Pod Worker,主循环继续处理其他事件。

事件来源与处理函数

syncLoopIteration通过 Go select 读取以下来源:

事件来源 携带的信息 分派结果
PodConfig Pod 的新增、修改、删除与对账 更新 Pod Manager,通知对应 Worker
PLEG 容器启动、退出、移除等运行时变化 对需要处理的事件触发 Pod 同步
周期同步 已到期的待同步 Pod 调用 getPodsToSync,重新提交这些 Pod
Liveness 结果 容器健康检查失败 请求同步,由 Runtime Manager 计算停止和重启动作
Readiness / Startup 结果 容器是否就绪、是否已启动 更新 Status Manager,并请求后续同步
Container Manager 受资源变化影响的 Pod UID 重新协调对应 Pod
Housekeeping 节点本地清理时机 处理孤立 Pod、目录和其他残留状态

syncLoopIteration 将配置、运行时、探针和定时事件分派到对应处理函数

图:Sync Loop 中各类事件的入口与处理函数。

配置变化由对应 Handler 更新 Pod Manager,再提交 Worker;需要同步的 PLEG 事件、到期任务和资源变化则汇入 HandlePodSyncs,通过 podWorkers.UpdatePod 通知对应 Worker。Readiness 和 Startup 结果还会更新 Status Manager 保存的容器状态。

DELETE 携带优雅删除信息,因此进入 HandlePodUpdates;REMOVE 表示配置源中已无该 Pod,进入 HandlePodRemoves。对应分支如下:

kubernetes/pkg/kubelet/kubelet.go
// syncLoopIteration 中的部分分支。
switch u.Op {
case kubetypes.ADD:
    handler.HandlePodAdditions(ctx, u.Pods)
case kubetypes.UPDATE:
    handler.HandlePodUpdates(ctx, u.Pods)
case kubetypes.REMOVE:
    handler.HandlePodRemoves(ctx, u.Pods)
case kubetypes.RECONCILE:
    handler.HandlePodReconcile(ctx, u.Pods)
case kubetypes.DELETE:
    // 保留更新语义,让 Pod Worker 执行优雅终止。
    handler.HandlePodUpdates(ctx, u.Pods)
// 其余分支省略。
}

同步任务与 Housekeeping

同步 ticker 每秒触发一次 getPodsToSync,选出需要再次同步的 Pod。正常同步成功后,Worker 按 syncFrequency 安排下一次执行,默认周期为 1 分钟,并加入随机抖动。配置、探针和运行时事件可以在周期到期前触发同步。

Kubelet 在运行时健康检查失败时会暂缓主循环处理。Housekeeping 还要等待所有配置源就绪,避免把尚未完成初次读取的 Pod 当成孤立资源。对应实现分别见 syncLoop和清理分支。

HandlePodCleanups 对照期望 Pod、Worker 进度与运行时对象,处理孤立 Pod、过期状态和节点目录。配置源未就绪时跳过这一步,是为了保留尚未读回的有效 Pod。清理中的某个对象仍在终止时,需要等其生命周期阶段允许回收,再移除相应资源。

Allocation Manager

Allocation Manager 执行 Pod 的本地准入检查,保存已获准使用的资源配置,并处理资源原地调整。新 Pod 通过准入检查后,Kubelet 再将其交给 Pod Worker 处理。

节点本地准入

Pod 绑定到 Node 后,Kubelet 还要检查节点当前能否落实该 Pod 的资源和运行约束。Scheduler 作出决定之后,设备健康状态、拓扑可用性或节点压力可能已经变化;本地准入以节点此刻的视图为依据。

HandlePodAdditions先更新 Pod Manager,再调用 allocationManager.AddPod 进行分配与准入,通过后调用 podWorkers.UpdatePod。检查内容包括:

  • 资源与拓扑:CPU、内存、设备分配以及 Topology Manager 策略是否能满足 Pod 请求。
  • 节点约束:当前资源占用、节点压力和 Pod 的节点级条件是否允许接纳。
  • 安全与功能配置:unsafe sysctl、AppArmor、节点声明的功能以及当前操作系统是否支持 Pod 配置。
  • 关机状态:节点进入受管理的关机流程后,是否还允许接纳新的 Pod。

本地准入失败时,Kubelet 记录失败原因和 Event,并将 Pod 置为 Failed。原 Pod 的 spec.nodeName 保留;Deployment、ReplicaSet 等控制器可以创建新的 Pod 补足副本。排查此类问题应查看本地准入原因,并检查控制器是否已经创建替代 Pod。

资源分配状态与原地调整

资源原地调整过程中,Allocation Manager 用 checkpoint 保存已获准的资源配置,通过 UpdatePodFromAllocation 将这些记录用于准入检查与后续同步。待处理的调整由后台定时任务或 RetryPendingResizes 重新评估;新的分配获准或调整状态变化时,再触发 Pod 同步。相关实现见资源状态记录和调整重试。

例如,将容器的 CPU request 从 1 调到 2,节点资源暂时不足时,已分配值仍为 1,调整请求等待重试。新的分配获准后,后续 Pod 同步再落实资源调整。

Pod Worker

Pod Worker 为每个 Pod UID 维护独立的工作状态与 goroutine。同一 UID 的同步操作串行执行,不同 UID 的 Worker 可以并行处理。一个 Pod 等待卷挂载时,其他 Pod 仍可继续启动或终止。

Pod Worker 的并发边界、更新合并和内部生命周期

图:Pod 更新的合并、Worker 唤醒与生命周期分派。

按 UID 串行处理

podWorkers.UpdatePod 按 UID 查找工作状态,为新 Pod 建立 podUpdates 通知 channel 和 podWorkerLoop。同一 Worker 只有当前一轮返回后才会开始下一轮;同名但不同 UID 的 Pod 使用不同状态。Static Pod 的替换还需要额外检查旧实例的终止进度。

更新合并与重试

UpdatePod将最新任务保存在 pendingUpdate 中,再向容量为 1 的 podUpdates channel 发送唤醒通知。Worker 收到通知后取出待处理任务;执行期间到来的更新可以替换尚未处理的旧更新。例如,同一 Pod 在等待挂卷期间连续发生配置变化,下一轮使用合并后的最新配置。

本轮执行结束后,completeWork 根据结果安排后续工作:正常同步进入周期队列,错误按类型退避重试;已经有 pendingUpdate 时,再通知 Worker 继续处理。具体见后续任务安排。

生命周期状态

Worker 通过内部 WorkType 选择本轮执行的同步方法。这些阶段描述节点上的处理进度,与 API 中的 Pod.status.phase 分别维护。

Worker 状态 调用方法 阶段完成条件
SyncPod Kubelet.SyncPod 完成本轮正常协调,等待下一次事件或周期同步
TerminatingPod Kubelet.SyncTerminatingPod 容器停止,取得最终运行时退出状态
TerminatedPod Kubelet.SyncTerminatedPod 卷卸载与前台资源清理完成,终止状态可确认

进入终止阶段后,Worker 先完成容器停止,再处理卷与节点资源清理。SyncTerminatedPod 成功返回时,Worker 记录清理完成并退出;返回错误时,仍由后续任务重试。

podWorkerLoop在正常同步前通过 podCache.GetNewerThan 取得比上次同步更新的运行时状态,再按阶段调用方法。缓存尚未更新时,Worker 可以等待 PLEG 提供新的观察结果。

Static Pod 更新后可能生成新的 UID。对于 namespace/name 相同的两代 Static Pod,Worker 还会协调旧实例的终止与新实例的启动,避免两代实例同时占用同一组本地资源。

Kubelet.SyncPod

Kubelet.SyncPod 完成一次 Pod 协调尝试:生成状态、检查运行条件、准备节点资源,再将容器状态协调交给 Runtime Manager。同一个 Pod 在首次启动、容器退出、探针变化或错误重试时,都可能再次进入这个方法。

Kubelet SyncPod 的状态生成、节点准备、卷等待与运行时调用

图:Kubelet.SyncPod 的正常路径、终态返回与错误重试分支。

状态与运行条件

SyncPod 通过 generateAPIPodStatus 结合运行时状态和探针结果生成 API PodStatus,并交给 Status Manager 保存。如果生成的 phase 已是 Succeeded 或 Failed,本轮返回 isTerminal=true,Worker 随后转入终止流程;其余 Pod 继续进行节点准备。

对于普通的非 hostNetwork Pod,容器运行时报告网络尚未就绪时,本轮同步会返回错误并等待重试。Kubelet 还会登记该 Pod 引用的 Secret 和 ConfigMap,让对应 Manager 为后续环境变量、卷内容和镜像凭据提供数据。

目录与 cgroup

Kubelet 按配置通过 EnsureExists 准备 Pod cgroup,再调用 makePodDataDirs 建立节点本地目录。cgroup 将 Pod 的资源使用纳入节点层级管理,目录则用于保存卷挂载点、插件数据和其他 Pod 级状态。

CPU、内存和 PID 的约束最终由 Linux 内核执行。Kubelet 与运行时将资源请求和限制转换为相应配置;requests 参与调度与资源分配,limits 对应的执行方式取决于资源类型。例如,CPU 限制可以导致节流,内存超限则可能触发 OOM。

卷与容器依赖

卷的挂载由独立的 Volume Manager 推进。SyncPod 调用 WaitForAttachAndMount 等待所需卷准备完成,成功后再取得镜像拉取凭据、登记探针,并调用 Runtime Manager。

挂载失败或等待超时时,本轮返回错误,由 Worker 安排后续尝试。卷数量超过节点限制则进入拒绝分支,将 Pod 标记为失败并转入终止流程。

下面的片段保留等待卷与进入运行时协调之间的依赖关系;目录、状态和错误记录等代码已省略:

kubernetes/pkg/kubelet/kubelet.go
// 卷没有准备完成时,本轮返回,后续协调继续尝试。
if err := kl.volumeManager.WaitForAttachAndMount(ctx, pod); err != nil {
    // 卷数量限制导致的终止分支、Event 与日志处理省略。
    return false, nil, err
}

pullSecrets := kl.getPullSecretsForPod(logger, pod)
kl.probeManager.AddPod(ctx, pod)

// 运行时协调的其他参数和结果处理见完整源码。

Runtime Manager 返回后,reasonCache.Update 保存本轮容器启动失败的原因,供后续生成容器状态时使用;启动成功则清除对应的旧错误。完整顺序见 Kubelet.SyncPod,错误缓存的处理见 ReasonCache.Update。

Kubelet.SyncTerminatingPod

SyncTerminatingPod 停止 Startup 与 Liveness 探测,生成终止中的状态,并请求 Runtime Manager 停止容器和 Sandbox。容器停止后,它移除探针,重新读取运行时状态,确认没有仍运行的容器,并保存退出码、原因和完成时间。

启用 DRA 时,Kubelet 在确认所有容器停止后、发布最终 Pod 状态前释放对应的节点资源。操作失败时,Worker 会再次尝试终止;成功后才推进到下一阶段。完整过程见 SyncTerminatingPod。

Kubelet.SyncTerminatedPod

容器停止后,Pod Worker 允许后台卷管理与清理路径回收该 Pod 的卷。SyncTerminatedPod 等待卷卸载和卷路径清理完成,再注销 Secret 与 ConfigMap 跟踪、销毁 Pod cgroup,并释放用户命名空间。最后调用 statusManager.TerminatePod,确认节点侧终止清理完成。

SyncTerminatedPod完成后,已退出容器的 CRI 记录仍可能保留,供日志查询和垃圾回收使用。孤立 Pod 目录则由 Housekeeping 等清理路径继续处理。

对于带删除时间戳且满足条件的 Pod,Status Manager 可以再向 API Server 提交带 UID 前置条件的最终删除请求,见状态同步中的删除处理。强制删除 API 对象则可能提前移除可见记录;在节点无法通信时,这不能证明节点上的进程已经停止。

Runtime Manager

Runtime Manager 将 Pod 的期望配置与当前运行时状态转换为具体动作。它实现 Kubelet 内部的运行时接口,向下调用 CRI RuntimeService 和 ImageService;containerd 等实现再完成 Sandbox、镜像和容器进程的实际操作。

Runtime Manager 计算 Pod 动作并通过 CRI 创建 Sandbox 和容器

图:Runtime Manager 根据本轮动作计划,按需停止、保留或创建 Sandbox 与容器。

主要源码分布如下:

kubernetes/pkg/kubelet/kuberuntime/
kuberuntime_manager.go             # computePodActions、SyncPod、重启退避
kuberuntime_sandbox.go             # Sandbox 配置与创建
kuberuntime_container.go           # 容器配置、启动、停止与 hook
kuberuntime_container_linux.go     # Linux 资源与安全配置
kuberuntime_gc.go                  # 容器和 Sandbox 垃圾回收

运行时状态与缓存

Runtime Manager 的 GetPods 汇集运行时中的 Sandbox 与容器信息,GetPodStatus 取得指定 Pod 的详细状态。PLEG 用这些查询刷新 Pod Cache;Worker 读取较新的缓存结果后,将 PodSpec、运行时 PodStatus 和探针信息用于本轮协调。查询接口见 GetPods与 GetPodStatus。

Kubelet 重启时会重新读入配置并查询运行时。如果 Sandbox 与容器仍符合配置,动作计算可以保留它们。缓存只是最近一次观察,运行时仍可能在两次观察之间变化,因此失败处理和下一轮同步仍然必需。

动作计算

computePodActions检查 Sandbox 是否仍然可用、哪些容器已经退出、Init Container 推进到哪里,以及探针或资源调整是否要求改变容器。

观察到的状态 本轮可能产生的动作
首次创建,Sandbox 尚不存在 创建 Sandbox,再推进容器初始化
Sandbox 失效,需要重建且 Pod 仍应继续运行 停止相关容器,重新创建 Sandbox 与所需容器
普通 Init Container 已成功退出 推进下一项初始化;全部完成后启动业务容器
业务容器退出且有效重启策略允许 在退避结束后创建新的容器实例
Liveness 或 Startup Probe 达到失败阈值 停止容器,并按有效重启策略决定是否重新启动
容器状态符合期望 保留现有实例,继续观察

例如,一个有两个业务容器的 Pod 中,容器 A 退出而 B 正常运行。如果 Sandbox 仍然有效、没有触发整组容器重启的规则、A 的重启策略允许且退避已结束,本轮可以只为 A 创建新实例。Pod UID、Sandbox 和 B 的容器实例保持不变。

下面的源码展示动作计划的主要字段。后续逻辑会根据重启策略、初始化进度等条件继续修改计划:

kubernetes/pkg/kubelet/kuberuntime/kuberuntime_manager.go
createPodSandbox, attempt, sandboxID := runtimeutil.PodSandboxChanged(pod, podStatus)
changes := podActions{
    KillPod:           createPodSandbox,
    CreateSandbox:     createPodSandbox,
    SandboxID:         sandboxID,
    Attempt:           attempt,
    ContainersToStart: []int{},
    ContainersToKill:  make(map[kubecontainer.ContainerID]containerToKillInfo),
}

Sandbox 配置与生命周期

Pod Sandbox 提供 Pod 级运行环境。Kubelet 根据 PodSpec、RuntimeClass、DNS 和安全配置生成 Sandbox 配置,再通过 RunPodSandbox 请求运行时创建。

对 containerd 的普通 Linux 网络路径,运行时准备网络命名空间并调用 CNI 插件;配置 hostNetwork 的 Pod 使用节点网络。Sandbox 的具体实现也可能随 RuntimeClass 改变,例如采用虚拟机隔离的运行时。

在 Sandbox 准备完成、网络状态取得之后,在拉取业务容器镜像前调用 OnPodSandboxReady,异步请求发布 PodReadyToStartContainers=True。镜像拉取与 API 状态发布不等待彼此完成。这个条件表示节点具备开始容器创建的基础环境,应用是否能够接收请求还要由容器和就绪状态判断。对应顺序见 kuberuntime.SyncPod。

镜像与容器创建

动作计划选出需要启动的容器后,Runtime Manager 通过 startContainer 执行创建路径。EnsureImageExists 按镜像策略检查和拉取镜像,generateContainerConfig 合并命令、环境变量、卷挂载、设备及资源限制等配置,再通过 CRI 创建并启动容器。

普通容器的创建顺序为:

  1. 取得镜像:按 imagePullPolicy 和节点缓存确定是否需要拉取,使用当前 Pod 的镜像凭据或节点凭据插件完成认证。
  2. 生成配置:合并 PodSpec、节点卷路径、设备分配和资源管理结果。
  3. 创建容器:调用 CreateContainer,取得运行时分配的容器 ID。
  4. 启动容器:调用 StartContainer,由运行时启动进程;随后按配置处理 postStart hook。

imagePullPolicy: Always 会在启动时解析镜像引用,但已有对应 digest 的内容仍可以复用;它不等于每次重新下载全部镜像层。镜像策略见 Images,节点调用见 startContainer。

Init Container 与 Sidecar

普通 Init Container 按声明顺序执行,前一个成功退出后才能推进后一个;所有必需的初始化完成后,业务容器才启动。初始化失败后的处理取决于有效重启策略。

原生 Sidecar 使用 initContainers 中的 restartPolicy: Always 声明。它开始运行并满足配置的 Startup Probe 后,就可以继续后续初始化;它可以与业务容器一起保持运行。生命周期语义见 Sidecar Containers。

初始化通常通过多轮协调推进。普通 Init Container 退出后,PLEG 提供新的观察结果,下一轮同步再推进后续容器。Ephemeral Container 由后续调试请求加入,也由 Runtime Manager 处理。

容器停止与重启退避

需要终止整个 Pod 时,KillPod 进入运行时停止路径,逐个处理容器并停止 Sandbox;正常同步也可以只停止动作计划选中的容器。killContainer 执行适用的 preStop,扣除已经消耗的宽限时间,再调用 CRI StopContainer。hook 与进程退出共用终止预算。

镜像拉取失败、容器启动失败和进程反复退出发生在不同阶段,等待机制也不同。

现象 等待的操作 应优先检查的证据
ImagePullBackOff 再次尝试取得镜像 镜像地址、凭据、网络和拉取错误
CrashLoopBackOff 再次启动已反复退出的容器 上一次退出原因、退出码、日志和探针事件
FailedCreatePodSandBox 重新创建 Pod Sandbox CRI 与 CNI 错误、运行时日志
FailedMount 等待节点卷准备完成 PVC/PV、VolumeAttachment、CSI Node Plugin 与节点日志

CrashLoopBackOff 是容器重启退避的可见原因,API Pod phase 仍可能是 Running。在所链接源码的默认配置下,容器重启延迟从 10 秒指数增长,最高 300 秒;容器稳定运行约 10 分钟后,退避重置。

节点可以通过 crashLoopBackOff.maxContainerRestartPeriod 调整上限;默认关闭的 ReduceDefaultCrashLoopBackOffDecay 也会改变初始值与上限。因此排障时应结合实际 Kubelet 配置。相关实现见退避参数与 doBackOff。

节点资源管理

Pod 中的资源声明需要转换为节点上的挂载路径、cgroup 配置、CPU 集合、NUMA 内存分配和设备配置。不同资源分别保存自己的状态:卷可能还在挂载,CPU 已经分配,设备插件也可能正在准备设备。Kubelet 通过节点准入、正常同步和终止清理这些入口协调它们。

Container Manager 组织 CPU、Memory、Device、Topology 和 DRA Manager,并管理 cgroup 与节点资源预留。Volume Manager、Eviction Manager、Container GC 和 Image GC 由 Kubelet 分别创建和持有;它们的后台循环与单个 Pod 的同步任务协作。对象关系见 Container Manager 初始化、卷与驱逐模块的创建及 回收模块的创建。

节点资源管理的准入、准备与回收调用关系

图:节点准入触发资源分配,Pod 同步等待资源准备,终止与后台回收释放不再使用的资源。

Volume Manager

Volume Manager 持续协调本节点 Pod 使用的卷。它从 Pod 的卷声明建立期望状态,观察已挂载卷的实际状态,再通过卷插件执行缺少的挂载或清理操作。容器创建使用的是已经准备好的节点路径。

Volume Manager 的期望状态、实际状态与卷操作

图:卷声明进入 Desired State of World,Reconciler 通过 OperationExecutor 推进挂载与卸载,Pod 同步等待实际状态满足条件。

期望状态与实际状态

VolumeManager.Run 分别启动 Desired State of World Populator 和 Reconciler。Populator 从 Pod Manager 读取 Pod,解析其实际使用的卷,将 Pod 与卷的关系加入 Desired State of World,简称 DSW。Actual State of World,简称 ASW,记录节点已经观察到的设备挂载、Pod 挂载及可能已经挂载但尚未确认的状态,供后续重试与重启恢复使用。

Reconciler 比较 DSW 与 ASW:仍被 Pod 引用但尚未就绪的卷需要准备;已经挂载而不再需要的卷需要卸载。reconcile 先处理可以卸载的 Pod 挂载,再调用 mountOrAttachVolumes,之后按状态处理设备级卸载与清理。卸载还受状态重建和配置来源是否齐全的约束,防止启动期间把仍在使用的卷误判为孤立挂载。对应实现见 VolumeManager.Run、processPodVolumes与 Reconciler.reconcile。

卷操作与 CSI 节点接口

Reconciler 通过 OperationExecutor 提交卷操作;OperationExecutor 调用具体卷插件,并管理操作并发、失败重试和状态更新。对于 CSI 文件系统卷,CSI volume plugin 的 MountDevice 可以调用 NodeStageVolume,SetUpAt 调用 NodePublishVolume;清理时由 TearDownAt 调用 NodeUnpublishVolume,UnmountDevice 调用 NodeUnstageVolume。

Stage 与 Unstage 取决于驱动是否声明 STAGE_UNSTAGE_VOLUME 能力;卷的 attach 与控制面置备也有各自的驱动条件。节点侧的这些调用由 Kubelet 进程中的 CSI volume plugin 发起,完整接口边界见接口与插件中的 CSI 小节。调用位置见 NodeStageVolume、NodePublishVolume、NodeUnpublishVolume与 NodeUnstageVolume。

Pod 同步中的等待

Kubelet.SyncPod 调用 WaitForAttachAndMount,等待 Pod 使用的卷在 ASW 中达到就绪状态;失败时当前同步返回错误,Pod Worker 后续重试。容器已经终止后,Kubelet.SyncTerminatedPod 调用 WaitForUnmount,等待卷卸载,再继续清理 Pod 目录等资源。这两个等待接口观察后台卷操作的进展,分别位于 正常同步与 终止后清理。

例如,模型缓存 PVC 已经 Bound,表示 PVC 与 PV 的绑定关系成立。目标节点仍需取得卷、建立本机挂载,并把路径加入容器配置;只有这些操作完成后,容器才能使用 /root/.cache/huggingface。PVC 的绑定状态与节点路径是否可用分别由不同阶段确定。存储的完整控制面和数据路径见存储:PersistentVolume、CSI 与数据路径。

Container Manager

Container Manager 管理节点资源的公共基础:建立 cgroup 层级、计算节点预留与可用资源,创建各资源 Manager,并把它们的结果提供给节点准入和容器创建。它持有的资源管理对象各自保存分配状态,共同使用节点拓扑和 Pod 生命周期信息。

Container Manager 的初始化、cgroup 和容器资源配置

图:Container Manager 初始化资源模块,提供 Pod cgroup 管理对象,并汇总容器所需的设备运行配置。

资源模块的组织

启动依赖先调用 cm.NewContainerManager,随后把对象传给 NewMainKubelet。Linux 实现依次创建 Topology、Device、DRA、CPU 和 Memory Manager,其中 Device、CPU、Memory Manager 注册为 Topology Manager 的 HintProvider。注册关系决定了 NUMA 准入时会询问哪些资源提供者;DRA 的 Claim 准备通过自己的节点插件接口推进。

ContainerManager.Start 启动各子模块,加载节点已有容器的映射,为 Kubelet 重启后的资源恢复提供依据。对象创建与注册见 NewContainerManager,子模块启动见 ContainerManager.Start。

cgroup 与节点预留

Container Manager 使用 Cgroup Manager 和 QoS Container Manager 管理节点资源层级。NewPodContainerManager 返回负责 Pod cgroup 的对象;正常同步调用它的 EnsureExists,为 Pod 建立相应的 cgroup。UpdateQOSCgroups 维护 QoS 层级的资源配置,而节点预留用于给系统进程与 Kubernetes 组件保留资源。

CPU 配额与 cpuset 分别限制可用 CPU 时间和可运行的 CPU 集合。对于分配了独占 CPU 的容器,默认开启的 DisableCPUQuotaWithExclusiveCPUs 会关闭 CFS quota,保留 CPU 集合约束;实际行为需要结合节点功能开关与 CPU 策略判断,见 Linux 资源生成与配额设置。Pod cgroup 的创建见 PodContainerManager.EnsureExists,QoS 更新入口见 UpdateQOSCgroups。

容器运行配置

Runtime Manager 生成容器配置时,通过 Kubelet 的 GenerateRunContainerOptions 调用 ContainerManager.GetResources。这个入口汇总 Device Manager 和 DRA Manager 的设备配置,将设备路径、挂载、环境变量、注解及 CDI 设备名称加入 RunContainerOptions。Runtime Manager 再把这些内容转换为 CRI 的 ContainerConfig。

这条路径中的设备配置已经有分配或准备结果。CPU 与内存的 NUMA 分配记录由相应 Manager 管理;Pod cgroup 也有单独的管理对象。具体汇合点见 GetResources与 GenerateRunContainerOptions。

CPU Manager

CPU Manager 管理 Pod 与容器可以使用的 CPU 集合。它按节点配置的策略维护共享池与独占分配,保存分配状态,并让运行中容器的 CPU 集合与这份状态一致。

CPU Manager 的独占分配、状态保存与运行时协调

图:静态 CPU 策略从共享池选择满足拓扑亲和性的 CPU,保存分配结果,并通过运行时更新容器的 cpuset。

共享池与独占 CPU

none 策略使用共享 CPU 管理方式;static 策略为符合条件的工作负载分配独占 CPU。对于使用容器级资源声明的 Pod,常见条件是 Pod 属于 Guaranteed QoS,且容器请求的 CPU 数量为整数。请求 4 个 CPU 的合格容器可以取得 4 个逻辑 CPU;请求 1500m 的容器使用共享池。节点的预留 CPU、可分配 CPU 和策略选项还会影响最终选择。

下面的资源声明用于说明整数 CPU 与 Guaranteed 条件。Pod 中其他容器也需要满足相应条件,节点还需要启用 static 策略并有足够的可分配 CPU。

resources:
  requests:
    cpu: "4"
    memory: "8Gi"
  limits:
    cpu: "4"
    memory: "8Gi"

资格判断见 staticPolicy.guaranteedCPUs。Pod 级资源声明使用相应的资源管理能力和条件,具体配置见 CPU 管理策略。

拓扑建议与分配状态

Topology Manager 先调用 CPU Manager 的 GetTopologyHints 获取候选 NUMA 亲和性,再把合并后的亲和性保存在 Topology Manager 中。CPU Manager 的 Allocate 调用静态策略,读取这份亲和性,选择可用 CPU,将结果写入按 Pod UID 和容器名索引的状态。

已有分配可以在后续同步或 Kubelet 重启后继续使用,避免每次同步重新选择 CPU。分配入口见 CPU Manager.Allocate,拓扑读取与状态保存见 staticPolicy.Allocate。

运行中的 cpuset 协调

容器创建前,Container Manager 的内部生命周期钩子 PreCreateContainer 读取 CPU Manager 的 GetCPUAffinity,将结果写入 ContainerConfig.Linux.Resources.CpusetCpus,见 PreCreateContainer。CPU Manager 的后台 reconcileState 再遍历活动 Pod,找到正在运行的容器,读取应当使用的 CPU 集合,与上次成功更新的集合比较。发生变化时,通过 UpdateContainerResources 更新运行时的 CpusetCpus,成功后记录本轮结果。分配结果落入本地状态与运行时已经成功应用该结果,是两个需要协调的状态。

例如,CPU Manager 为容器记录了 2-5,但运行时更新失败,本轮不会把该集合标成已成功应用。下一轮协调仍会尝试更新。容器临时退出、准备重启时,也不能立即释放它的独占 CPU;最终移除分配依据容器与 Pod 的生命周期处理。具体见 reconcileState。

Memory Manager

Memory Manager 维护可用于资源分配的 NUMA 内存状态,为满足策略条件的容器记录内存与 HugePages 的节点亲和性。它既向 Topology Manager 提供候选建议,也根据最终亲和性保存内存分配结果。

Memory Manager 的 NUMA 内存账本、拓扑建议与分配

图:Memory Manager 从 NUMA 内存账本生成候选 hint,再按选定亲和性记录容器内存块。

NUMA 内存账本

Memory Manager 从节点内存拓扑与预留配置建立状态。账本记录各 NUMA 节点可分配、已分配和剩余的普通内存及 HugePages,并保存 Pod 与容器的内存块。静态策略需要合理的系统预留内存,使资源账本与节点可用于工作负载的内存相符。

例如,NUMA 0 有 16 GiB 可分配普通内存,某容器已经记账 8 GiB,则后续容器的 hint 计算会以剩余量为依据。这里的分配记录表示资源管理器为容器保留了相应的 NUMA 额度;应用实际访问和申请物理页仍由内核执行。策略初始化见 staticPolicy.Start,状态结构见 Memory Manager state。

Hint 计算与分配

在静态策略下,GetTopologyHints 检查工作负载条件与内存请求,使用本地账本计算能够满足请求的 NUMA 节点组合。已有内存块时,它根据已记录的结果重新生成 hint,保持重启前后的亲和性。Topology Manager 接纳合并结果后,调用 Memory Manager 的 Allocate,后者读取最终 hint,检查普通内存与 HugePages 的容量,并写入新的内存块和节点账本。

例如,一个请求 8 GiB 普通内存的容器,可能同时得到 {0} 和 {1} 两个候选;如果 CPU 和 GPU 的共同亲和性是 {1},Memory Manager 就需要在 NUMA 1 上满足其分配。相应路径见 GetTopologyHints与 staticPolicy.Allocate。

容器亲和性与资源释放

GetMemoryNUMANodes 从容器已分配的内存块中提取 NUMA 节点。Container Manager 的 PreCreateContainer 钩子在创建容器前读取这些节点,将其写入 ContainerConfig.Linux.Resources.CpusetMems,见 内存亲和性配置。容器和 Pod 清理时,Memory Manager 删除不再需要的分配,恢复账本中可供后续工作负载使用的额度。

内存亲和性、容器内存 limit 与实际内存占用分别有自己的含义。Memory Manager 的账本用于 NUMA 资源协调;memory limit 约束容器的内存使用,实际占用通过运行时和节点统计观察。NUMA 节点提取见 GetMemoryNUMANodes,配置要求见 Memory Manager。

Device Manager

Device Manager 管理节点上的 Device Plugin。它接收插件报告的设备列表与健康状态,为容器选择具体设备,并保存设备分配响应,供容器创建时生成运行配置。

Device Manager 的设备列表、分配与容器运行配置

图:Device Manager 接收 ListAndWatch,选择设备并调用 Allocate,再把缓存的分配结果提供给容器配置生成。

设备列表与健康状态

插件注册后,Device Manager 建立客户端并调用 ListAndWatch,持续接收设备 ID、健康状态及拓扑信息。管理器据此维护健康与不健康设备集合,以及已经分配给容器的设备。节点资源上报使用这些集合计算对应扩展资源的 capacity 与 allocatable。

例如,插件提供两个健康 GPU,其中一个已经分配给容器。Node 上报的 allocatable 反映健康设备容量,调度器结合已调度 Pod 的请求计算剩余量;节点侧的 Device Manager 则维护具体设备 ID 的分配记录。设备变为不健康后,后续分配会避开该设备,已使用设备的容器仍需要结合工作负载、驱动与恢复机制处理。列表接收见 ListAndWatch 客户端,资源接口见 GetCapacity。

设备选择与 Allocate

节点资源准入调用 Device Manager 的 Allocate。它检查资源请求,选择能够复用或新分配的设备,必要时结合 Topology Manager 保存的亲和性,然后调用对应插件的 Allocate RPC。插件返回设备文件、挂载、环境变量、注解或 CDI 设备名称。

分配结果按 Pod UID、容器名和资源名缓存,并通过 checkpoint 保存必要状态。后续容器创建读取这份结果,避免重复向插件请求同一分配。已有 Init Container 的设备在符合生命周期与复用条件时也可能被后续容器使用。选择与 RPC 调用见 allocateContainerResources及 endpoint.allocate。

运行配置与 CDI

GetDeviceRunContainerOptions 从缓存中读取容器的设备运行配置,Container Manager 将它们合入 RunContainerOptions。传统设备配置携带具体设备路径、挂载和环境变量;CDI 路径携带全限定设备名称,由支持 CDI 的运行时读取对应规范并注入设备。

例如,插件可以返回 vendor.com/gpu=device0,Kubelet 将这个名称放入 CRI 的 ContainerConfig.CDIDevices。实际注入还要求运行时能够解析该设备名称并取得相应 CDI 配置。缓存中的 CDI 提取见 getCDIDeviceInfo。插件接口、配置文件与设备注入的完整路径见GPU 设备:Device Plugin 与设备注入。

Topology Manager

Topology Manager 协调 CPU、内存和设备的 NUMA 亲和性。它在节点准入时询问资源提供者,合并候选 hint,根据策略决定是否接纳,再让各提供者按照共同结果分配资源。

Topology Manager 的 hint 合并、准入与资源分配

图:container scope 下,Topology Manager 先合并候选亲和性,再保存结果并触发各资源 Manager 分配。

HintProvider 与候选结果

CPU、Memory、Device Manager 通过 AddHintProvider 注册到 Topology Manager。每个提供者的 GetTopologyHints 返回资源名与候选 hint,hint 包含 NUMA 节点集合和 Preferred 标记。多个候选表示资源可能在不同节点或节点组合上满足请求。

在 container scope 下,containerScope.Admit 分别处理 Init Container 和应用容器。calculateAffinity 调用 accumulateProvidersHints 收集各提供者结果,再交给 policy.Merge。注册关系见 AddHintProvider 调用,收集与合并见 calculateAffinity。

策略与分配

不同 policy 对合并结果有不同要求:

policy 准入条件
none 按资源管理器自身规则分配,省略拓扑合并决策
best-effort 尝试取得较好的亲和性,无法满足偏好时仍可接纳
restricted 要求合并结果满足 preferred 条件
single-numa-node 要求得到满足条件的单 NUMA 节点结果

策略允许接纳后,setTopologyHints 保存最终结果,allocateAlignedResources 依次调用每个提供者的 Allocate。CPU、Memory、Device Manager 可以读取保存的亲和性,选择具体资源。若策略拒绝或某个资源分配失败,节点准入返回相应错误。调用顺序见 containerScope.Admit与 allocateAlignedResources。

跨 NUMA 资源冲突

假设容器请求 4 个独占 CPU 和 1 个 GPU,CPU Manager 只能在 NUMA 0 满足 CPU 请求,GPU 只在 NUMA 1;设备插件也提供了 GPU 的 NUMA 信息。两类资源没有共同的单 NUMA 亲和性,single-numa-node 策略因而拒绝准入,返回 TopologyAffinityError。调度器按资源数量选中了节点,并不能保证节点上的这些资源满足更严格的拓扑条件。

topologyManagerScope 决定按 container 还是 pod 汇总资源,policy 决定合并结果的准入要求。两个配置项应分别理解;Pod 级资源和具体策略选项还会影响准入规则,配置见 Topology Manager。

DRA Manager

DRA Manager 为 Pod 引用的动态资源执行节点准备。它读取 ResourceClaim 的分配结果,检查当前 Pod 是否允许使用这些资源,调用 DRA 节点插件,并保存创建容器所需的 CDI 设备信息。

DRA Manager 的 Claim 校验、节点准备、CDI 与释放

图:DRA Manager 按驱动准备 ResourceClaim,缓存 CDI 设备信息,并在最后一个引用者释放资源时调用节点清理。

Claim 校验与节点准备

Container Manager 创建并启动 DRA Manager。Runtime Manager 的 SyncPod 在启动容器前调用 runtimeHelper.PrepareDynamicResources,经 Kubelet 与 Container Manager 的同名包装方法进入 DRA Manager.PrepareResources。准备过程读取 Claim,验证分配、Pod 预留关系及驱动信息,把需要准备的 Claim 按驱动分组,调用 NodePrepareResources。准备调用见 Runtime Manager.SyncPod与 Kubelet.PrepareDynamicResources。

插件负责让已经分配的资源在本节点上可用,例如准备设备配置,并返回该 Claim 对应的设备与 CDI device ID。DRA Manager 将结果写入 Claim 缓存和 checkpoint,标记已准备状态;后续同步可以使用已完成的结果。相应路径见 PrepareResources、NodePrepareResources与 准备结果保存。

容器资源映射

一个 Claim 可以包含多个设备或请求,Pod 中各容器也可能引用不同的请求。GetResources 根据容器声明找到应使用的 Claim 和请求,从已经准备的缓存中取出相应 CDI device ID,再经 Container Manager 合入容器运行配置。

例如,驱动为 Claim 准备了两个设备,但某容器只引用其中一个请求,容器收到的是该请求对应的 CDI 名称。DRA Manager 保存 Claim 与 Pod 的引用关系,运行时根据最终传入的名称完成设备注入。读取与映射见 GetResources。

共享引用与释放

Pod 终止后,Kubelet 通过 UnprepareDynamicResources 调用 DRA Manager 的释放入口。若 Claim 仍被其他 Pod 引用,管理器只移除当前 Pod 的引用;最后一个引用者释放时,才按驱动调用 NodeUnprepareResources。成功后删除对应缓存并保存状态。

例如,两个 Pod 共享一个 Claim,先终止的 Pod 不应让另一个 Pod 的设备准备失效。引用计数与幂等状态使重试和重启后的清理仍能沿正确路径推进。释放入口见 UnprepareResources,节点 RPC 见 NodeUnprepareResources。

Eviction Manager

Eviction Manager 观察节点资源压力,先尝试回收节点资源,必要时终止选中的 Pod。它使用的是节点实际资源状态;Pod 已经被调度到节点,并不消除运行中资源耗尽的可能。

Eviction Manager 的阈值检查、资源回收与 Pod 驱逐

图:Eviction Manager 从资源信号判断压力,尝试节点回收;压力持续时按相应规则选择 Pod 并发出终止请求。

压力信号与阈值

EvictionManager.Start 启动监控循环,反复调用 synchronize。管理器从节点统计获得可用内存、文件系统空间、inode 和 PID 等信号,判断硬阈值与软阈值,记录软阈值首次达到的时间,并结合 grace period 确定是否触发回收。

例如,软内存阈值要求 memory.available 低于设定值并持续一段时间,短暂波动未必立即引发驱逐;硬阈值满足条件时走更紧急的回收路径。节点压力条件也保留自己的状态与转换周期,由 Node Status 更新路径读取。循环入口见 Start,阈值处理见 synchronize。

节点回收与 Pod 排序

达到可处理的阈值后,reclaimNodeLevelResources 调用与该信号对应的回收函数,例如容器或镜像清理,然后重新读取资源统计。资源已经恢复时本轮结束;压力仍存在时,管理器按该信号的排序规则选取 Pod。

内存压力下,排序会结合使用量是否超过 requests、Pod Priority 和超出量等信息。节点压力阈值路径每轮选择一个 Pod 发起驱逐,然后等待其资源清理。本地临时存储超限使用独立的检查路径,一轮可以处理多个超限 Pod。选择过程见 节点回收与 Pod 排序,回收后重新判断见 reclaimNodeLevelResources。

驱逐与终止状态

evictPod 通过 Kubelet 提供的 killPodFunc 请求终止 Pod,并把状态设为 Failed、原因设为 Evicted。Pod Worker 推进后续终止与资源清理;Deployment 等控制器可以创建替代 Pod。节点压力驱逐的策略与 PodDisruptionBudget 保护的主动驱逐不同,配置见 Node-pressure Eviction。

内核 OOM 与驱逐也有不同结果:OOM 可能只终止容器中的进程,后续是否重启由有效重启策略决定;被驱逐的 Pod 已进入 Pod 终态,应同时观察控制器是否创建了新 Pod。驱逐入口见 evictPod。

Container GC

Container GC 清理节点上不再需要的容器运行时对象。它根据容器退出状态、保留年龄与数量策略回收已退出容器,并清理符合条件的 Sandbox 和日志目录。回收循环与 Pod Worker 的单次同步独立运行。

Container GC 的周期入口与运行时对象回收

图:Kubelet 的回收循环通过 GC 包装对象进入 Runtime Manager,按条件删除退出容器、空 Sandbox 和日志目录。

周期入口与运行时实现

Kubelet 通过 NewContainerGC 创建 kubecontainer.GC 对象。StartGarbageCollection 启动周期任务,调用该对象的 GarbageCollect;包装对象把 GC policy 与配置源是否齐全传给 Runtime Manager 的 GarbageCollect,后者调用内部 containerGC 执行具体删除。

这种组织使 Kubelet 的周期触发、节点回收请求与运行时对象清理共享实现。Eviction Manager 也可通过 DeleteAllUnusedContainers 请求更积极的未使用容器清理。周期入口见 StartGarbageCollection,包装对象见 realContainerGC,运行时入口见 Runtime Manager.GarbageCollect。

容器保留策略

运行时 GC 找出已经退出且超过最小保留年龄的容器,再按每个 Pod/容器组的保留数量和节点总保留数量删除较旧对象。正在运行的容器不属于这条已退出容器回收路径。保留策略可以给排障留下最近退出的实例与日志,同时控制累计占用。

例如,同一容器连续重启产生多个退出实例,GC 可以在它们符合年龄条件后,保留策略允许的较新实例并删除较旧实例。运行中实例及仍需保留的退出实例继续存在。候选选择与数量控制见 evictContainers,策略字段见 GCPolicy。

Sandbox 与日志目录

Sandbox 清理使用自己的条件:运行时 GC 检查 Sandbox 是否仍有容器、是否仍用于 Pod,再清理不再需要的对象。Sandbox 的判断与退出容器的年龄、数量策略分别执行。日志目录清理还结合 Pod 的存活与配置来源状态,避免配置尚未齐全时过早删除。

Pod Worker 完成终止后,运行时可能仍保留已退出容器和旧 Sandbox,直到 GC 或其他清理入口处理。节点排障因而需要同时观察 Pod 状态与运行时对象。运行时 GC 的三个主要步骤见 containerGC.GarbageCollect。

Image GC

Image GC 维护节点镜像的使用记录,根据镜像年龄、磁盘使用率与保留条件删除未使用镜像。它通过 Runtime Manager 请求运行时删除镜像,释放节点镜像文件系统空间。

Image GC 的使用记录、阈值判断与镜像删除

图:Image GC 建立未使用镜像的回收顺序,处理年龄规则与磁盘阈值,再通过运行时删除可回收镜像。

镜像使用记录

Kubelet 的 imageManager 是 ImageGCManager,负责镜像使用统计和回收;Runtime Manager 中的 imagePuller 负责确保容器所需镜像可用。Image GC 读取运行时的镜像与容器信息,记录镜像首次观察时间、最近使用时间及是否正在使用,再形成候选回收顺序。

例如,一个模型服务镜像正在被运行中的容器使用,就不能作为普通的未使用镜像删除;节点上过去拉取但当前未使用的旧镜像可以进入候选列表。创建对象见 NewImageGCManager,回收入口见 GarbageCollect。

年龄与磁盘阈值

GarbageCollect 先获取镜像回收顺序,按启用的最大年龄规则处理旧镜像,再读取 ImageFsStats。磁盘使用率达到高阈值时,管理器计算降到低阈值需要释放的空间,调用 freeSpace 按候选顺序回收,并遵守最小保留年龄等限制。

默认高、低阈值分别为 85% 和 80%,见 默认配置。例如,容量为 100 GiB 的镜像文件系统已使用 90 GiB,目标使用量为 80 GiB,则需要尝试释放 10 GiB。回收需要有足够的未使用镜像;镜像都在使用中、尚未满足保留条件或空间被其他数据占据时,删除镜像可能达不到目标。空间计算与不足处理见 GarbageCollect 阈值逻辑。

镜像删除与回收不足

freeImage 调用 Runtime Manager 的 RemoveImage,后者通过 CRI ImageService 请求运行时删除镜像。成功后,Image GC 移除本地使用记录并更新指标;失败时保留错误,供下一轮处理。运行时文件系统中的共享层可能被其他镜像使用,删除一个镜像对象与实际释放的空间仍需结合运行时统计观察。

Image GC 无法替代卷数据或应用日志的容量管理。出现 FreeDiskSpaceFailed 时,需要检查候选镜像、运行中镜像、镜像文件系统空间以及其他磁盘占用,再判断是否能够通过镜像回收缓解压力。删除入口见 freeImage。

运行状态反馈

Kubelet 持续接收容器运行状态和应用探测结果,用这些观察结果决定哪些 Pod 需要再次同步。PLEG 维护运行时状态,Probe Manager 维护探针结果,Status Manager 将计算后的 PodStatus 发布到 API Server。

PLEG、Probe Manager、Pod Worker 与 Status Manager 的反馈关系

图:事件触发 Pod 同步,运行时 Pod Cache 提供详细状态,Status Manager 发布 API PodStatus。依据 syncLoopIteration 与 Status Manager 后台循环 绘制。

PLEG

PLEG(Pod Lifecycle Event Generator)比较容器运行时前后两次观察到的 Sandbox、容器状态,生成生命周期事件,并更新运行时 Pod Cache。一个容器退出后,事件用于通知 syncLoop 同步对应 Pod,缓存则向 Pod Worker 提供退出码、容器 ID 和状态时间等信息。

Generic PLEG 的全量刷新、按需刷新、运行时缓存与生命周期事件

图:PLEG 的事件通道与运行时缓存分别承担通知和状态读取。依据 Generic PLEG 刷新与对账实现 绘制。

Generic PLEG

Generic PLEG 周期性读取节点上的运行时对象。Relist 调用 Runtime Manager 的 GetPods,将结果写入当前记录,再由 reconcilePodRecord 比较新旧记录。运行态变为退出态会生成 ContainerDied,运行时对象消失则可能生成 ContainerRemoved;没有状态变化的 Pod 通常不需要重新读取全部详细状态。

对发生变化或被标记为需要重检的 Pod,updateCache 继续调用 GetPodStatus。详细状态读取成功后,PLEG 更新记录并发送事件;读取失败时,将错误保存到缓存、保留该 Pod 的重检标记,留待后续刷新。这样,下次对账仍能发现尚未成功处理的变化。实现见 reconcilePodRecord 与 updateCache。

启用 PLEGOnDemandRelist 后,Kubelet 还可以通过 RequestRelist 请求刷新单个 Pod。请求若已被一次更新的全量刷新覆盖,就无需重复处理;否则 relistPod 读取对应 Pod,复用相同的对账逻辑,并推进该 Pod 的观察时间。按需请求与全量刷新由同一工作循环协调,相关代码见 workerLoopIteration 与 relistPod。

Pod Cache 与生命周期事件

运行时 Pod Cache 保存 kubecontainer.PodStatus、读取错误和观察时间。Pod Worker 调用 GetNewerThan,等待比上次同步更新的状态,避免立即重复使用过时的运行时观察结果。读取到缓存中的错误时,本轮同步返回错误,由 Worker 的重试机制继续推进。

例如,容器 C1 退出后,PLEG 先将它的退出状态写入缓存,再发出 ContainerDied。syncLoop 找到对应 Pod 并提交同步任务,Worker 取得包含 C1 退出信息的新状态。Runtime Manager 再结合 PodSpec 和重启策略决定是否创建 C2。事件中携带的 Pod UID 标识同步对象,详细退出信息来自缓存。

syncLoop 对值得同步的事件调用 HandlePodSyncs,对 ContainerDied 还会请求清理该 Pod 的已退出容器。ContainerRemoved 等事件按各自用途处理,不能把所有 PLEG 事件都理解为重启命令。具体分派见 syncLoopIteration。

PLEG 健康检查

Generic PLEG 用最近一次成功读取全量 Pod 列表所对应的开始时间判断刷新是否仍在推进。GetPods 成功后,Relist 更新 relistTime,随后继续逐 Pod 对账;如果列表读取失败,时间戳保持原值。Healthy 在时间戳尚未建立,或距该时间超过阈值时返回错误。

PLEG 刷新时间与健康检查如何影响 syncLoop 和 Node Ready

图:PLEG 健康错误进入运行时错误集合,分别影响 Pod 事件处理和节点 Ready 条件。依据 Healthy、syncLoop 检查与 ReadyCondition 绘制。

默认情况下,每次全量 relist 完成后等待 1 秒启动下一轮,健康阈值为 3 分钟。这 3 分钟衡量的是距 relistTime 的时间间隔;一次 GetPods、某个 Pod 的详细状态读取或其他刷新步骤耗时过长,都可能拖延下一次时间戳更新。单 Pod 按需刷新只更新对应 Pod 的观察时间。参数与时间戳位置分别见 PLEG 周期参数 和 Relist。

健康错误进入 runtimeErrors() 后,syncLoop 暂停事件分派并退避等待;独立运行的节点状态更新循环也读取这些错误,将 Ready 设为 False,原因记为 KubeletNotReady。排查 PLEG is not healthy 时,应继续检查 CRI 请求耗时、运行时日志和节点负载,找出刷新停滞的位置。

Evented PLEG

Evented PLEG 通过 CRI GetContainerEvents 流接收运行时推送的容器事件,再更新运行时缓存与生命周期通知。它需要运行时提供事件流能力,并显式开启 EventedPLEG;链接源码中的该功能仍为 Alpha、默认关闭,见功能开关定义。

启用事件流后,Generic PLEG 继续以较低频率执行全量刷新,用来核对整体状态。流连接失败会触发重连和一次主动 relist;事件流失败次数达到重试阈值后,代码恢复 Generic PLEG 的常规刷新周期。事件处理和恢复逻辑见 watchEventsChannel,设计背景见 KEP-3386:Kubelet Evented PLEG。

Probe Manager

Probe Manager 检查容器中的应用是否完成启动、可以接收流量或仍然健康。它根据 Pod 的探针配置创建探测 Worker,把结果存入对应缓存,再将变化通知给 syncLoop。

Probe Worker 的结果如何进入状态更新和容器动作计算

图:探针结果达到阈值后进入结果缓存,由 syncLoop 更新状态并请求同步。依据 Probe Worker 与 探针结果处理 绘制。

Startup、Readiness 与 Liveness

三类探针分别回答启动完成、服务就绪和进程健康三个问题。Startup Probe 成功前,同一容器的 Liveness 与 Readiness 探测暂不执行;容器启动后,Readiness 和 Liveness 按各自周期继续检查。

探针 成功结果 达到失败阈值后的处理
Startup 将容器标记为已启动,允许后续探测 请求终止当前容器,后续是否重启由有效重启策略决定
Readiness 参与容器 Ready 与 Pod Ready 的计算 将容器标记为未就绪,保留当前容器实例
Liveness 继续观察当前容器 请求终止当前容器,后续是否重启由有效重启策略决定

例如,模型初始化通常需要 90 秒,可以将 Startup Probe 的 periodSeconds 设为 5、failureThreshold 设为 30,提供约 150 秒的连续失败容忍窗口。实际时间还受 initialDelaySeconds、探测耗时和调度影响。Startup 成功之后,Readiness 才开始判断服务是否能够处理请求。

探测 Worker 与结果缓存

AddPod 按 Pod UID、容器名称和探针类型建立 Worker。Worker 按 periodSeconds 调用 doProbe,检查容器是否在运行、初始延迟是否结束,再执行 HTTP、TCP、Exec 或 gRPC 探测。容器 ID 变化时,Worker 切换到新实例并重置相应结果状态,见 AddPod 与 doProbe。

一次探测结果不会必然改变对外状态。Worker 累积连续成功或失败的次数,达到 successThreshold 或 failureThreshold 后调用 resultsManager.Set;结果缓存只有在值发生变化时才发送更新。Liveness 或 Startup 达到失败阈值后,Worker 暂停当前实例的后续探测,等待容器重新创建,避免终止过程中不断产生重复失败。

结果传播与处理

syncLoopIteration 对 Readiness 和 Startup 更新分别调用 SetContainerReadiness、SetContainerStartup,修改本地 PodStatus 中的 Ready、Started 字段,并通过 handleProbeSync 请求 Pod 同步。Liveness 的失败结果也进入这条同步路径。

Runtime Manager 在动作计算时读取 Liveness、Startup 缓存,决定需要停止哪个容器,以及是否按有效重启策略创建新实例。容器级策略启用并配置时使用该策略,否则使用 Pod 级策略;相关判断见探针失败处理。

Readiness 变化经 Status Manager 发布到 API Server 后,EndpointSlice Controller 才会据此更新服务端点条件。因此,探测失败、本地 Ready 更新和端点变化是依次传播的状态;Service 的 publishNotReadyAddresses 等配置还会影响未就绪端点的使用方式。

Status Manager

Status Manager 缓存面向 API 的 v1.PodStatus,将本地状态变化与 API 写入分开处理。正常同步、终止处理和探针结果都可以更新这份缓存,后台循环负责合并、发布和重试。

Status Manager 的版本化 PodStatus 缓存、异步通知与 API 确认

图:本地版本记录状态变化,API 确认版本记录发布进度。依据 updateStatusInternal、syncBatch 与 syncPod 绘制。

PodStatus 缓存与状态版本

SetPodStatus 复制输入状态,再通过 updateStatusInternal 规范化字段并比较新旧值。状态变化或调用者要求强制更新时,缓存中的本地版本递增,同时产生同步通知。podStatuses 以 Pod UID 为键,apiStatusVersions 则记录 API 已经确认的本地版本。

例如,某个 Pod 的本地版本从 7 变为 8、再变为 9,而后台尚未完成 API 写入,下一批同步可以直接读取版本 9。这里的版本是 Status Manager 的本地计数,与 API 对象的 metadata.resourceVersion 分别维护。

这份缓存保存 Pod phase、Condition、容器 API 状态等;PLEG 的运行时 Pod Cache 保存 Sandbox 和容器的节点观察结果。Kubelet 从运行时观察结果计算 API 状态,再写入 Status Manager,二者有各自的使用者和更新时机。

异步发布与批量同步

状态通知使用容量为 1 的 podStatusChannel,已经挂起的通知可以合并。后台收到通知时调用 syncBatch(ctx, false),筛选尚未发布的新版本;每 10 秒调用一次 syncBatch(ctx, true),同时检查 API 状态偏差和满足删除条件的 Pod。两类触发在同一个后台 goroutine 中处理,见 Start。

发布单个 Pod 时,syncPod 先读取 API 对象并核对 UID。Static Pod 的状态通过对应 Mirror Pod 发布;同名 Pod 已经删除重建时,旧 UID 的缓存不能覆盖新对象。随后,mergePodStatus 保留其他组件管理的字段,PatchPodStatus 按需更新 status 子资源。写入成功或状态已经一致时,才推进 API 确认版本;请求失败则保留待同步状态。

因此,kubectl get pod 展示的是 API 最近一次成功保存的状态。节点上的容器已经退出时,API 显示可能仍短暂保留上一轮状态,排障需要结合运行时信息。

终态确认与删除

Pod 的节点清理完成后,SyncTerminatedPod 调用 TerminatePod,补齐终态并记录 podIsFinished。Status Manager 继续发布最终状态,在 Pod 已有删除时间、API phase 已进入 Succeeded 或 Failed、节点终止清理完成等条件满足时,才以零宽限期请求删除 API 对象;Mirror Pod 使用独立的处理规则。

删除请求携带 UID 前置条件,防止延迟请求误删后来创建的同名 Pod。终态构造与删除条件分别见 TerminatePod 和 canBeDeleted。

节点状态与心跳

Kubelet 用 Node Status 发布节点的资源和运行条件,用 Lease 上报自身心跳。两条路径由独立后台任务通过 API Server 更新对象,kube-controller-manager 中的 Node Lifecycle Controller 结合它们监测节点健康。

Node Status 与 Lease 的独立更新路径及控制面健康观察

图:Node Status 提供详细状态,Lease 持续续约;控制面维护自己的最近观察时间。依据 后台任务启动 与 节点健康观察 绘制。

Node Status 更新循环

Node Status 更新循环读取节点资源、运行时健康、压力信号和地址等信息,生成当前状态,并在内容变化或报告周期到期时发布。节点启动时,这条路径还负责按配置注册 Node。

Node 注册、状态生成、变化判断与 Node status 发布

图:syncNodeStatus 先确保注册,再计算和按需发布状态。依据 Node Status 更新实现 绘制。

Node 注册

默认启用 --register-node 时,registerWithAPIServer 调用 initialNode 构造初始 Node,再由 tryRegisterWithAPIServer 创建对象。对象已存在时,Kubelet 读取它并协调自己负责的节点字段。注册成功后,后续循环通过本地标记快速跳过重复注册,见注册实现。

初始对象包含节点名称、标签和初始化得到的状态。节点状态继续变化时,后续更新循环负责补充和修正;Scheduler 读取 Node 的标签、污点、Conditions 与 Allocatable,判断该节点是否适合新 Pod。

节点资源与运行条件

setNodeStatus 依次调用各状态设置函数,将不同管理模块的观察结果写入 Node。主要信息包括:

  • Capacity 与 Allocatable:节点资源总量和可供 Pod 使用的资源。系统预留、Kubernetes 组件预留与相关驱逐预留会影响两者差额。
  • Conditions:Ready、MemoryPressure、DiskPressure、PIDPressure 等。运行时错误、网络状态及节点关闭状态等会参与 Ready 计算,压力条件由相应压力信号生成。
  • Addresses 与 NodeInfo:节点地址、内核、操作系统和运行时信息。
  • Images 与 VolumesInUse:节点镜像信息及正在使用的卷,后者还参与卷管理的状态协调。

例如,运行时暂时不可用时,Ready 计算可以报告 False,而 Lease 控制器仍可能成功续约。此时控制面能看到节点代理仍在联系,但节点当前不满足运行条件。字段生成入口见 defaultNodeStatusFuncs 与 Ready 条件设置。

状态生成与发布

syncNodeStatus 调用 updateNodeStatus,后者在失败时重试 tryUpdateNodeStatus。首次尝试优先从 Node informer 缓存读取对象,重试时直接读取 API;随后 updateNode 复制对象,更新节点字段并比较状态差异。

状态发生变化或报告周期到期时,patchNodeStatus 更新 Node 的 status 子资源,并记录 lastStatusReportTime。无须写入 API 时,循环仍会将观察到的 VolumesInUse 反馈给 Volume Manager,见 tryUpdateNodeStatus。

默认每 10 秒计算一次状态,稳定状态的报告周期为 5 分钟;状态变化和 Kubelet 重启后的报告间隔带有随机抖动。若显式设置 nodeStatusUpdateFrequency 而省略 nodeStatusReportFrequency,后者会采用前者的值。具体默认值与报告条件见配置默认值 和报告周期计算。

Lease Controller

Lease Controller 由 Kubelet 运行,定期通过 API Server 更新节点 Lease 的 renewTime,上报 Kubelet 心跳。每个节点在 kube-node-lease 命名空间中对应一个同名 Lease,Node Status 内容稳定时,Kubelet 仍会独立续约 Lease。

Kubelet 创建 Lease Controller、续约 Lease 与控制面观察的调用关系

图:Kubelet 持有 Lease Controller 实例,共享控制器实现负责创建和续约。依据 实例创建 与 Lease 控制器实现 绘制。

控制器创建与共享实现

NewMainKubelet 根据节点名称、Lease 时长和 API 客户端调用 lease.NewController,将实例保存到 nodeLeaseController。Kubelet.Run 启动它的 Run 方法,因此节点 Lease 的实际执行任务属于 Kubelet。

控制器代码放在 staging/src/k8s.io/component-helpers/apimachinery/lease,因为创建 Lease、刷新时间和处理更新冲突是一组可复用操作。Kubelet 向通用实现传入节点身份、kube-node-lease 命名空间和 Node owner reference 处理函数,使它维护当前节点的心跳对象。

Lease 续约

Run 按续约间隔调用 sync。已有 latestLease 时,控制器先基于缓存对象尝试更新;首次运行或需要重新读取时,backoffEnsureLease 获取现有 Lease,不存在则创建。newLease 复制已有对象并更新 renewTime、leaseDurationSeconds,retryUpdateLease 将它写入 API,成功后保存返回对象。

更新冲突时,控制器重新读取 Lease 后重试;确保 Lease 存在的步骤发生错误时使用指数退避,避免连续失败造成密集请求。相关路径见 sync、retryUpdateLease 和 newLease。

nodeLeaseDurationSeconds 默认为 40,Kubelet 按其四分之一计算续约间隔,默认约 10 秒,并带少量抖动。这个时长参与心跳配置;节点失联的判断还由控制面的监控参数决定。

节点失联与控制面观察

API Server 接收并持久化 Kubelet 提交的 Lease 更新。kube-controller-manager 中的 Node Lifecycle Controller 通过 informer 获取 Node 和 Lease 的变化,观察到 Node Ready 的心跳时间变化,或 Lease 的 renewTime 增大时,刷新本地 probeTimestamp。超过监控宽限期仍未观察到推进,就将 Ready 等相应 Condition 设为 Unknown,随后相关控制器按污点和容忍时间等规则处理节点上的 Pod。

链接源码中的 nodeMonitorGracePeriod 默认值为 50 秒,实际可见时间还受控制器检查周期和通信影响,见默认监控参数 与超时判断。控制面将节点标为失联,只能说明它没有及时收到心跳;原节点上的容器进程是否已停止,还要根据节点与运行时状态确认。

接口与插件

Kubelet 各模块通过不同接口与 API Server、容器运行时和节点插件交互:

  • API Server:API 配置源监听本节点 Pod 的变化,并交给 PodConfig;Status Manager、Node Status 更新循环和 Lease Controller 分别上报 Pod 状态、节点状态和心跳。
  • CSI:Volume Manager 通过 CSI 卷插件调用 CSI Node Plugin,完成节点侧的卷挂载和卸载。容器运行时使用准备好的节点路径,将卷挂载到容器中。
  • CRI:Runtime Manager 通过 RuntimeService 管理 Sandbox 和容器;镜像拉取与回收路径通过 ImageService 查询、拉取或删除镜像。这两组服务均由 containerd、CRI-O 等容器运行时提供。
  • CNI:对普通 Linux、非 hostNetwork Pod,容器运行时在创建或停止 Sandbox 时调用 CNI 插件,配置或清理网络接口、IP 地址和路由等资源。
  • Device Plugin API:Device Manager 调用 ListAndWatch 接收设备列表与健康状态,调用 Allocate 获取容器使用设备所需的设备节点、挂载、环境变量或 CDI 设备名称。
  • DRA 节点接口:DRA Manager 调用 NodePrepareResources,为 Pod 引用的已分配 ResourceClaim 准备节点资源;本节点不再有 Pod 使用这些资源时,调用 NodeUnprepareResources 完成清理。
  • CDI:Device Plugin 或 DRA 节点插件可以返回 CDI 设备名称,由 Kubelet 经 CRI 传给容器运行时。支持 CDI 的运行时读取对应的设备描述文件,将设备节点、挂载和环境变量等加入容器的 OCI 配置。

Kubelet 外部接口:控制面、CRI、CSI、设备插件及运行时内部的 CNI 和 CDI 路径

图:内部模块连接各自的外部接口;CNI 的调用方和 CDI 描述文件的读取方位于容器运行时。

Kubernetes API

Kubelet 通过 Kubernetes API 观察分配到本节点的 Pod,并发布节点执行结果。配置观察、Pod 状态发布和节点心跳使用各自的后台循环,API 请求失败后的重试也在对应路径中处理。

Kubernetes API:Pod 配置观察与 PodStatus、Node Status、Lease 独立发布

图:API 配置源把 Pod 变化送入 PodConfig;状态管理和节点循环分别更新各自的 API 对象。

Pod 配置观察

API 配置源的 LIST/WATCH 带有 spec.nodeName=<node-name> 字段选择器,只观察分配给本节点的 Pod。Reflector 维护对象集合,回调将集合交给 PodConfig;PodConfig 再与其他配置源合并并生成增量更新。例如,一个 Pod 完成绑定后出现在这个集合中,后续会产生节点侧的添加处理。

字段选择器、Reflector 和更新通道定义在 NewSourceApiserver。Secret、ConfigMap、RuntimeClass 和 ResourceClaim 等依赖对象分别由对应管理路径读取或缓存。

状态与 Lease 更新

Status Manager 发布 Pod 的 status 子资源,Node Status 循环发布 Node 的资源和运行条件,Lease Controller 更新 kube-node-lease 命名空间中的节点 Lease。三者反映不同层次的事实:某个容器退出属于 PodStatus,节点资源与 Ready 条件属于 Node Status,最近一次续约时间属于 Lease。

Pod 状态的写入使用 PatchPodStatus,其中带有 Pod UID 校验,避免把同名新 Pod 当作旧 Pod 更新。节点状态和 Lease 的发布分别由 updateNodeStatus与共享库中的 Lease Controller推进。

CRI

CRI 使用 gRPC 定义 Kubelet 与容器运行时之间的请求和响应。Kubelet 传入 Sandbox、容器和镜像配置,containerd、CRI-O 等实现返回运行时对象的 ID、状态或错误。接口定义分为 RuntimeService 和 ImageService。

CRI:Runtime Manager 调用 RuntimeService,镜像管理调用 ImageService

图:容器生命周期和镜像操作通过两组 CRI 服务执行;查询结果返回 Kubelet,供下一轮协调使用。

RuntimeService

RunPodSandbox 根据 Pod 级配置准备运行环境,CreateContainer 在指定 Sandbox 中创建容器,StartContainer 启动容器进程。Kubelet 通过 ContainerStatus、PodSandboxStatus 及列表接口观察现状,再决定后续动作。容器终止使用 StopContainer;停止和删除 Sandbox 分别使用 StopPodSandbox 与 RemovePodSandbox。

Runtime Manager 将 Pod 的 RuntimeClass 名称解析为运行时 handler,再与 PodSandboxConfig 一起交给 RunPodSandbox。容器配置包含镜像、命令、挂载、资源限制、设备和安全上下文;具体字段及调用约束见 CRI RuntimeService 定义。

ImageService

容器启动路径通过 ImageStatus 判断镜像是否在本地,并按镜像拉取策略决定是否调用 PullImage。镜像垃圾回收通过 ListImages、ImageFsInfo 和 RemoveImage 观察存储占用并删除符合条件的镜像。拉取和回收服务于不同的循环,共用运行时维护的镜像存储。

例如,imagePullPolicy: IfNotPresent 且本地已有匹配镜像时,可以直接继续容器创建;拉取失败时,Kubelet 记录错误并按退避策略再次尝试。请求与响应字段见 CRI ImageService 定义。容器进程创建后的内部实现见容器运行时:从 CRI 到容器进程。

CNI

CNI 负责为容器网络命名空间配置网络接口、地址、路由等。对 containerd、CRI-O 的普通 Linux Pod,Kubelet 通过 CRI 请求创建 Sandbox,运行时再根据节点网络配置调用 CNI 插件。

CNI:Kubelet 的 Sandbox 请求进入运行时,再由运行时执行网络配置和清理

图:普通 Linux Pod 的网络调用经过 CRI 运行时;网络错误沿原调用路径返回。

Sandbox 创建与网络调用链

Runtime Manager 生成 PodSandboxConfig,通过 RunPodSandbox 将网络命名空间模式、DNS 配置、端口映射等信息交给运行时。运行时为普通 Pod 准备网络命名空间并执行 CNI ADD,插件及其委派插件完成网络接口、IPAM 和路由等配置。成功后,运行时向 Kubelet 返回 Sandbox ID,并通过状态接口提供 Pod IP。

这条路径的 Kubelet 入口是 createPodSandbox。运行时对 CNI 的集成由 Kubernetes 网络插件文档说明,插件命令的输入输出遵循 CNI 规范。

网络清理与 hostNetwork

Sandbox 停止时,运行时清理对应的网络资源,并按 CNI 协议调用 DEL。清理必须考虑网络配置只完成一部分的情况,因此插件需要容忍重复删除和部分资源已不存在。

hostNetwork: true 的 Pod 使用节点网络命名空间,运行时跳过普通 Pod 的 CNI 网络创建路径。不同 RuntimeClass 的隔离实现可能改变 Sandbox 的内部结构,Pod 级请求仍通过 CRI 表达。

网络失败的回传与定位

CNI 地址分配失败或网络插件调用失败,会使运行时的 Sandbox 创建请求返回错误。Kubelet 记录 FailedCreatePodSandBox 事件,Pod Worker 后续再次同步。例如 IPAM 地址池耗尽时,Pod 已经有 spec.nodeName,但 Sandbox 尚未完成网络准备,业务容器仍无法进入启动阶段。

定位时沿 Pod 事件 → 运行时日志 → CNI 插件或网络代理日志 检查同一次 Sandbox 创建。网络路径与插件实现的关系见网络:通信与流量转发。

CSI

CSI Node Service 把卷准备到节点上供 Pod 使用。Kubelet 的 Volume Manager 协调卷状态,Operation Executor 执行挂载任务,CSI 卷插件通过 gRPC 请求 CSI Node Plugin。容器运行时随后使用已经准备好的宿主机路径构造容器挂载。

CSI:卷协调进入节点插件,发布路径交给容器挂载,清理按相反依赖顺序执行

图:CSI 的节点调用与 Volume Manager 相连;Stage/Unstage 由驱动能力决定,Publish/Unpublish 对应 Pod 使用路径。

卷管理与 CSI Node Service 调用链

Volume Manager 的期望状态记录 Pod 需要的卷,实际状态记录已经完成的节点操作。Reconciler 比较两者后调度挂载或卸载,Operation Executor 调用具体卷插件。CSI 插件从注册信息找到节点驱动的 socket,再调用 Node Service。

控制面的卷置备、attach 与节点挂载有各自的执行者。需要 attach 的驱动,节点路径会等待控制面完成相应操作;不需要 attach 的驱动可以直接推进节点准备。Volume Manager 的后台协调与 WaitForAttachAndMount配合,使 Pod 同步在所需卷可用后继续。

NodeStageVolume 与 NodePublishVolume

NodeStageVolume 把卷准备到节点级 staging 路径;NodePublishVolume 将卷发布到 Pod 使用的 target 路径。多个 Pod 使用同一节点上的卷时,可以共享节点级准备结果,同时拥有各自的发布路径。驱动提供 STAGE_UNSTAGE_VOLUME 能力时才执行 staging。

文件系统卷的入口分别是 CSI attacher 的 MountDevice和 mounter 的 SetUpAt。原始块卷走块设备映射路径,仍使用对应 CSI 节点接口,但交给容器的是设备映射。

NodeUnpublishVolume 与 NodeUnstageVolume

Pod 不再使用卷时,Kubelet 通过 NodeUnpublishVolume 清理对应的 target 路径。节点上其他 Pod 仍使用该卷时,节点级准备需要保留;满足卸载条件后才通过 NodeUnstageVolume 清理 staging 路径。

对应调用位于 TearDownAt与 UnmountDevice。操作失败会保留待协调状态,由后台循环继续处理。

驱动能力与卷就绪条件

PVC 的 Bound 只说明 PVC 与 PV 的绑定关系已经确定。节点上的 attach、stage 或 publish 仍可能因驱动、权限和存储网络问题失败。容器所需卷准备完成后,GenerateRunContainerOptions 读取已挂载卷,生成 CRI 的挂载或设备配置。

例如,模型缓存 PVC 已绑定,但 CSI Node Plugin 无法访问存储后端时,Pod 会停留在等待卷准备的阶段。RPC 的能力约束见 CSI 规范,完整数据路径见存储:PersistentVolume、CSI 与数据路径。

Device Plugin API

Device Plugin 向 Kubelet 提供设备发现、健康状态和容器设备配置。Device Manager 保存设备池与分配结果,在 Pod 请求资源时选择设备,再通过插件取得使用这些设备所需的运行配置。

Device Plugin API:注册、设备流、Allocate 响应与容器配置传递

图:设备插件注册到 Kubelet;Device Manager 发起 ListAndWatch 与 Allocate,再保存响应供容器创建使用。

插件注册与 ListAndWatch

插件启动 gRPC 服务后,向 Kubelet 注册资源名称和服务端点。Kubelet 连接插件并调用 ListAndWatch,持续接收设备 ID、健康状态和拓扑信息。Device Manager 将健康设备纳入可分配集合,Node Status 路径据此更新扩展资源信息。

例如,插件报告四块健康 GPU 后,节点可以对外报告相应的可分配数量;某块 GPU 转为不健康,后续分配会避开它。注册服务见 plugin/v1beta1/server.go,设备流的接收见 ListAndWatch。

Allocate 与容器运行配置

Device Manager 根据资源请求和已有分配选择具体 device ID,调用插件的 Allocate。插件返回容器需要的设备节点、挂载、环境变量、注解或 CDI 设备名称。Device Manager 保存响应,容器配置生成时再按 Pod UID 和容器名称读取。

Allocate 的 gRPC 调用位于 endpointImpl.allocate。GetDeviceRunContainerOptions 提取分配结果,Container Manager 将其合入运行选项,Runtime Manager 再转换成 CRI 字段。可选的 GetPreferredAllocation 和 PreStartContainer 取决于插件声明的能力。协议说明见 Kubernetes Device Plugins 文档。

DRA 节点接口

DRA 节点接口处理已分配 ResourceClaim 的节点准备和释放。Kubelet 按驱动汇总 Claim,调用节点插件并保存准备结果;容器配置按自身引用的 Claim 取得所需 CDI 设备名称。

DRA 节点接口:Claim 校验、按驱动准备、保存 CDI 名称与最后使用者释放

图:节点准备的结果按 Claim 保存;容器停止后,最后一个本地使用者释放对应准备状态。

NodePrepareResources

DRA Manager 读取 Pod 引用的 ResourceClaim,校验 Claim UID、分配结果和 Pod 使用关系,按驱动组织请求。NodePrepareResources 携带 Claim 的命名空间、名称与 UID;驱动读取分配信息,完成设备配置或其他节点操作,并按 Claim 返回设备及 CDI device ID。

Kubelet 检查每个 Claim 的返回结果,把成功准备的信息写入缓存和 checkpoint。某个 Claim 失败或响应缺失时,本轮准备返回错误,后续同步继续尝试。完整流程见 PrepareResources,RPC 调用和响应检查位于 NodePrepareResources 调用处。

NodeUnprepareResources

容器停止后,Kubelet 移除当前 Pod 对 Claim 的本地引用。其他 Pod 仍使用同一 Claim 时保留准备结果;最后一个本地引用释放时,DRA Manager 按驱动调用 NodeUnprepareResources,成功后清理缓存和 checkpoint 中的准备状态。

例如,两个本地 Pod 共享一个允许共享的 Claim,其中一个 Pod 退出只减少引用;第二个退出后才触发驱动释放。引用检查与请求批处理见 UnprepareResources。该调用释放节点准备状态,ResourceClaim 对象与分配的生命周期由控制面继续管理。

CDI 设备注入

CDI 用统一的设备名称和描述文件表达容器所需的设备修改。Device Plugin 或 DRA Driver 返回 CDI 名称,Kubelet 将名称放入 CRI 容器配置,支持 CDI 的运行时读取节点上的描述文件并生成 OCI 配置。

CDI:设备名称从插件经 Kubelet 和 CRI 传递到运行时,描述文件生成 OCI 配置

图:Kubelet 传递 CDI 设备名称;容器运行时读取 CDI 描述文件并应用设备配置。

CDI 设备名称与描述文件

CDI 完整设备名称采用 vendor.com/class=device-name 形式,例如 example.com/accelerator=card0。节点上的 CDI 描述文件记录设备条目,以及设备节点、挂载、环境变量和 hook 等容器配置修改。设备软件或驱动工具负责生成这些描述文件,具体发现目录由运行时及其 CDI 集成配置决定。

CDI 定义的是设备描述与注入规则,不提供一组与 CRI、CSI 相同形式的 gRPC 服务。设备名称、文件结构和容器修改的语义见 CDI 规范。

Device Plugin 与 DRA 的设备信息传递

Device Plugin 可在 AllocateResponse 中返回 CDI 设备;DRA 节点插件可在 NodePrepareResourcesResponse 中返回 CDI device ID。两条路径分别缓存响应,直到生成指定容器的运行配置时,由 ContainerManager.GetResources汇总。

这里传递的是完整设备名称。一个容器引用两个已准备设备时,最终配置会携带对应的两个名称;这些名称必须能被运行时解析到节点上的 CDI 描述。

CRI CDIDevices 与 OCI 配置注入

Runtime Manager 的 generateContainerConfig调用 Kubelet 的 GenerateRunContainerOptions 取得设备运行选项,再通过 makeCDIDevices生成 ContainerConfig.CDIDevices。CRI 运行时在创建容器时解析这些名称,将 CDI 描述中的修改应用到 OCI 配置。

例如,设备已经分配,但对应 CDI 描述文件缺失时,失败点可能出现在运行时的容器创建阶段。排查需要同时检查插件返回的名称、CRI 容器配置和运行时可见的 CDI 描述文件。GPU 的设备暴露过程见 GPU 设备:Device Plugin 与设备注入。

Pod 的节点执行过程

Pod 在节点上经历准入、资源准备、容器运行和终止清理。执行过程中,各种反馈不断触发新的同步;单次函数返回并不表示整个 Pod 生命周期已经结束。

Pod 从绑定、运行到终止的节点生命周期

图:生命周期由多轮同步推进,运行反馈触发下一轮,终止后继续清理节点资源。

GPU Pod 启动

这里以课程中的 vllm-demo Deployment 为例,说明已经介绍的模块如何协作。下面保留节点执行所关心的 Pod 模板字段;完整镜像、命令和参数位于示例文件中。

examples/chapter-01/12-vllm/02-deployment.yaml
spec:
  template:
    spec:
      runtimeClassName: nvidia
      containers:
        - name: vllm
          resources:
            limits:
              nvidia.com/gpu: "1"
          volumeMounts:
            - name: model-cache
              mountPath: /root/.cache/huggingface
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: vllm-model-cache

这段配置使用传统 Device Plugin 的 GPU 请求方式,指定 RuntimeClass,并把 PVC 挂载到模型缓存目录。独占 CPU 和 NUMA 对齐需要额外的资源声明与节点策略;模型加载是否完成,需要通过应用状态或相应探针判断。当前示例未设置 CPU、内存 request,也未配置探针。

一次普通的首次启动可以沿以下依赖关系推进:

  1. 接收绑定结果:API 来源观察到本节点的 Pod,PodConfig 产生更新,HandlePodAdditions 更新 Pod Manager 并执行本地准入。
  2. 分配节点资源:Device Manager 为 GPU 请求选择设备,调用设备插件 Allocate 并保存结果;其他资源按节点策略处理。
  3. 准备目录和卷:Pod Worker 调用 Kubelet.SyncPod,准备 Pod cgroup、目录并等待模型缓存卷挂载完成。
  4. 创建 Sandbox:Runtime Manager 解析 RuntimeClass 对应的 runtime handler,通过 CRI 创建 Sandbox,由运行时准备普通 Pod 的网络。
  5. 创建业务容器:按需取得 vLLM 镜像,把挂载、设备和环境配置写入 CRI ContainerConfig,再创建、启动容器。
  6. 发布观察结果:PLEG 更新运行时状态,后续同步与 Status Manager 将容器状态写回 API Server。
  7. 持续协调:容器退出、配置变化或周期任务触发下一轮;删除 Pod 后,Worker 推进停止容器和节点资源清理。

这里的 runtimeClassName: nvidia 是 RuntimeClass 对象名称。Kubelet 读取该对象的 handler 再交给 CRI;handler 的具体含义由节点运行时配置定义。创建 CRI 容器后的 containerd、shim、OCI runtime 与 GPU 注入过程见容器运行时:从 CRI 到容器进程和 GPU 设备:Device Plugin 与设备注入。

vLLM GPU Pod 的节点启动路径

图:传统 Device Plugin 分配 GPU,卷管理准备模型缓存路径,Runtime Manager 请求创建 Sandbox 与容器。

容器退出与重启

假设一个 Pod 中的容器 A 退出,容器 B 仍正常运行,并且该 Pod 没有配置触发整组容器重启的规则。PLEG 观察到 A 的状态变化,更新 Pod Cache,并发送生命周期事件。syncLoop 据此提交 Pod 同步,Worker 读取新的运行时状态,Runtime Manager 再按有效重启策略、Init 进度和退避状态决定所需动作。

容器退出通过 PLEG 和 Worker 触发重新协调

图:退出事件与运行时缓存分别进入事件路径和状态读取路径,重启决定由 Runtime Manager 作出。

若 Sandbox 仍有效、没有触发整组容器重启的规则,且 A 允许重启,退避结束后只需创建 A 的新实例;Pod UID 不变,A 的容器 ID 改变,B 可以继续运行。若有效策略禁止重启,后续同步按 Pod 的完成条件处理终态。restartCount 增长表示同一 Pod 内的容器尝试增加;控制器创建替代 Pod 则会产生新的 Pod UID。

Pod 删除与清理

API Server 接受优雅删除请求后,为 Pod 设置 deletionTimestamp 与删除宽限期。Kubelet 观察到更新,Worker 转入 TerminatingPod,将有效宽限期传入停止流程。Pod 默认终止宽限期为 30 秒,可以通过 PodSpec 和删除请求调整。

容器配置了 preStop,且有效宽限期大于 0 时,Runtime Manager 在停止容器前执行 hook,hook 消耗同一份终止宽限时间。随后调用 CRI StopContainer,运行时发送相应停止信号,并在期限耗尽后结束仍未退出的进程。停止信号可以来自镜像或支持的生命周期配置。具体计算见 killContainer。

例如,Pod 的宽限期为 30 秒,preStop 用去约 20 秒,业务进程通常只剩约 10 秒处理停止信号;实现还包含最小停止时间等边界处理。为 hook 和应用各自预留完整 30 秒,会错误估计总终止时间。

Pod 优雅终止、节点清理和独立垃圾回收

图:停止进程与释放本地资源分属 Worker 的两个阶段;退出对象和镜像另由后台 GC 回收。

删除更新使 Worker 转入终止阶段。SyncTerminatingPod 停止容器、确认退出并处理动态资源;后续 SyncTerminatedPod 等待卸卷、清理 Pod 资源并确认终态。任一阶段失败都需要在对应阶段重试,避免在进程仍使用卷时提前卸载。

API 对象的消失与节点进程退出可以发生在不同时间。节点失联或强制删除时,控制面可能先移除对象,节点稍后才能处理;出现替代 Pod 也不能证明旧节点上的进程已经停止。

观察与排障

排查节点执行问题时,先确定失败发生在哪个阶段,再用 Pod UID、容器 ID 和时间关联 API、Kubelet、运行时与插件证据。API 状态异步发布,节点上的实际进度需要结合运行时与日志判断。

从配置到应用进程的排障证据链

图:不同层提供不同证据,按对象身份与时间关联后才能还原节点执行过程。

API 对象与事件

先选择课程示例的一个 Pod,再读取完整状态、事件与所分配节点。describe 适合查看失败原因,JSON 输出用于核对 UID、Condition 和容器状态。

POD_NAME=$(kubectl get pods -n default -l app=vllm-demo -o jsonpath='{.items[0].metadata.name}')
NODE_NAME=$(kubectl get pod -n default "$POD_NAME" -o jsonpath='{.spec.nodeName}')

kubectl describe pod -n default "$POD_NAME"
kubectl get pod -n default "$POD_NAME" -o json
kubectl get events -n default --field-selector "involvedObject.name=$POD_NAME" --sort-by=.metadata.creationTimestamp
kubectl get node "$NODE_NAME" -o yaml
kubectl get lease -n kube-node-lease "$NODE_NAME" -o yaml

spec.nodeName 为空时,优先沿调度路径检查;已经设置节点时,再区分本地准入、卷、Sandbox、镜像和应用进程。Event 按名称筛选可能包含同名旧对象的记录,需要结合 Pod UID 和时间判断。

Pod、Node、Lease 和 Events 的关联字段

图:Pod UID 标识一次生命周期,nodeName 指向节点,容器状态、条件与事件分别提供执行信息。

Running 是 Pod phase,描述容器整体运行阶段。是否可以接收业务请求,还需要看 Ready Condition、探针配置与应用状态。课程中的 vLLM 示例没有配置探针,进程运行时模型仍可能处于加载过程。

节点、运行时与插件日志

在实际承载 Pod 的节点上,使用正确配置了 CRI endpoint 的 crictl 查询 Sandbox 与容器。采用 systemd 管理 Kubelet 的节点还可以读取其日志。

sudo crictl pods
sudo crictl ps -a
sudo journalctl -u kubelet --since '10 minutes ago' --no-pager

在 crictl 输出中选定目标 ID 后,可继续使用 crictl inspectp、crictl inspect 和 crictl logs 检查 Sandbox、容器状态与日志。Kubelet 日志记录协调和插件错误;应用日志用于解释进程启动失败、模型加载异常和退出原因,两者应结合时间线阅读。

Kubelet、CRI、CNI、CSI 和设备插件的日志位置

图:沿实际调用路径读取调用方和对端日志,确认错误在哪一层产生或回传。

运行时报告 CNI 失败时,继续读取实际负责 Sandbox 网络的运行时与网络插件日志;卷等待失败时,检查 Kubelet 的卷操作、CSI Node Plugin 与相关控制面对象。设备分配错误则需要关联 Device Plugin 的注册、设备健康和 Allocate 结果。插件以 Pod 部署时,可以使用 kubectl logs 读取对应组件;其命名空间和标签由集群部署决定。

故障与模块定位

同一个可见状态可能对应多个底层原因。下表用于选择下一组证据,具体根因仍以事件消息、调用错误和节点状态为准。

观察结果 已确认的进度 后续检查方向
已绑定 Node,出现本地准入失败原因 调度完成,节点接纳失败 资源、NUMA、节点功能与安全配置
PVC 已 Bound,仍出现 FailedMount 卷对象关系已确定 节点 attach/mount 与 CSI 状态
Sandbox 创建失败 已进入运行时准备 CRI、CNI、RuntimeClass 与节点运行时配置
容器为 Running,应用访问失败 进程已启动 Readiness、应用日志、监听地址与模型加载
restartCount 增长 同一 Pod 内出现容器重启 previous 日志、退出码、探针和 OOM
API 对象已删除,节点仍有容器 控制面记录已移除 节点连通性、Worker 终止进度和 CRI 状态

完整实验环境与部署步骤见实验:搭建 nvkind 集群并追踪 vLLM 部署。

沿准入、卷、Sandbox、镜像和容器运行阶段定位故障

图:失败原因帮助确定检查入口,再结合调用方与插件日志缩小范围。

Virtual Kubelet

Virtual Kubelet 将 Kubernetes Node 连接到外部计算平台。它维护 Kubernetes 中的 Node 与 Pod 对象,通过 Provider 将 Pod 生命周期操作转换为云容器服务、远端集群或其他后端的调用。

标准 Kubelet 与 Virtual Kubelet 的后端执行路径

图:标准 Kubelet 与 Virtual Kubelet 的后端边界。来源:Virtual Kubelet 官方架构图。

Node 与 Provider

Scheduler 仍然根据虚拟 Node 的标签、污点、Conditions、Capacity 和 Allocatable 选择节点。Pod 绑定到虚拟 Node 后,PodController 观察这些 Pod 的变化,调用 Provider 完成后端工作负载的创建、更新或删除。

Virtual Kubelet 的 NodeController、PodController 与 Provider 接口

图:Pod 生命周期调用流向 Provider,节点与 Pod 状态向 API 回传。依据 PodLifecycleHandler 与 PodNotifier、NodeProvider 绘制。

Provider 的 PodLifecycleHandler 提供 CreatePod、UpdatePod、DeletePod,以及 GetPod、GetPodStatus、GetPods 等查询方法。Provider 实现 PodNotifier 时,可以通过注册的 NotifyPods 回调通知状态变化,再由 PodController 发布到 Kubernetes。删除完成后,Provider 应报告 Pod 和容器的终态,以便控制器确认生命周期已经结束。

NodeController 管理虚拟 Node 的注册和状态。NodeProvider.Ping 提供轻量的存活检查,NotifyNodeStatus 在容量、地址或 Conditions 等发生变化时通知控制器更新 Node。虚拟 Node 宣告的容量来自 Provider 对后端能力的描述,具体含义需要与后端的配额和资源分配方式一致。

能力映射

Virtual Kubelet 将 Kubernetes 接口保留在集群一侧,Provider 决定 PodSpec 中的镜像、资源、网络、卷、探针和安全配置如何映射为后端工作负载。使用某个 Provider 时,需要核对这些字段的支持范围,以及后端失败如何反映到 PodStatus。

PodSpec、容器交互和状态观察经 Provider 映射到后端能力

图:工作负载操作、交互接口和状态反馈分别由 Provider 映射到后端。依据 Provider 接口 与 Virtual Kubelet 架构说明 绘制。

例如,虚拟 Node 发布 nvidia.com/gpu,可以使 Scheduler 按 GPU 资源请求选择它;实际分配哪个 GPU、怎样隔离不同工作负载、设备故障后如何处理,仍由 Provider 与后端完成。节点资源字段与后端分配语义需要同时成立,Pod 才能获得预期的设备。

容器交互也需要后端支持。使用 nodeutil.Provider 集成时,GetContainerLogs 对应日志,RunInContainer 对应 exec,AttachToContainer 对应 attach,PortForward 对应端口转发;AttachProviderRoutes 将 HTTP 路由连接到这些处理方法。接口已接入并不保证后端具备全部操作,Provider 的具体实现和返回错误决定最终行为,见路由绑定实现。

相关资料