Pod 在节点落地通信流程

目标

这篇文档只回答一个核心问题:kubelet 如何把“期望运行”的 Pod 变成“真实运行”的容器?

重点放在 kubelet 内部各组件之间的协作与 CRI 通信边界,而不是每一个 gRPC 字段的含义。

一句话摘要

kubelet 收到 Pod 更新后,把期望态交给 podWorkerspodWorkers 驱动单 Pod 生命周期状态机,再由 kubeGenericRuntimeManager 通过 CRI 调用容器运行时完成 sandbox、镜像与容器操作。


1. 流程总览

从通信视角看,这条链路可以拆成 3 个阶段:

  1. 更新接入阶段:kubelet 从 Pod source 收到属于本节点的 Pod 更新,并交给内部子系统。
  2. 状态机驱动阶段podWorkers 根据当前状态决定执行同步、终止还是清理。
  3. 运行时执行阶段:runtime manager 通过 CRI 把操作翻译给容器运行时,完成 sandbox 和容器的创建/销毁。

Mermaid:总览图

flowchart LR
    A[Pod update from source] --> B[podManager]
    B --> C[podWorkers]
    C --> D{SyncPod or TerminatingPod?}
    D -->|SyncPod| E[prepare volumes / secrets]
    E --> F[kubeGenericRuntimeManager]
    F --> G[CRI RuntimeService / ImageService]
    G --> H[container runtime]
    D -->|TerminatingPod| I[SyncTerminatingPod]
    I --> F

Kubelet 内部组件协作架构图

flowchart TB
    subgraph 输入层["Pod 输入层"]
        direction LR
        API[API Server<br/>Pod 来源]
        FILE[静态文件<br/>Pod 来源]
        HTTP[HTTP 端点<br/>Pod 来源]
    end

    subgraph 聚合层["配置聚合层"]
        PC[PodConfig]
        CH[updates channel]
    end

    subgraph 核心层["Kubelet 核心层"]
        direction TB
        PM[podManager<br/>期望态索引]
        PW[podWorkers<br/>状态机驱动器]
        SM[statusManager<br/>状态同步]
        VM[volumeManager<br/>卷管理]
        PR[probeManager<br/>探针管理]
    end

    subgraph 运行时层["容器运行时层"]
        direction TB
        KRM[kubeGenericRuntimeManager]
        CRI_R[RuntimeService<br/>容器生命周期]
        CRI_I[ImageService<br/>镜像管理]
    end

    subgraph 外部系统["外部系统"]
        RT[容器运行时<br/>containerd/cri-o]
        CSI[CSI Driver<br/>存储插件]
        CNI[CNI Plugin<br/>网络插件]
    end

    API --> PC
    FILE --> PC
    HTTP --> PC
    PC --> CH
    CH --> PM
    PM --> PW
    PW -->|SyncPod| KRM
    PW -->|SyncTerminatingPod| KRM
    PW --> SM
    PW --> VM
    PW --> PR
    VM --> CSI
    KRM --> CRI_R
    KRM --> CRI_I
    CRI_R --> RT
    CRI_I --> RT
    RT --> CNI
    SM -->|状态上报| API

Pod 生命周期状态机图

stateDiagram-v2
    [*] --> SyncPod: Pod 创建/更新
    SyncPod --> Running: 容器启动成功
    SyncPod --> SyncPod: 启动失败,重试
    Running --> SyncTerminatingPod: 收到删除请求
    SyncTerminatingPod --> SyncTerminatedPod: 优雅终止完成
    SyncTerminatingPod --> SyncTerminatedPod: 超时,强制终止
    SyncTerminatedPod --> [*]: 清理完成

    note right of SyncPod
        创建 sandbox
        挂载卷
        拉取镜像
        创建容器
        启动容器
    end note

    note right of SyncTerminatingPod
        发送 SIGTERM
        等待优雅终止期
        超时发送 SIGKILL
        停止容器
    end note

    note right of SyncTerminatedPod
        卸载卷
        清理 sandbox
        从 podManager 移除
    end note

这张图想表达什么

  • podManager 只负责元数据视图,不负责执行。
  • podWorkers 是单 Pod 生命周期的驱动器,区分同步和终止路径。
  • CRI 是 kubelet 与容器运行时之间的标准协议边界。

2. 分阶段通信流程


阶段一:从 Pod source 到内部子系统

这个阶段的本质是:把控制面的 Pod 期望态同步到 kubelet 本地视图。

步骤 1.1:PodConfig 聚合多个来源

kubelet 支持三个 Pod 来源:apiserver source、file source(静态 Pod)、HTTP source。PodConfig 负责聚合这三个来源的更新,对外提供一个统一的更新通道。

通信方向:多个 source → PodConfig 的 merge channel

步骤 1.2:收到 Pod 更新事件

当 PodConfig 从某个 source 收到 Pod 更新时,它会根据 UID 和全名进行去重和合并,然后生成一个统一的 kubetypes.PodUpdate

通信方向:PodConfig 内部处理

步骤 1.3:podManager 更新本地视图

podManager 收到更新后,会维护本地期望态视图,包括:

  • podByUID:按 UID 索引 Pod
  • podByFullName:按 namespace/name 索引 Pod
  • mirrorPodByUID:静态 Pod 对应的 mirror pod

通信方向:PodConfig → podManager(通过 channel)

步骤 1.4:podWorkers 接收 UpdatePod

podWorkers.UpdatePod(...) 是节点侧生命周期的入口。它会根据 Pod UID 找到或创建对应的工作上下文,并决定下一步走哪条状态机路径。

通信方向:podManager → podWorkers(通过函数调用)


阶段二:podWorkers 驱动状态机

这个阶段的本质是:每个 Pod 有独立的状态机,podWorkers 负责驱动状态转换。

步骤 2.1:判断当前状态

podWorkers 收到更新后,会对比期望态和当前实际状态,决定执行:

  • SyncPod:需要创建或更新 Pod
  • SyncTerminatingPod:需要优雅终止 Pod
  • SyncTerminatedPod:需要清理已终止 Pod 的残留

通信方向:podWorkers 内部状态判断

步骤 2.2:进入 SyncPod 路径(创建/更新)

如果决定执行 SyncPod,kubelet 会依次完成:

  1. 准备存储卷(VolumeManager.WaitForAttachAndMount)
  2. 拉取 secrets 和 configmaps
  3. 准备 pod sandbox
  4. 创建并启动容器

通信方向:podWorkers → kubelet.SyncPod(…) → 多个子系统

步骤 2.3:准备存储卷

VolumeManager 会确保 Pod 需要的卷已经 attach 并 mount 到正确位置。这一步可能涉及与 CSI driver 的通信。

通信方向:kubelet → VolumeManager → CSI driver(外部)

步骤 2.4:拉取 secrets/configmaps

如果 Pod 引用了 secrets 或 configmaps,kubelet 会从 API Server 拉取这些资源,并传递给 runtime manager。

通信方向:kubelet → secretManager/configMapManager → API Server

步骤 2.5:进入 SyncTerminatingPod 路径(优雅终止)

如果 Pod 被标记为删除,podWorkers 会进入终止路径。它会先向容器发送 SIGTERM,等待优雅终止期,超时后发送 SIGKILL。

通信方向:podWorkers → kubelet.SyncTerminatingPod(…) → CRI

步骤 2.6:进入 SyncTerminatedPod 路径(清理)

终止完成后,SyncTerminatedPod 会清理 Pod 的残留资源,包括:

  • 卸载存储卷
  • 清理 sandbox
  • 从 podManager 中移除 Pod

通信方向:podWorkers → kubelet.SyncTerminatedPod(…) → 多个子系统


阶段三:通过 CRI 执行容器操作

这个阶段的本质是:kubelet 把操作意图翻译成标准化的 CRI gRPC 调用。

步骤 3.1:创建 pod sandbox

在创建任何业务容器之前,runtime manager 会先调用 CreatePodSandbox 创建一个 pod sandbox。sandbox 是 Pod 的基础运行环境,包含网络命名空间等基础设施。

通信方向:kubeGenericRuntimeManager → CRI RuntimeService(gRPC)

步骤 3.2:拉取容器镜像

对于 Pod 中定义的每个容器,runtime manager 会调用 PullImage 拉取所需镜像。如果镜像已存在,这一步会快速返回。

通信方向:kubeGenericRuntimeManager → CRI ImageService(gRPC)

步骤 3.3:创建容器

镜像准备好后,runtime manager 调用 CreateContainer 创建容器。这一步会设置容器的资源限制、挂载点、环境变量等配置。

通信方向:kubeGenericRuntimeManager → CRI RuntimeService(gRPC)

步骤 3.4:启动容器

容器创建成功后,runtime manager 调用 StartContainer 启动容器。此时容器开始执行其 entrypoint。

通信方向:kubeGenericRuntimeManager → CRI RuntimeService(gRPC)

步骤 3.5:停止容器(终止路径)

在终止路径中,runtime manager 调用 StopContainer,先发送 SIGTERM,等待优雅终止期后发送 SIGKILL。

通信方向:kubeGenericRuntimeManager → CRI RuntimeService(gRPC)

步骤 3.6:停止 sandbox(终止路径)

所有容器停止后,runtime manager 调用 StopPodSandbox 停止 sandbox,释放网络等资源。

通信方向:kubeGenericRuntimeManager → CRI RuntimeService(gRPC)


小结:这条链路的 3 个关键通信边界

边界 谁和谁通信 通过什么方式
PodConfig → podManager 聚合多个 source 的更新 Go channel
podWorkers → runtime manager 驱动状态机,发起操作意图 函数调用
kubelet → container runtime 执行具体的容器操作 CRI gRPC

核心结论:kubelet 不直接调用 containerd/docker 私有 API,而是通过 CRI 标准接口与容器运行时通信。


2.5 完整流程图

下面这张图把本文所有步骤串在一起,方便一眼看完整个通信链路:

flowchart TD
    subgraph 阶段一["阶段一:从 Pod source 到内部子系统"]
        A1[apiserver source] --> B[PodConfig]
        A2[file source] --> B
        A3[HTTP source] --> B
        B --> C[merge and dedup]
        C --> D[podManager 更新本地视图]
        D --> E[podWorkers.UpdatePod]
    end

    subgraph 阶段二["阶段二:podWorkers 驱动状态机"]
        E --> F{判断状态}
        F -->|SyncPod| G[准备存储卷]
        G --> H[拉取 secrets/configmaps]
        H --> I[准备 sandbox]
        I --> J[创建并启动容器]
        F -->|SyncTerminatingPod| K[发送 SIGTERM]
        K --> L{优雅终止期超时?}
        L -->|是| M[发送 SIGKILL]
        L -->|否| N[等待容器退出]
        F -->|SyncTerminatedPod| O[卸载存储卷]
        O --> P[清理 sandbox]
        P --> Q[从 podManager 移除]
    end

    subgraph 阶段三["阶段三:通过 CRI 执行容器操作"]
        I --> R[CreatePodSandbox gRPC]
        J --> S[PullImage gRPC]
        J --> T[CreateContainer gRPC]
        J --> U[StartContainer gRPC]
        K --> V[StopContainer gRPC]
        M --> V
        P --> W[StopPodSandbox gRPC]
    end

    subgraph 外部系统["外部系统"]
        R --> X[Container Runtime]
        S --> X
        T --> X
        U --> X
        V --> X
        W --> X
    end

2.6 与其他组件的交互

这条链路不只是 kubelet 内部的事,还涉及多个外部系统的协作:

与 API Server 的交互

交互点 方向 目的
apiserver source API Server → kubelet 接收 Pod 更新
secretManager kubelet → API Server 拉取 Pod 所需的 secrets
configMapManager kubelet → API Server 拉取 Pod 所需的 configmaps
statusManager kubelet → API Server 回传 Pod 运行状态

关键理解:kubelet 不是完全自治的,它需要频繁与 API Server 交互来获取配置和回传状态。

与 CSI(Container Storage Interface)的交互

当 Pod 声明了 PersistentVolume 时,kubelet 需要与 CSI driver 协作:

flowchart LR
    A[VolumeManager] --> B[CSI driver]
    B --> C[Attach volume]
    C --> D[Mount to node]
    D --> E[Bind mount to container]
步骤 说明
Attach 把远程存储卷附加到节点(对于云盘等)
Mount 把卷挂载到节点的指定目录
Bind mount 把节点目录绑定挂载到容器内

关键理解:VolumeManager.WaitForAttachAndMount 会阻塞 SyncPod,直到卷准备好。如果 CSI driver 响应慢,Pod 启动也会变慢。

与 CNI(Container Network Interface)的交互

虽然 CNI 调用不是 kubelet 直接发起的,但 sandbox 创建时会触发网络插件配置:

flowchart LR
    A[CreatePodSandbox] --> B[container runtime]
    B --> C[CNI plugin]
    C --> D[配置网络命名空间]
    D --> E[分配 IP 地址]

关键理解:sandbox 创建成功后,Pod 的网络就绪。后续所有容器共享这个网络命名空间。

与镜像仓库的交互

PullImage 调用会让容器运行时从镜像仓库拉取镜像:

flowchart LR
    A[PullImage gRPC] --> B[container runtime]
    B --> C{镜像是否存在?}
    C -->|否| D[从仓库拉取]
    C -->|是| E[快速返回]
    D --> F[解压并存储]

关键理解:如果镜像仓库不可达或镜像不存在,PullImage 会失败,导致 Pod 进入 ImagePullBackOff

与探针系统的交互

容器启动后,probeManager 会接管健康检查:

flowchart LR
    A[容器启动成功] --> B[probeManager]
    B --> C{liveness probe}
    B --> D{readiness probe}
    B --> E[startup probe}
    C --> F[livenessManager]
    D --> G[readinessManager]
    E --> H[startupManager]
    F --> I[statusManager]
    G --> I
    H --> I

关键理解:探针结果会通过 probe result manager 传递给 statusManager,最终影响 Pod 的 Ready 条件。

交互总结表

外部系统 交互方式 影响什么
API Server informer / client-go Pod 配置获取、状态回传
CSI driver gRPC / 本地调用 存储卷 attach/mount
CNI plugin 由 runtime 调用 网络命名空间配置
镜像仓库 由 runtime 访问 镜像拉取
Probe 系统 kubelet 内部 容器健康状态

Mermaid:主时序图

sequenceDiagram
    participant PodSource
    participant PodManager
    participant PodWorkers
    participant RuntimeMgr as kubeGenericRuntimeManager
    participant CRI
    participant Runtime

    PodSource-->>PodManager: Pod update
    PodManager-->>PodWorkers: UpdatePod(...)
    PodWorkers->>PodWorkers: decide SyncPod / TerminatingPod
    alt SyncPod path
        PodWorkers->>RuntimeMgr: SyncPod(...)
        RuntimeMgr->>CRI: CreatePodSandbox
        CRI->>Runtime: gRPC request
        Runtime-->>CRI: sandbox ID
        RuntimeMgr->>CRI: PullImage / CreateContainer / StartContainer
        CRI->>Runtime: gRPC requests
        Runtime-->>CRI: container states
    else TerminatingPod path
        PodWorkers->>RuntimeMgr: SyncTerminatingPod(...)
        RuntimeMgr->>CRI: StopContainer / StopPodSandbox
    end
    RuntimeMgr-->>PodWorkers: sync result

时序图里的 3 个关键点

  • podManager 不直接调用 CRI,它只管理元数据。
  • podWorkers 决定走哪条状态机路径,而不是 runtime manager。
  • 所有与容器运行时的交互都必须经过 CRI。

4. 异常与分支路径

Mermaid:异常/分支路径图

flowchart TD
    A[Pod update received] --> B{podWorkers decide state}
    B -->|SyncPod| C[prepare volumes and secrets]
    C --> D{sandbox creation succeeds?}
    D -->|No| E[retry SyncPod later]
    D -->|Yes| F{image pull succeeds?}
    F -->|No| G[ImagePullBackOff / retry]
    F -->|Yes| H{container start succeeds?}
    H -->|No| I[CrashLoopBackOff / restart policy]
    H -->|Yes| J[probe manager begins health check]
    B -->|TerminatingPod| K[SyncTerminatingPod]
    K --> L{graceful stop succeeds?}
    L -->|No| M[kill after grace period]
    L -->|Yes| N[SyncTerminatedPod cleanup]

读这张图时要关注什么

  • sandbox 创建失败:整个 SyncPod 会重试,而不是跳过 sandbox。
  • 镜像拉取失败:会进入 ImagePullBackOff,由 kubelet 根据重启策略决定后续行为。
  • 容器启动失败:可能进入 CrashLoopBackOff,而不是立即放弃。
  • 优雅终止超时:会强制 kill,而不是一直等。

5. 这个流程里谁负责什么

组件 主要职责 不负责什么
podManager 维护 Pod 本地期望态视图 不直接调用 CRI
podWorkers 驱动单 Pod 生命周期状态机 不执行具体容器操作
kubeGenericRuntimeManager 把操作意图翻译成 CRI 调用 不决定 Pod 应该处于哪个状态
CRI RuntimeService 管理 sandbox 和容器生命周期 不理解 Pod 语义
CRI ImageService 管理镜像拉取和删除 不理解容器运行语义

6. 常见误解

误解 1:kubelet 直接调用 containerd/docker 私有 API

不是。标准链路是通过 CRI。

误解 2:Pod 落地是一次函数调用完成的

不是。podWorkers 管的是单 Pod 生命周期状态机,会多轮收敛。

误解 3:sandbox 可以省略

不能。Pod 不是直接从 spec 变成业务容器,中间必须有 pod sandbox。

误解 4:podManager 负责执行容器操作

不是。podManager 只负责索引与期望态视图,执行发生在 runtime manager。


7. 版本差异说明

这条链路在 Kubernetes 1.27/1.28/1.29 中有一些值得注意的变化:

1.27 → 1.28 变化

变化点 说明
Sidecar Containers (Beta) init container 支持 restartPolicy: Always,作为 sidecar 运行
用户命名空间 (Alpha) 支持容器内用户与宿主机用户隔离,影响 sandbox 创建
Kubelet Evented PLEG (Beta) 可选的事件驱动 PLEG,减少 relist 开销

1.28 → 1.29 变化

变化点 说明
init container 重启修复 修复了 Always restartPolicy 的 init container 可能无法优雅终止的问题
终止 Pod 不重新接纳 kubelet 重启后,已终止的 Pod 不会被重新处理,避免状态混乱
临时容器改进 临时容器(ephemeral container)的创建流程更稳定

向后兼容性

版本 兼容性注意
1.27+ Sidecar containers 需要容器运行时支持
1.28+ 用户命名空间需要容器运行时和操作系统支持
1.29+ 终止 Pod 不重新接纳可能影响依赖重启行为的逻辑

需要关注的废弃

废弃项 替代方案 移除版本
--container-runtime flag 已删除,只支持 CRI 1.24 已移除
Dockershim 使用 containerd 或 CRI-O 1.24 已移除

7. 与后续文档的关系

建议按这个顺序阅读:

  1. docs/pod-scheduling-and-binding-flow.md
  2. 本文:理解 kubelet 如何把期望态变成运行态
  3. docs/kubelet-status-and-event-feedback-flow.md:理解运行结果如何回到控制面

8. 代码入口(精简版)

如果读者想从流程跳回实现,可从下面几个入口开始:


9. 详细时序图

9.1 Pod 创建完整时序图

sequenceDiagram
    autonumber
    participant API as kube-apiserver
    participant KL as Kubelet
    participant PC as PodConfig
    participant PM as podManager
    participant PW as podWorkers
    participant VM as volumeManager
    participant SM as secretManager
    participant RM as runtimeManager
    participant CRI as CRI Runtime
    participant RT as Container Runtime

    API-->>KL: watch 推送 Pod ADDED
    KL->>PC: Pod 更新进入 channel
    PC->>PC: 聚合多 source 更新
    PC-->>KL: PodUpdate{Op: ADD}

    KL->>PM: UpdatePods(pods)
    PM->>PM: 更新 podByUID 索引
    PM->>PM: 更新 podByFullName 索引

    PM-->>PW: UpdatePod(options)
    PW->>PW: 获取/创建 podWorker goroutine

    Note over PW: 状态机判断 -> SyncPod

    PW->>PW: SyncPod(ctx, pod, ...)

    Note over PW,VM: 阶段1: 准备卷
    PW->>VM: WaitForAttachAndMount(pod)
    VM->>VM: 等待卷 attach/mount
    VM-->>PW: 卷就绪

    Note over PW,SM: 阶段2: 拉取 secrets
    PW->>SM: GetSecrets(pod)
    SM->>API: GET /api/v1/secrets/...
    API-->>SM: Secret 数据
    SM-->>PW: secrets 返回

    Note over PW,RM: 阶段3: 创建 sandbox
    PW->>RM: SyncPod(pod, ...)
    RM->>RM: computePodActions()
    RM->>CRI: CreatePodSandbox(podSandboxConfig)
    CRI->>RT: 创建 sandbox 容器
    RT-->>CRI: sandboxID
    CRI-->>RM: sandboxID

    Note over RM: CNI 配置网络

    Note over PW,RM: 阶段4: 创建 init 容器
    loop 每个 init 容器
        RM->>CRI: PullImage(image)
        CRI->>RT: 拉取镜像
        RT-->>CRI: success
        RM->>CRI: CreateContainer(sandboxID, config)
        CRI->>RT: 创建容器
        RT-->>CRI: containerID
        RM->>CRI: StartContainer(containerID)
        CRI->>RT: 启动容器
        RT-->>CRI: success
        RM->>RM: 等待 init 容器完成
    end

    Note over PW,RM: 阶段5: 创建主容器
    loop 每个主容器
        RM->>CRI: PullImage(image)
        CRI->>RT: 拉取镜像
        RT-->>CRI: success
        RM->>CRI: CreateContainer(sandboxID, config)
        CRI->>RT: 创建容器
        RT-->>CRI: containerID
        RM->>CRI: StartContainer(containerID)
        CRI->>RT: 启动容器
        RT-->>CRI: success
    end

    RM-->>PW: SyncPod 完成
    PW->>PW: 更新 Pod 状态

9.2 Pod 优雅终止时序图

sequenceDiagram
    autonumber
    participant User as 用户
    participant API as kube-apiserver
    participant KL as Kubelet
    participant PW as podWorkers
    participant PM as probeManager
    participant RM as runtimeManager
    participant CRI as CRI Runtime
    participant RT as Container Runtime

    User->>API: DELETE /api/v1/pods/<pod>
    API->>API: 设置 deletionTimestamp
    API-->>KL: watch 推送 Pod UPDATE

    KL->>PW: UpdatePod(options)
    PW->>PW: 检测到 deletionTimestamp

    Note over PW: 状态机判断 -> SyncTerminatingPod

    PW->>PW: SyncTerminatingPod(ctx, pod, ...)

    Note over PW,PM: 停止探针
    PW->>PM: StopLivenessProbe(pod)
    PW->>PM: StopReadinessProbe(pod)
    PM-->>PW: 探针已停止

    Note over PW,RM: 发送 SIGTERM
    PW->>RM: KillPod(pod, gracePeriod)
    RM->>CRI: StopContainer(containerID, timeout)

    loop 每个容器
        CRI->>RT: 发送 SIGTERM
        RT->>RT: 等待优雅终止

        alt 容器正常退出
            RT-->>CRI: 退出码 0
        else 超时
            CRI->>RT: 发送 SIGKILL
            RT-->>CRI: 被强制终止
        end
    end

    RM-->>PW: 所有容器已停止
    PW->>PW: 状态转换 -> Terminated

    Note over PW: 状态机判断 -> SyncTerminatedPod

    PW->>PW: SyncTerminatedPod(ctx, pod, ...)

    Note over PW: 清理资源
    PW->>RM: RemovePodSandbox(sandboxID)
    RM->>CRI: RemovePodSandbox(sandboxID)
    CRI->>RT: 删除 sandbox
    RT-->>CRI: success

    PW->>PW: 从 podManager 移除
    PW-->>KL: Pod 清理完成

    KL->>API: PATCH Pod status (phase=Failed/Succeeded)
    API->>API: 从 etcd 删除 Pod

9.3 容器重启时序图

sequenceDiagram
    autonumber
    participant PLEG as PLEG
    participant PW as podWorkers
    participant RM as runtimeManager
    participant CRI as CRI Runtime
    participant RT as Container Runtime

    Note over PLEG: 定期 relist 容器状态
    PLEG->>CRI: ListContainers()
    CRI->>RT: 列出容器
    RT-->>CRI: 容器列表
    CRI-->>PLEG: 容器状态

    PLEG->>PLEG: 对比上次状态
    PLEG->>PLEG: 检测到容器死亡

    PLEG-->>PW: PodLifecycleEvent{ContainerDied}

    PW->>PW: 触发 SyncPod
    PW->>RM: SyncPod(pod, ...)

    RM->>RM: computePodActions()
    RM->>RM: 检查 restartPolicy

    alt restartPolicy = Always/OnFailure
        RM->>CRI: CreateContainer(...)
        CRI->>RT: 创建新容器
        RT-->>CRI: containerID
        RM->>CRI: StartContainer(containerID)
        CRI->>RT: 启动容器
        RT-->>CRI: success
    else restartPolicy = Never
        RM->>RM: 不重启,标记容器终止
    end

    RM-->>PW: SyncPod 完成

10. 故障排查指南

10.1 常见问题与诊断方法

问题 1:Pod 一直处于 ContainerCreating 状态

症状

1
2
3
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-1 0/1 ContainerCreating 0 5m

排查流程图

flowchart TD
    A[ContainerCreating 卡住] --> B{检查 Pod 事件}

    B --> B1[kubectl describe pod]

    B1 --> C{卡在哪个阶段?}

    C -->|ContainerCreating| D{镜像拉取问题}
    D --> D1[ImagePullBackOff]
    D --> D2[ErrImagePull]
    D1 --> E[检查镜像名称/权限]
    D2 --> E

    C -->|PodInitializing| F{Init 容器问题}
    F --> F1[检查 init 容器日志]
    F --> F2[检查 init 容器状态]

    C -->|WaitForAttachAndMount| G{存储卷问题}
    G --> G1[检查 PVC 状态]
    G --> G2[检查 PV 绑定]
    G --> G3[检查 CSI driver]

    C -->|网络配置| H{CNI 问题}
    H --> H1[检查 CNI 插件]
    H --> H2[检查网络配置]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看 Pod 详细事件
kubectl describe pod <pod-name> -n <namespace>

# 查看容器状态
crictl ps -a | grep <pod-name>

# 查看容器日志
crictl logs <container-id>

# 查看镜像列表
crictl images

# 检查存储卷挂载
kubectl get pvc -n <namespace>
lsblk
mount | grep <pvc-name>

# 检查 kubelet 日志
journalctl -u kubelet | grep <pod-name>

问题 2:容器频繁重启 (CrashLoopBackOff)

症状

1
2
3
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-1 0/1 CrashLoopBackOff 5 10m

排查流程图

flowchart TD
    A[CrashLoopBackOff] --> B{检查退出码}

    B -->|0| C[正常退出但 restartPolicy=Always]
    C --> C1[检查应用逻辑]

    B -->|1-127| D[应用错误]
    D --> D1[检查应用日志]
    D --> D2[检查配置]

    B -->|137| E[OOMKilled]
    E --> E1[增加内存限制]
    E --> E2[检查内存泄漏]

    B -->|139| F[Segmentation Fault]
    F --> F1[检查应用代码]

    B -->|143| G[SIGTERM 接收]
    G --> G1[检查优雅终止处理]

    A --> H{检查探针}
    H -->|Liveness 失败| I[健康检查失败]
    I --> I1[检查 liveness probe 配置]
    I --> I2[检查应用健康端点]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看容器退出码
kubectl describe pod <pod-name> -n <namespace> | grep -A 5 "Last State"

# 查看容器日志 (上一个容器)
kubectl logs <pod-name> -n <namespace> --previous

# 查看容器日志 (当前容器)
kubectl logs <pod-name> -n <namespace>

# 在节点上检查
crictl inspect <container-id> | grep -A 10 "exitCode"

# 检查 OOM
dmesg | grep -i oom

问题 3:Pod 终止卡住

症状:执行 kubectl delete pod 后 Pod 一直处于 Terminating 状态

排查流程图

flowchart TD
    A[Pod Terminating 卡住] --> B{检查 finalizers}

    B -->|有 finalizers| C[等待 finalizer 处理]
    C --> C1[检查 finalizer 类型]
    C1 --> C2[检查相关控制器状态]

    B -->|无 finalizers| D{检查 kubelet}

    D --> E{节点可达?}
    E -->|否| F[节点故障]
    F --> F1[修复节点或强制删除]

    E -->|是| G{检查容器状态}
    G --> G1[crictl ps -a]

    G1 --> H{容器在运行?}
    H -->|是| I[检查容器是否响应 SIGTERM]
    I --> I1[检查应用信号处理]

    H -->|否| J[检查 kubelet 日志]
    J --> J1[查找清理错误]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 检查 Pod finalizers
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.metadata.finalizers}'

# 检查节点状态
kubectl get node <node-name>

# 在节点上检查容器
crictl ps -a | grep <pod-name>

# 强制终止容器
crictl stop <container-id>
crictl rm <container-id>

# 强制删除 Pod (最后手段)
kubectl delete pod <pod-name> -n <namespace> --force --grace-period=0

# 检查 kubelet 日志
journalctl -u kubelet | grep <pod-name>

10.2 关键日志关键词

场景 日志关键词 含义
Pod 同步 SyncPod 开始 Pod 同步
Sandbox 创建 CreatePodSandbox 创建沙箱
镜像拉取 PullImage 拉取镜像
容器创建 CreateContainer 创建容器
容器启动 StartContainer 启动容器
容器停止 StopContainer 停止容器
优雅终止 graceful termination 优雅终止
卷挂载 WaitForAttachAndMount 等待卷挂载
探针失败 Probe failed 健康检查失败

10.3 配置参数参考

参数 默认值 说明
--image-pull-progress-deadline 1m 镜像拉取进度超时
--minimum-container-ttl-duration 0s 容器垃圾回收间隔
--maximum-dead-containers -1 最大死亡容器数
--eviction-hard memory.available<100Mi 硬驱逐阈值
--pod-eviction-timeout 5m Pod 驱逐超时

10.4 一键诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Pod 问题诊断脚本
POD_NAME=${1:-"<pod-name>"}
NAMESPACE=${2:-"default"}

echo "=== Pod Status ===" && \
kubectl get pod $POD_NAME -n $NAMESPACE -o wide && \
echo -e "\n=== Pod Events ===" && \
kubectl describe pod $POD_NAME -n $NAMESPACE | grep -A 20 "Events:" && \
echo -e "\n=== Container Status ===" && \
kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.status.containerStatuses}' | jq . && \
echo -e "\n=== Recent Logs ===" && \
kubectl logs $POD_NAME -n $NAMESPACE --tail=50 && \
echo -e "\n=== Previous Container Logs (if any) ===" && \
kubectl logs $POD_NAME -n $NAMESPACE --previous --tail=20 2>/dev/null || echo "No previous logs"

11. 面试题与详细解答

11.1 基础概念题

Q1: kubelet 是如何知道要运行哪些 Pod 的?

答案

kubelet 通过三种 Pod 来源(Pod Source)获取 Pod 信息,由 PodConfig 聚合后统一处理:

1. API Server Source(主要来源)

1
2
3
4
5
// pkg/kubelet/kubelet.go:283
func makePodSourceConfig(...) (*config.PodConfig, error) {
// 通过 informer/watch 监听 API Server
// 过滤条件: spec.nodeName == 本节点名称
}
  • 使用 client-go 的 informer 机制
  • 只 watch 分配给本节点的 Pod
  • 支持增量更新(ADD/UPDATE/DELETE)

2. File Source(静态 Pod)

1
2
// 从 --pod-manifest-path 指定的目录读取
// 定期扫描文件变化
  • 用于运行静态 Pod(如 control plane 组件)
  • Pod 定义以 YAML/JSON 文件形式存在
  • kubelet 定期扫描目录,检测文件变化

3. HTTP Source

1
2
// 从 --manifest-url 指定的 URL 获取
// 定期 HTTP GET 请求
  • 较少使用,用于特定场景

聚合处理

1
2
3
4
5
6
// pkg/kubelet/config/config.go
type PodConfig struct {
// 聚合三个 source 的更新
// 通过 channel 统一对外提供
updates <-chan kubetypes.PodUpdate
}

Q2: 什么是 Pod Sandbox?为什么需要它?

答案

Pod Sandbox(沙箱) 是 Pod 中所有容器共享的基础运行环境,在创建任何业务容器之前必须先创建。

Sandbox 的作用

  1. 网络命名空间

    • Sandbox 创建 Pod 的网络命名空间
    • 所有业务容器共享同一个网络栈
    • 容器间可通过 localhost 通信
  2. IPC 命名空间

    • 共享 IPC 命名空间
    • 容器间可通过 System V IPC 通信
  3. 基础设施容器

    • 持有 Pod 的网络配置
    • 即使业务容器重启,网络配置保持不变

创建流程

1
2
3
4
5
6
// pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
func (m *kubeGenericRuntimeManager) createPodSandbox(...) (string, error) {
// 1. 构建 sandbox 配置
// 2. 调用 CRI: RunPodSandbox
// 3. 触发 CNI 配置网络
}

CRI 接口

1
2
3
4
// RuntimeService
rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}

containerd 实现

  • Pause 容器作为 sandbox
  • 镜像:registry.k8s.io/pause:3.9
  • 持有网络命名空间

11.2 进阶原理题

Q3: podWorkers 的状态机是如何工作的?

答案

podWorkers 为每个 Pod 维护独立的状态机,驱动 Pod 生命周期转换。

状态机图

stateDiagram-v2
    [*] --> SyncPod: Pod 创建/更新
    SyncPod --> Running: 容器启动成功
    SyncPod --> SyncPod: 启动失败,重试
    Running --> SyncTerminatingPod: 收到删除请求
    SyncTerminatingPod --> SyncTerminatedPod: 优雅终止完成
    SyncTerminatingPod --> SyncTerminatedPod: 超时,强制终止
    SyncTerminatedPod --> [*]: 清理完成

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// pkg/kubelet/pod_workers.go:735
func (p *podWorkers) UpdatePod(options UpdatePodOptions) {
// 获取/创建 podWorker goroutine
// 每个 Pod 一个独立的工作协程

// 状态判断
if isPodTerminated(pod) {
// 已终止 Pod
p.syncTerminatedPod(pod)
} else if pod.DeletionTimestamp != nil {
// 需要终止的 Pod
p.syncTerminatingPod(pod)
} else {
// 正常同步
p.syncPod(pod)
}
}

三种同步方法

方法 触发条件 主要操作
SyncPod Pod 创建/更新 创建 sandbox、挂载卷、启动容器
SyncTerminatingPod DeletionTimestamp 设置 发送 SIGTERM、等待优雅终止
SyncTerminatedPod 容器全部停止 清理卷、删除 sandbox、更新状态

并发控制

  • 每个 Pod 独立的 goroutine
  • 使用 channel 串行化同一 Pod 的更新
  • 支持重入和幂等

Q4: kubelet 如何处理容器的优雅终止?

答案

优雅终止流程

sequenceDiagram
    participant API as API Server
    participant KL as Kubelet
    participant PW as podWorkers
    participant RM as RuntimeManager
    participant CRI as CRI Runtime
    participant CTR as Container

    API->>KL: Pod DeletionTimestamp 设置
    KL->>PW: UpdatePod

    Note over PW: 状态机 -> SyncTerminatingPod

    PW->>RM: SyncTerminatingPod

    Note over RM: 1. 停止探针
    RM->>RM: probeManager.StopLivenessProbe()

    Note over RM: 2. 发送 SIGTERM
    loop 每个容器
        RM->>CRI: StopContainer(timeout=gracePeriod)
        CRI->>CTR: SIGTERM
    end

    Note over CTR: 等待优雅终止期 (默认30s)

    alt 容器正常退出
        CTR-->>CRI: exit 0
    else 超时
        CRI->>CTR: SIGKILL (强制终止)
    end

    RM-->>PW: 终止完成

    Note over PW: 状态机 -> SyncTerminatedPod

    PW->>RM: SyncTerminatedPod
    RM->>CRI: RemovePodSandbox

关键参数

1
2
3
4
5
6
7
8
spec:
terminationGracePeriodSeconds: 30 # 优雅终止期
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "cleanup.sh"] # preStop hook

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pkg/kubelet/kuberuntime/kuberuntime_container.go
func (m *kubeGenericRuntimeManager) killContainer(
pod *v1.Pod,
containerID kubecontainer.ContainerID,
containerName string,
gracePeriodOverride *int64,
reason string,
message string,
) error {
// 1. 执行 preStop hook(如果有)
// 2. 发送 SIGTERM
// 3. 等待 gracePeriod
// 4. 超时则发送 SIGKILL
}

注意事项

  • preStop hook 在 SIGTERM 之前执行
  • hook 执行时间计入 gracePeriod
  • 如果 hook 超时,直接 SIGKILL

11.3 故障排查题

Q5: 容器一直重启 (CrashLoopBackOff),如何排查?

答案

排查流程图

flowchart TD
    A[CrashLoopBackOff] --> B{检查退出码}

    B -->|0| C[正常退出但 restartPolicy=Always]
    C --> C1[检查应用逻辑]

    B -->|1-127| D[应用错误]
    D --> D1[检查应用日志]
    D --> D2[检查配置]

    B -->|137| E[OOMKilled]
    E --> E1[增加内存限制]
    E --> E2[检查内存泄漏]

    B -->|139| F[Segmentation Fault]
    F --> F1[检查应用代码]

    B -->|143| G[SIGTERM 接收]
    G --> G1[检查优雅终止处理]

    A --> H{检查探针}
    H -->|Liveness 失败| I[健康检查失败]
    I --> I1[检查 liveness probe 配置]
    I --> I2[检查应用健康端点]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 查看容器状态和退出码
kubectl describe pod <pod-name> | grep -A 10 "Last State"
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState}'

# 2. 查看应用日志
kubectl logs <pod-name> --previous # 上一个容器的日志
kubectl logs <pod-name> -c <container-name> --tail=100

# 3. 检查 OOM
dmesg | grep -i "killed process"
kubectl describe node | grep -i oom

# 4. 检查探针配置
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].livenessProbe}'

# 5. 进入容器调试
kubectl exec -it <pod-name> -- /bin/sh

退出码含义

退出码 含义 排查方向
0 正常退出 检查 restartPolicy
1 应用错误 检查日志、配置
126 命令无法执行 检查权限、路径
127 命令未找到 检查镜像、路径
128+N 信号 N 终止 检查信号处理
137 SIGKILL (128+9) OOMKilled 或强制终止
139 SIGSEGV (128+11) 段错误
143 SIGTERM (128+15) 正常终止信号

11.4 设计思想题

Q6: kubelet 为什么要用 CRI (Container Runtime Interface)?

答案

CRI 设计目的:解耦 kubelet 与容器运行时,实现运行时可插拔。

设计优势

  1. 运行时多样性

    • 支持 containerd、CRI-O、Docker (已废弃) 等
    • 用户可根据需求选择
    • 运行时可独立升级
  2. 标准化接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // CRI 定义了两组服务
    service RuntimeService {
    // 容器生命周期管理
    rpc CreateContainer(...)
    rpc StartContainer(...)
    rpc StopContainer(...)
    rpc RemoveContainer(...)

    // Pod Sandbox 管理
    rpc RunPodSandbox(...)
    rpc StopPodSandbox(...)
    rpc RemovePodSandbox(...)
    }

    service ImageService {
    // 镜像管理
    rpc PullImage(...)
    rpc RemoveImage(...)
    rpc ListImages(...)
    }
  3. 隔离变化

    • kubelet 代码不依赖具体运行时实现
    • 运行时变更不影响 kubelet
    • 便于测试和 mock

架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────┐
│ kubelet │
│ ┌───────────────────────────┐ │
│ │ kubeGenericRuntimeManager│ │
│ └───────────┬───────────────┘ │
│ │ gRPC │
│ ┌───────────▼───────────────┐ │
│ │ CRI Shim (remote) │ │
│ └───────────┬───────────────┘ │
└──────────────│──────────────────┘

┌──────────▼──────────┐
│ containerd / │
│ CRI-O │
└─────────────────────┘

对比非 CRI 架构

  • DockerShim 时代:kubelet 直接调用 Docker API
  • 问题:Docker 私有 API 变化导致 kubelet 需要同步修改
  • CRI 后:通过标准 gRPC 接口,双方独立演进

Q7: kubelet 如何保证 Pod 的期望状态与实际状态一致?

答案

控制循环 (Control Loop) 是 kubelet 保证一致性的核心机制。

设计模式:声明式 API + 控制循环

flowchart TD
    subgraph 期望状态["期望状态 (Desired State)"]
        A1[Pod Spec]
        A2[podManager 维护]
    end

    subgraph 实际状态["实际状态 (Actual State)"]
        B1[容器状态]
        B2[PLEG 检测]
    end

    subgraph 控制循环["控制循环 (Reconcile Loop)"]
        C1[对比差异]
        C2[执行操作]
        C3[更新状态]
    end

    A1 --> C1
    B1 --> C1
    C1 --> C2
    C2 --> C3
    C3 --> B1

实现机制

  1. PLEG (Pod Lifecycle Event Generator)

    1
    2
    3
    4
    // pkg/kubelet/pleg/generic.go
    // 定期 relist 容器状态
    // 生成 PodLifecycleEvent
    // 触发 SyncPod
  2. 定期同步

    1
    2
    3
    // pkg/kubelet/kubelet.go
    // --sync-frequency 默认 1 分钟
    // 定期检查所有 Pod 是否需要同步
  3. 幂等操作

    1
    2
    3
    // SyncPod 是幂等的
    // 多次调用结果一致
    // 根据当前状态决定操作

状态对比逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (m *kubeGenericRuntimeManager) SyncPod(...) {
// 1. 获取当前容器状态
podStatus := m.podCache.Get(pod.UID)

// 2. 计算需要执行的操作
actions := computePodActions(pod, podStatus)

// 3. 执行差异操作
if actions.NeedCreateSandbox {
m.createPodSandbox()
}
if actions.ContainersToStart != nil {
for _, c := range actions.ContainersToStart {
m.startContainer(c)
}
}
if actions.ContainersToKill != nil {
for _, c := range actions.ContainersToKill {
m.killContainer(c)
}
}
}

最终一致性保证

  • 即使中间状态不一致,控制循环会持续收敛
  • 网络抖动、临时故障不影响最终结果
  • 符合 Kubernetes 声明式 API 设计理念