06-Kubelet 源码解析:从启动到 Pod 管理与指标监控

本文基于1.29.0版本

Kubelet 源码解析:从启动到 Pod 管理与指标监控

摘要

Kubelet 是 Kubernetes 集群中不可或缺的节点代理,负责管理 Pod 的生命周期、监控资源并确保节点上的容器按照预期状态运行。本文旨在通过对 Kubelet 源码的深度剖析,揭示其核心工作机制,内容涵盖从启动初始化、核心工作循环、Pod 生命周期管理到基于 cAdvisor 的指标监控全流程。通过对这些关键模块的分析,我们将构建一个关于 Kubelet 如何作为节点"管家"维持集群稳定性的完整视图。

1. Kubelet 的职责与定位

在 Kubernetes 的宏伟蓝图中,Kubelet 扮演着连接"大脑"(Master 节点)与"四肢"(工作节点)的关键角色。它的核心职责可以概括为:

  • Pod 管理:作为 Pod 生命周期的直接执行者,负责根据 kube-apiserver 的指令或静态配置文件创建、销毁和管理 Pod。
  • 健康检查:通过 Liveness 和 Readiness 探针持续监控容器的健康状况,并执行重启等恢复策略。
  • 资源监控:利用内嵌的 cAdvisor 收集节点和容器的资源使用情况(CPU、内存、磁盘、网络),为 metrics-server 和 HPA 提供数据基础。
  • 存储卷管理:为 Pod 动态挂载和卸载所需的存储卷。
  • 状态上报:定期向 kube-apiserver 汇报节点状态、资源容量和运行中的 Pod 列表。

2. 启动流程:从 main 函数到 syncLoop

Kubelet 的启动过程是一个精心设计的初始化序列,它通过命令行解析、配置加载和依赖注入,最终构建出一个功能完备的 Kubelet 实例。

2.1. 入口与命令构建

Kubelet 的生命始于 cmd/kubelet/kubelet.gomain 函数,它通过调用 app.NewKubeletCommand() 创建一个 Cobra 命令行应用。这个过程封装了所有启动逻辑,包括参数解析和服务的最终运行。

1
2
3
4
5
6
// cmd/kubelet/kubelet.go
func main() {
    command := app.NewKubeletCommand()
    code := cli.Run(command)
    os.Exit(code)
}

2.2. 依赖注入与初始化

NewKubeletCommand 的核心在于其 Run 函数,它负责协调整个初始化过程。关键步骤如下:

  1. 构建依赖 (KubeletDeps):通过 UnsecuredDependencies 创建 Kubelet 运行所需的核心依赖项,如 CAdvisorInterfaceContainerManagerStatsProvider 等。
  2. 创建 Kubelet 实例RunKubelet -> createAndInitKubelet -> kubelet.NewMainKubelet,这一系列调用最终创建并初始化了 Kubelet 核心对象。
  3. 启动核心循环:初始化完成后,Kubelet 启动其"心脏"——syncLoop
graph TD
    A[main] --> B[app.NewKubeletCommand];
    B --> C{创建 Flags 和 Config};
    B --> D[创建 cobra.Command];
    D -- 执行 --> E[RunE 函数];
    E --> F[构建 KubeletServer];
    F --> G[UnsecuredDependencies -> 创建 KubeletDeps];
    G --> H[RunKubelet];
    H --> I[createAndInitKubelet];
    I --> J[kubelet.NewMainKubelet];
    J --> K[返回初始化的 Kubelet 实例];
    K --> L[启动 syncLoop];
graph TD
    A[main] --> B[app.NewKubeletCommand];
    B --> C{创建 Flags 和 Config};
    B --> D[创建 cobra.Command];
    D -- 执行 --> E[RunE 函数];
    E --> F[构建 KubeletServer];
    F --> G[UnsecuredDependencies -> 创建 KubeletDeps];
    G --> H[RunKubelet];
    H --> I[createAndInitKubelet];
    I --> J[kubelet.NewMainKubelet];
    J --> K[返回初始化的 Kubelet 实例];
    K --> L[启动 syncLoop];
graph TD
    A[main] --> B[app.NewKubeletCommand];
    B --> C{创建 Flags 和 Config};
    B --> D[创建 cobra.Command];
    D -- 执行 --> E[RunE 函数];
    E --> F[构建 KubeletServer];
    F --> G[UnsecuredDependencies -> 创建 KubeletDeps];
    G --> H[RunKubelet];
    H --> I[createAndInitKubelet];
    I --> J[kubelet.NewMainKubelet];
    J --> K[返回初始化的 Kubelet 实例];
    K --> L[启动 syncLoop];

3. 核心工作循环:syncLoop 的事件驱动模型

syncLoop 是 Kubelet 的主控制循环,它通过 syncLoopIteration 函数以事件驱动的方式工作,确保 Pod 的"期望状态"与节点的"实际状态"保持同步。

3.1. 多路事件监听

syncLoopIteration 使用 select 语句同时监听来自多个 Channel 的事件,实现了高效的并发处理:

  • configCh (配置变更):接收来自 kube-apiserver、静态 Pod 文件或 HTTP 端点的 Pod 配置更新,触发 HandlePodAdditionsHandlePodUpdates 等操作。
  • plegCh (Pod 生命周期事件):PLEG (Pod Lifecycle Event Generator) 监控容器运行时的底层变化(如容器启动、停止),生成事件以驱动 Kubelet 快速响应。
  • syncCh (定期同步):默认每秒触发一次,确保即使没有外部事件,Kubelet 也会定期检查所有 Pod 的状态。
  • housekeepingCh (定期清理):执行周期性任务,如清理孤儿 Pod 和无效的目录。
  • livenessManager.Updates() (存活探针):处理 Liveness Probe 的失败结果,通常会导致容器重启。
graph TD
    A[syncLoop] --> B{select};
    B --> C[configCh: 配置变更];
    B --> D[plegCh: 容器状态变更];
    B --> E[syncCh: 定期同步];
    B --> F[housekeepingCh: 定期清理];
    B --> G[livenessManager: 探针结果];

    C --> H[HandlePodAdditions/Updates/Removes];
    D --> I[HandlePodSyncs];
    E --> I;
    G --> I;

    H --> J[podManager: 更新 Pod 缓存];
    J --> K[dispatchWork: 异步同步 Pod];
    I --> K;

    K --> L[syncPod: 同步单个 Pod];
graph TD
    A[syncLoop] --> B{select};
    B --> C[configCh: 配置变更];
    B --> D[plegCh: 容器状态变更];
    B --> E[syncCh: 定期同步];
    B --> F[housekeepingCh: 定期清理];
    B --> G[livenessManager: 探针结果];

    C --> H[HandlePodAdditions/Updates/Removes];
    D --> I[HandlePodSyncs];
    E --> I;
    G --> I;

    H --> J[podManager: 更新 Pod 缓存];
    J --> K[dispatchWork: 异步同步 Pod];
    I --> K;

    K --> L[syncPod: 同步单个 Pod];
graph TD
    A[syncLoop] --> B{select};
    B --> C[configCh: 配置变更];
    B --> D[plegCh: 容器状态变更];
    B --> E[syncCh: 定期同步];
    B --> F[housekeepingCh: 定期清理];
    B --> G[livenessManager: 探针结果];

    C --> H[HandlePodAdditions/Updates/Removes];
    D --> I[HandlePodSyncs];
    E --> I;
    G --> I;

    H --> J[podManager: 更新 Pod 缓存];
    J --> K[dispatchWork: 异步同步 Pod];
    I --> K;

    K --> L[syncPod: 同步单个 Pod];

4. Pod 生命周期管理:syncPod 的同步逻辑

syncPod 是 Kubelet 中负责同步单个 Pod 状态的核心函数。它是一个庞大而复杂的函数,包含了 Pod 从创建到销毁的全套管理逻辑。

4.1. syncPod 的核心步骤

  1. 终止处理:如果 Pod 被标记为删除 (SyncPodKill),则立即调用 killPod 流程。
  2. 状态计算与更新:调用 generateAPIPodStatus 计算 Pod 的最新状态,并通过 statusManager 异步上报给 kube-apiserver
  3. 运行状态检查:通过 podShouldnotBeRunning 判断 Pod 是否已完成或失败,如果是,则执行清理。
  4. 环境准备:创建 Pod 所需的数据目录 (makePodDataDirs) 并等待存储卷挂载就绪 (waitForVolumes)。
  5. 调用容器运行时 (CRI):这是最关键的一步。Kubelet 调用 containerRuntime.SyncPod,将 Pod 的期望状态传递给容器运行时(如 containerd),由其完成具体的容器操作(拉取镜像、创建/销毁容器等)。
1
2
3
4
5
6
7
8
// pkg/kubelet/kubelet.go
func (kl *Kubelet) syncPod(o syncPodOptions) error {
    // ...
    // 核心逻辑:调用容器运行时 (CRI) 的 SyncPod
    result := kl.containerRuntime.SyncPod(pod, podStatus, pullSecrets, kl.backOff)
    // ...
    return nil
}

4.2. Pod 创建、更新与删除流程图

graph TD
    subgraph "事件分发"
        A[dispatchWork] --> B{根据事件类型};
        B -- ADD/UPDATE/SYNC --> C[podWorkers.UpdatePod];
        B -- REMOVE --> C;
    end

    subgraph "Pod 同步执行"
        C --> D["工作协程 (Worker Goroutine)"];
        D --> E(syncPod);
    end

    subgraph "syncPod 内部逻辑"
        E --> F{"检查 Pod 是否需要终止 (deletionTimestamp?)"};
        F -- 是 --> G[执行 killPod 流程];
        F -- 否 --> L{"Pod 是否应该运行?"};
        
        subgraph "终止流程"
            G --> H[CRI: StopPodSandbox/RemovePodSandbox];
            H --> I[清理 Pod 目录和卷];
        end

        L -- 否 --> M["执行 killPod (如果容器仍在运行)"];
        L -- 是 --> O["调用 CRI: SyncPod"];
        
        subgraph "CRI: SyncPod 内部"
            O --> T{"容器运行时"};
            T -- 容器不存在 --> U[创建沙箱和容器];
            T -- 容器状态与期望不符 --> V[停止并重建容器];
            T -- 容器状态一致 --> W[不执行操作];
        end
    end
graph TD
    subgraph "事件分发"
        A[dispatchWork] --> B{根据事件类型};
        B -- ADD/UPDATE/SYNC --> C[podWorkers.UpdatePod];
        B -- REMOVE --> C;
    end

    subgraph "Pod 同步执行"
        C --> D["工作协程 (Worker Goroutine)"];
        D --> E(syncPod);
    end

    subgraph "syncPod 内部逻辑"
        E --> F{"检查 Pod 是否需要终止 (deletionTimestamp?)"};
        F -- 是 --> G[执行 killPod 流程];
        F -- 否 --> L{"Pod 是否应该运行?"};
        
        subgraph "终止流程"
            G --> H[CRI: StopPodSandbox/RemovePodSandbox];
            H --> I[清理 Pod 目录和卷];
        end

        L -- 否 --> M["执行 killPod (如果容器仍在运行)"];
        L -- 是 --> O["调用 CRI: SyncPod"];
        
        subgraph "CRI: SyncPod 内部"
            O --> T{"容器运行时"};
            T -- 容器不存在 --> U[创建沙箱和容器];
            T -- 容器状态与期望不符 --> V[停止并重建容器];
            T -- 容器状态一致 --> W[不执行操作];
        end
    end
graph TD
    subgraph "事件分发"
        A[dispatchWork] --> B{根据事件类型};
        B -- ADD/UPDATE/SYNC --> C[podWorkers.UpdatePod];
        B -- REMOVE --> C;
    end

    subgraph "Pod 同步执行"
        C --> D["工作协程 (Worker Goroutine)"];
        D --> E(syncPod);
    end

    subgraph "syncPod 内部逻辑"
        E --> F{"检查 Pod 是否需要终止 (deletionTimestamp?)"};
        F -- 是 --> G[执行 killPod 流程];
        F -- 否 --> L{"Pod 是否应该运行?"};
        
        subgraph "终止流程"
            G --> H[CRI: StopPodSandbox/RemovePodSandbox];
            H --> I[清理 Pod 目录和卷];
        end

        L -- 否 --> M["执行 killPod (如果容器仍在运行)"];
        L -- 是 --> O["调用 CRI: SyncPod"];
        
        subgraph "CRI: SyncPod 内部"
            O --> T{"容器运行时"};
            T -- 容器不存在 --> U[创建沙箱和容器];
            T -- 容器状态与期望不符 --> V[停止并重建容器];
            T -- 容器状态一致 --> W[不执行操作];
        end
    end

5. 指标监控:cAdvisor 的集成与工作机制

Kubelet 的资源监控能力由内嵌的 cAdvisor 提供。cAdvisor 负责收集、处理和暴露容器与节点的性能指标。

5.1. 初始化与启动

cAdvisor 在 Kubelet 启动时被初始化和启动。buildKubeletDeps 函数创建 cAdvisor 实例,随后 initializeRuntimeDependentModules 调用 kl.cadvisor.Start() 启动其后台收集任务。

5.2. 指标收集周期与实现

cAdvisor 的指标收集频率由 Kubelet 的 --housekeeping-interval 参数(默认 10 秒)控制。其实现机制如下:

  1. 参数传递:Kubelet 通过一个"hack"方式,在启动时将自己的 --housekeeping-interval 值设置给 cAdvisor 库的全局标志 housekeeping_interval
  2. 容器级 housekeeping:cAdvisor 为每个容器启动一个独立的 housekeeping goroutine。这个 goroutine 使用 time.Timer 来周期性地触发指标收集。
  3. 数据采集housekeeping 循环最终调用 updateStats,该函数通过容器处理器 (containerHandler) 从 cgroup 文件系统或 CRI 获取原始的 CPU、内存等数据。
 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
// vendor/github.com/google/cadvisor/manager/container.go

// housekeeping 是每个容器指标收集的主循环
func (cd *containerData) housekeeping() {
    houseKeepingTimer := cd.clock.NewTimer(cd.housekeepingInterval)
    defer houseKeepingTimer.Stop()
    for {
        select {
        case <-cd.stop:
            return
        case <-houseKeepingTimer.C():
            // 定时器触发,执行单次指标收集
            if err := cd.updateStats(); err != nil {
                klog.Errorf("Failed to update stats for container %q: %v", cd.info.Name, err)
            }
            houseKeepingTimer.Reset(cd.nextHousekeepingInterval())
        }
    }
}

// updateStats 调用 handler 获取并存储指标
func (cd *containerData) updateStats() error {
    stats, statsErr := cd.handler.GetStats() // <-- 从 cgroup 或 CRI 获取数据
    if statsErr != nil {
        return statsErr
    }
    // 将统计数据添加到内存缓存
    return cd.memoryCache.AddStats(cd.info, stats)
}

5.3. 指标存储:InMemoryCache

cAdvisor 采集的指标存储在名为 InMemoryCache 的内存缓存中,其核心特点是:

  • 环形缓冲区 (Ring Buffer):为每个容器维护一个固定大小的环形缓冲区,用于存储最近一段时间(默认 60 秒)的时间序列数据。
  • 数据写入updateStats 函数调用 memoryCache.AddStats() 将新采集的指标存入缓冲区,旧数据会被自动覆盖。
  • 数据读取:当外部请求(如 metrics-server)访问 Kubelet 的 /stats/summary 端点时,StatsProvider 会调用 RecentStats 从缓存中提取数据用于聚合。

这种设计使得 cAdvisor 能够在不引入磁盘 I/O 的情况下,高效地提供短期、高分辨率的性能指标。

6. 总结

通过对 Kubelet 源码的深入探索,我们揭示了其作为 Kubernetes 节点核心代理的复杂而精妙的工作机制。从命令行的解析启动,到依赖注入的构建,再到事件驱动的 syncLoop 核心循环,Kubelet 的设计处处体现着健壮性与可扩展性。

syncPod 作为 Pod 生命周期管理的最终执行者,通过与容器运行时的紧密协作,精确地将声明式 API 转化为实际的容器操作。而内嵌的 cAdvisor 则为 Kubelet 提供了强大的监控能力,通过高效的周期性采集和内存缓存机制,为整个集群的调度和自动扩缩容提供了关键的数据支持。

理解 Kubelet 的内部工作原理,不仅有助于我们更好地排查节点问题,也让我们对 Kubernetes 的分布式系统设计哲学有了更深刻的认识。Kubelet 正是这一哲学在节点侧的完美体现,是确保云原生应用稳定、高效运行的基石。

0%