Kubelet 深度分析

本文档深入分析 Kubernetes Kubelet 组件的架构设计、启动流程、核心功能和关键子系统。

目录

  1. 架构概览
  2. 关键数据结构
  3. 启动流程分析
  4. 核心功能原理
  5. 关键子系统详解
  6. 边界情况与错误处理
  7. 性能优化
  8. 最佳实践

1. 架构概览

1.1 Kubelet 在 Kubernetes 架构中的位置

Kubelet 是 Kubernetes 集群中运行在每个节点上的核心代理(Node Agent),是 Kubernetes 控制平面与节点级容器运行时之间的桥梁。它负责维护节点上容器的生命周期,并将节点状态上报给 API Server。

在 Kubernetes 整体架构中,Kubelet 处于如下位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+-------------------------+         +-------------------------+
| Kubernetes Control | | Node (Kubelet) |
| Plane | | |
| | | +-------------------+ |
| +------------------+ | Pod | | PodConfig | |
| | API Server |---+-------->+ | (API/File/HTTP) | |
| +------------------+ | Spec | +--------+----------+ |
| ^ | | | |
| | | | +--------v----------+ |
| | Status | | | Kubelet Core | |
| +---------------+---------+ | | |
| | Status | +----------------+ | |
| | Update | | Pod Workers | | |
| | | | PLEG | | |
| | | | Managers | | |
| | | +-------+--------+ | |
| | | | | |
+-------------------------+ | +--------v----------+ |
| | Container | |
| | Runtime (CRI) | |
| +-------------------+ |
+-------------------------+

1.2 主要职责和功能边界

Kubelet 的核心职责包括以下几个主要方面:

1.2.1 Pod 生命周期管理

Kubelet 负责管理节点上 Pod 的完整生命周期,包括:

  • Pod 接收与解析:从多个来源(API Server、静态文件、HTTP 端点)接收 Pod 规范
  • 容器创建与启动:根据 Pod Spec 创建并启动容器
  • 健康检查:执行探针(liveness、readiness、startup)检测容器状态
  • 容器生命周期:处理容器的优雅终止、重启策略等
  • 垃圾回收:清理已终止的容器和未使用的镜像

1.2.2 节点管理

  • 节点注册:向 API Server 注册节点信息
  • 节点状态维护:定期上报节点状态(内存、磁盘、网络)
  • 节点租约(Node Lease):维护节点的租约以实现高效的节点心跳

1.2.3 资源管理

  • CPU 和内存管理:通过 cgroups 实现资源限制和 QoS
  • 存储卷管理:挂载、卸载 PersistentVolume,处理 CSI 操作
  • 资源驱逐:在资源压力下主动驱逐低优先级 Pod

1.2.4 与外部系统交互

  • 容器运行时接口(CRI):通过 CRI 与容器运行时(docker、containerd、CRI-O)交互
  • 镜像管理:拉取、缓存、垃圾回收容器镜像
  • CSI 插件交互:与外部存储插件通信

1.3 核心模块划分

Kubelet 的核心功能被划分为多个协作的模块,主要包括:

1.3.1 Pod 配置源(Pod Sources)

Kubelet 支持三种 Pod 配置来源:

来源 描述 配置方式
API Server 通过 Watch 机制从 API Server 获取 Pod 规范 --kubeconfig
File 从本地文件目录读取静态 Pod 配置 --pod-manifest-path
HTTP 通过 HTTP 端点定期获取 Pod 配置 --manifest-url

这三种来源的配置会通过 PodConfig 进行聚合,统一对外提供 Pod 变更通知。

1.3.2 核心组件

flowchart TB
    subgraph "Kubelet 架构"
        A[Pod Sources] --> B[PodConfig]
        B --> C[Kubelet Core]
        C --> D[Pod Workers]
        C --> E[PLEG]
        C --> F[Managers]

        subgraph "Managers"
            F1[PodManager]
            F2[StatusManager]
            F3[ProbeManager]
            F4[VolumeManager]
            F5[EvictionManager]
        end

        D --> G[Container Runtime Interface]
        G --> H[Container Runtime<br/>docker/containerd/cri-o]
    end

主要模块说明:

  • PodConfig:Pod 配置聚合器,监听多个配置源的变化,生成统一的 Pod 更新通道
  • PodManager:管理期望的 Pod 集合(包括普通 Pod 和 Mirror Pod),维护 Pod 的元数据
  • PodWorkers:Pod 工作者池,每个 Pod 拥有独立的工作协程,处理 Pod 的同步、终止、清理等状态转换
  • PLEG(Pod Lifecycle Event Generator):定期 Relist 容器状态,生成 Pod 生命周期事件
  • StatusManager:接收 Pod 状态更新,向 API Server 同步 Pod 状态
  • ProbeManager:管理健康探针的执行,包括 Liveness、Readiness、Startup 探针
  • VolumeManager:管理卷的挂载、卸载、附加、分离操作
  • EvictionManager:监控节点资源使用,在资源压力下执行 Pod 驱逐

1.3.3 启动流程概览

Kubelet 的启动流程主要分为以下几个阶段:

  1. 命令行解析 (cmd/kubelet/kubelet.go:34):通过 NewKubeletCommand() 创建 Cobra 命令
  2. 配置初始化 (cmd/kubelet/app/server.go):加载 Kubelet 配置、创建依赖对象
  3. Kubelet 实例创建 (pkg/kubelet/kubelet.go:339):通过 NewMainKubelet() 创建主实例,初始化所有 Manager
  4. 服务启动 (cmd/kubelet/app/server.go:1297):启动主循环、HTTP 服务器、只读端口等

1.4 核心组件职责

Kubelet 通过多个专业 Manager 组件协同工作,每个组件负责特定的职责领域。以下是核心组件的职责说明:

组件 文件路径 核心职责 关键数据结构
PodManager pkg/kubelet/pod/pod_manager.go Pod 元数据管理,维护静态 Pod 和镜像 Pod 之间的映射关系。Kubelet 从三个来源(File、HTTP、API Server)发现 Pod 更新,并保持内存中的 Pod 索引 podByUIDmirrorPodByUIDpodByFullNametranslationByUID
StatusManager pkg/kubelet/status/status_manager.go Pod 状态同步到 API Server。作为 Kubelet 中 Pod 状态的权威来源,接收来自 Worker 的状态更新并缓存,通过状态适配器将状态发布到 API Server podStatusesapiStatusVersions
ProbeManager pkg/kubelet/prober/prober_manager.go 健康检查探针管理。为每个需要探针的容器创建探针 Worker(Liveness、Readiness、Startup),定期执行探针并缓存结果,用于设置 PodStatus 中的 Ready 状态 workers(map[probeKey]*worker)、readinessManagerlivenessManagerstartupManager
VolumeManager pkg/kubelet/volumemanager/volume_manager.go 存储卷管理。运行多个异步循环确定需要附加/挂载/卸载/分离的卷,并根据节点上调度的 Pod 执行相应的卷操作 desiredStateOfWorldactualStateOfWorldoperationExecutor
EvictionManager pkg/kubelet/eviction/eviction_manager.go 资源驱逐管理。监控节点资源使用(内存、磁盘等),在资源压力超过阈值时主动驱逐低优先级 Pod,维护节点稳定性 nodeConditionsthresholdsMetsignalToRankFunc
PLEG pkg/kubelet/pleg/pleg.go Pod 生命周期事件生成。定期 Relist 容器状态,生成 Pod 生命周期事件(ContainerStarted、ContainerDied、ContainerRemoved、PodSync),触发 Pod 同步 PodLifecycleEventRelistPeriod

组件协作关系

flowchart LR
    subgraph "配置源"
        A[API Server] --> F[PodConfig]
        B[File] --> F
        C[HTTP] --> F
    end

    F --> D[PodManager]
    D --> E[PodWorkers]
    D --> PLEG[PLEG]
    D --> VM[VolumeManager]
    D --> EM[EvictionManager]

    PLEG --> E
    E --> PM[ProbeManager]
    E --> SM[StatusManager]
    E --> VM

    PM --> SM
    SM --> API[API Server]

协作流程说明:

  1. Pod 接收:PodManager 从 PodConfig 接收 Pod 规范,维护 Pod 元数据
  2. Pod 同步:PodWorkers 处理 Pod 的创建、更新、删除操作
  3. 探针执行:ProbeManager 为容器执行健康检查,结果写入 StatusManager
  4. 状态上报:StatusManager 将 Pod 状态同步到 API Server
  5. 卷管理:VolumeManager 处理 Pod 的存储卷挂载操作
  6. 生命周期事件:PLEG 监控容器状态变化,生成事件触发 Pod 同步
  7. 资源驱逐:EvictionManager 在资源压力下执行 Pod 驱逐

1.5 依赖注入设计

Kubelet 使用依赖注入模式,通过 Dependencies 结构体(pkg/kubelet/kubelet.go:249)管理可注入的依赖项:

1
2
3
4
5
6
7
8
9
10
11
12
type Dependencies struct {
Auth server.AuthInterface
CAdvisorInterface cadvisor.Interface
Cloud cloudprovider.Interface
ContainerManager cm.ContainerManager
KubeClient clientset.Interface
PodConfig *config.PodConfig
ProbeManager prober.Manager
VolumePlugins []volume.VolumePlugin
RemoteRuntimeService internalapi.RuntimeService
// ... 其他依赖
}

这种设计使得 Kubelet 核心逻辑与外部依赖解耦,便于单元测试时注入 mock 对象。


2. 关键数据结构

Kubelet 的核心功能依赖于几个关键的数据结构,理解这些结构对于深入分析 Kubelet 的工作原理至关重要。

2.1 Kubelet 主结构体

Kubelet 结构体是 Kubelet 的核心,封装了所有状态和管理器。以下是主要字段的说明:

位置: pkg/kubelet/kubelet.go:941

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
type Kubelet struct {
// kubeletConfiguration 保存 Kubelet 的配置参数
kubeletConfiguration kubeletconfiginternal.KubeletConfiguration

// nodeName 是 Kubelet 所在节点的名称
nodeName types.NodeName

// runtimeCache 是容器运行时的缓存接口
runtimeCache kubecontainer.RuntimeCache

// kubeClient 用于与 API Server 通信
kubeClient clientset.Interface
heartbeatClient clientset.Interface

// mirrorPodClient 用于管理静态 Pod 的镜像 Pod
mirrorPodClient kubepod.MirrorClient

// podManager 维护期望的 Pod 集合(已接收的 Pod 和镜像 Pod)
podManager kubepod.Manager

// podWorkers 负责驱动每个 Pod 的生命周期状态机
podWorkers PodWorkers

// evictionManager 观察节点资源压力并执行 Pod 驱逐
evictionManager eviction.Manager

// probeManager 管理健康检查探针
probeManager prober.Manager

// secretManager 缓存运行中 Pod 使用的 Secret
secretManager secret.Manager

// configMapManager 缓存运行中 Pod 使用的 ConfigMap
configMapManager configmap.Manager

// volumeManager 管理卷的附加、挂载、卸载和分离
volumeManager volumemanager.VolumeManager

// statusManager 接收 Pod 状态更新并同步到 API Server
statusManager status.Manager

// cAdvisor 用于获取容器信息
cadvisor cadvisor.Interface

// resyncInterval 是定期完全同步的间隔
resyncInterval time.Duration

// volumePluginMgr 管理卷插件
volumePluginMgr *volume.VolumePluginMgr

// livenessManager、readinessManager、startupManager 管理探针结果
livenessManager proberesults.Manager
readinessManager proberesults.Manager
startupManager proberesults.Manager
}

设计要点:

  • Kubelet 结构体使用组合模式,将所有子管理器(Manager)作为字段组合进来
  • 每个 Manager 负责特定的职责领域,实现关注点分离
  • 结构体字段大多为接口类型,便于依赖注入和单元测试

2.2 Dependencies 依赖注入容器

Dependencies 结构体是 Kubelet 的依赖注入容器,用于在运行时注入外部依赖项。这种设计实现了核心逻辑与外部依赖的解耦。

位置: pkg/kubelet/kubelet.go:249

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
type Dependencies struct {
Options []Option

// Injected Dependencies
Auth server.AuthInterface // 认证接口
CAdvisorInterface cadvisor.Interface // cAdvisor 接口
Cloud cloudprovider.Interface // 云提供商接口
ContainerManager cm.ContainerManager // 容器管理器
EventClient v1core.EventsGetter // 事件客户端
HeartbeatClient clientset.Interface // 心跳客户端
OnHeartbeatFailure func() // 心跳失败回调
KubeClient clientset.Interface // Kubernetes API 客户端
Mounter mount.Interface // 挂载接口
HostUtil hostutil.HostUtils // 主机工具
OOMAdjuster *oom.OOMAdjuster // OOM 调整器
OSInterface kubecontainer.OSInterface // 操作系统接口
PodConfig *config.PodConfig // Pod 配置
ProbeManager prober.Manager // 探针管理器
Recorder record.EventRecorder // 事件记录器
Subpather subpath.Interface // 子路径接口
TracerProvider trace.TracerProvider // 追踪提供者
VolumePlugins []volume.VolumePlugin // 卷插件列表
DynamicPluginProber volume.DynamicPluginProber // 动态插件探针
TLSOptions *server.TLSOptions // TLS 选项
RemoteRuntimeService internalapi.RuntimeService // 远程运行时服务
RemoteImageService internalapi.ImageManagerService // 远程镜像服务
PodStartupLatencyTracker util.PodStartupLatencyTracker // Pod 启动延迟追踪
NodeStartupLatencyTracker util.NodeStartupLatencyTracker // 节点启动延迟追踪
}

设计要点:

  • Dependencies 是临时解决方案,用于在确定更全面的依赖注入方案之前对依赖进行分组
  • 所有外部依赖通过此结构体注入,便于单元测试时替换为 mock 对象
  • Options 字段允许使用函数式选项模式进行配置

2.3 SyncHandler 接口

SyncHandler 接口定义了 Pod 同步处理的契约,是 Kubelet 与外部交互的关键接口。

位置: pkg/kubelet/kubelet.go:222

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type SyncHandler interface {
// HandlePodAdditions 处理新增的 Pod
HandlePodAdditions(pods []*v1.Pod)

// HandlePodUpdates 处理更新的 Pod
HandlePodUpdates(pods []*v1.Pod)

// HandlePodRemoves 处理删除的 Pod
HandlePodRemoves(pods []*v1.Pod)

// HandlePodReconcile 处理定期协调的 Pod
HandlePodReconcile(pods []*v1.Pod)

// HandlePodSyncs 处理需要同步的 Pod
HandlePodSyncs(pods []*v1.Pod)

// HandlePodCleanups 处理清理任务
HandlePodCleanups(ctx context.Context) error
}

接口方法说明:

方法 用途
HandlePodAdditions 处理从配置源新增的 Pod,触发 Pod 创建流程
HandlePodUpdates 处理 Pod 规格更新,触发 Pod 更新同步
HandlePodRemoves 处理删除的 Pod,触发 Pod 终止流程
HandlePodReconcile 处理定期协调,确保实际状态与期望状态一致
HandlePodSyncs 处理需要强制同步的 Pod
HandlePodCleanups 执行清理任务,如清理已删除的 Pod 相关资源

设计要点:

  • SyncHandler 接口实现了 观察者模式,PodConfig 通过此接口通知 Kubelet Pod 变更
  • 接口设计便于测试,测试时可以传入 mock 实现
  • Kubelet 自身实现了此接口,作为主要的同步处理器

2.4 versionedPodStatus 版本化 Pod 状态

versionedPodStatus 是包装 v1.PodStatus 的结构体,带有版本号用于确保不会将过时的 Pod 状态发送到 API Server。

位置: pkg/kubelet/status/status_manager.go:54

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type versionedPodStatus struct {
// version 是单调递增的版本号(每个 Pod)
version uint64

// Pod 名称和命名空间,用于发送到 API Server
podName string
podNamespace string

// at 是最近一次状态检测到更新的时间
at time.Time

// podIsFinished 表示状态是在 SyncTerminatedPod 结束时生成,还是在完成后生成
podIsFinished bool

// status 是 v1.PodStatus
status v1.PodStatus
}

设计要点:

  • 版本控制: version 字段是单调递增的,确保状态更新的顺序性,避免因异步更新导致状态回退
  • 时间戳: at 字段记录状态更新时间,用于判断状态的时效性
  • 完成标志: podIsFinished 标志用于区分 Pod 是否已完成终止过程
  • 线程安全: statusManager 使用 podStatusesLock 保护 podStatuses map 的并发访问

StatusManager 核心结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type manager struct {
kubeClient clientset.Interface
podManager PodManager

// podStatuses 保存每个 Pod UID 对应的版本化状态
podStatuses map[types.UID]versionedPodStatus
podStatusesLock sync.RWMutex
podStatusChannel chan struct{}

// apiStatusVersions 保存成功发送到 API Server 的最新状态版本
// 此字段只能从同步线程访问
apiStatusVersions map[kubetypes.MirrorPodUID]uint64

podDeletionSafety PodDeletionSafetyProvider
podStartupLatencyHelper PodStartupLatencyStateHelper
state state.State
stateFileDirectory string
}

2.5 数据结构关系图

classDiagram
    class Kubelet {
        +podManager: PodManager
        +podWorkers: PodWorkers
        +statusManager: StatusManager
        +probeManager: ProbeManager
        +volumeManager: VolumeManager
        +evictionManager: EvictionManager
    }

    class Dependencies {
        +KubeClient: clientset.Interface
        +PodConfig: *PodConfig
        +ProbeManager: Manager
        +VolumePlugins: []VolumePlugin
        +RemoteRuntimeService: RuntimeService
    }

    class SyncHandler {
        <<interface>>
        +HandlePodAdditions()
        +HandlePodUpdates()
        +HandlePodRemoves()
        +HandlePodReconcile()
        +HandlePodSyncs()
        +HandlePodCleanups()
    }

    class versionedPodStatus {
        +version: uint64
        +podName: string
        +podNamespace: string
        +at: time.Time
        +podIsFinished: bool
        +status: v1.PodStatus
    }

    Kubelet ..> Dependencies : 依赖注入
    Kubelet ..> SyncHandler : 实现
    StatusManager --> versionedPodStatus : 管理

3. 启动流程分析

3.1 启动流程概述

Kubelet 的启动流程主要分为以下几个阶段:

  1. 命令行解析 (cmd/kubelet/app/server.go:135):通过 NewKubeletCommand() 创建 Cobra 命令
  2. 配置初始化 (cmd/kubelet/app/server.go):加载 Kubelet 配置、创建依赖对象
  3. Kubelet 实例创建 (pkg/kubelet/kubelet.go:337):通过 NewMainKubelet() 创建主实例,初始化所有 Manager
  4. 服务启动 (cmd/kubelet/app/server.go:1297):启动主循环、HTTP 服务器、只读端口等

3.2 详细启动流程

3.2.1 命令行参数解析流程

Kubelet 使用 Cobra 框架构建命令行接口,NewKubeletCommand() 函数负责创建命令和解析参数。

位置: cmd/kubelet/app/server.go:135-249

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func NewKubeletCommand() *cobra.Command {
// 1. 创建 FlagSet
cleanFlagSet := pflag.NewFlagSet(componentKubelet, pflag.ContinueOnError)

// 2. 初始化 KubeletFlags(命令行参数)
kubeletFlags := options.NewKubeletFlags()

// 3. 初始化 KubeletConfiguration(配置文件)
kubeletConfig, err := options.NewKubeletConfiguration()

// 4. 创建 Cobra 命令
cmd := &cobra.Command{
DisableFlagParsing: true, // 手动解析参数
RunE: func(cmd *cobra.Command, args []string) error {
// 解析命令行参数
if err := cleanFlagSet.Parse(args); err != nil {}

// 加载配置文件
if len(kubeletFlags.KubeletConfigFile) > 0 {
kubeletConfig, err = loadConfigFile(kubeletFlags.KubeletConfigFile)
}

// 合并 drop-in 配置
if len(kubeletFlags.KubeletDropinConfigDirectory) > 0 {
mergeKubeletConfigurations(kubeletConfig, kubeletFlags.KubeletDropinConfigDirectory)
}

// 验证参数优先级
kubeletConfigFlagPrecedence(kubeletConfig, args)

// 初始化日志
logs.InitLogs()

// 启动 Kubelet
return RunKubelet(kubeServer, kubeDeps, runOnce)
},
}
}

参数解析关键步骤:

步骤 函数/操作 说明
1 NewKubeletFlags() 初始化命令行参数结构体
2 NewKubeletConfiguration() 初始化默认配置
3 cleanFlagSet.Parse(args) 解析命令行参数
4 loadConfigFile() 加载 YAML 配置文件
5 mergeKubeletConfigurations() 合并 drop-in 配置文件
6 kubeletConfigFlagPrecedence() 确保命令行参数优先级最高
7 logs.InitLogs() 初始化日志系统

3.2.2 依赖初始化顺序

RunKubelet() 函数负责协调依赖对象的创建和初始化。

位置: cmd/kubelet/app/server.go:1207-1295

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
func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencies, runOnce bool) error {
// 1. 获取主机名
hostname, err := nodeutil.GetHostname(kubeServer.HostnameOverride)

// 2. 获取节点名称(可能来自云提供商)
nodeName, err := getNodeName(kubeDeps.Cloud, hostname)

// 3. 创建事件记录器
makeEventRecorder(kubeDeps, nodeName)

// 4. 解析节点 IP
nodeIPs, err := nodeutil.ParseNodeIPArgument(kubeServer.NodeIP, ...)

// 5. 初始化容器能力
capabilities.Initialize(capabilities.Capabilities{AllowPrivileged: true})

// 6. 创建并初始化 Kubelet
k, err := createAndInitKubelet(kubeServer, kubeDeps, hostname, ...)

// 7. 启动 Kubelet 服务
if runOnce {
k.RunOnce(podCfg.Updates())
} else {
startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps, kubeServer.EnableServer)
}
}

容器运行时服务初始化:

PreInitRuntimeService() 在主 Kubelet 启动前初始化 CRI 服务。

位置: cmd/kubelet/app/server.go:318-335

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func PreInitRuntimeService(kubeCfg *KubeletConfiguration, kubeDeps *Dependencies) error {
// 创建远程运行时服务(CRI gRPC 客户端)
kubeDeps.RemoteRuntimeService, err = remote.NewRemoteRuntimeService(
kubeCfg.ContainerRuntimeEndpoint,
kubeCfg.RuntimeRequestTimeout.Duration,
kubeDeps.TracerProvider)

// 创建远程镜像服务
kubeDeps.RemoteImageService, err = remote.NewRemoteImageService(
remoteImageEndpoint,
kubeCfg.RuntimeRequestTimeout.Duration,
kubeDeps.TracerProvider)

return nil
}

CreateAndInitKubelet 流程:

位置: cmd/kubelet/app/server.go:1317-1362

1
2
3
4
5
6
7
8
9
10
11
12
func createAndInitKubelet(...) (k kubelet.Bootstrap, err error) {
// 1. 创建 Kubelet 主实例
k, err = kubelet.NewMainKubelet(&kubeServer.KubeletConfiguration, kubeDeps, ...)

// 2. 发送启动事件
k.BirthCry()

// 3. 启动垃圾回收
k.StartGarbageCollection()

return k, nil
}

3.2.3 运行时启动流程

Run() 方法是 Kubelet 的主循环,启动所有后台 goroutine。

位置: pkg/kubelet/kubelet.go:1742-1900

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// 1. 初始化日志服务器
if kl.logServer == nil {
kl.logServer = http.FileServer(http.Dir(nodeLogDir))
}

// 2. 启动云资源同步管理器
if kl.cloudResourceSyncManager != nil {
go kl.cloudResourceSyncManager.Run(wait.NeverStop)
}

// 3. 初始化内部模块(容器网络、驱逐等)
if err := kl.initializeModules(); err != nil {
os.Exit(1)
}

// 4. 启动卷管理器
go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)

// 5. 启动节点状态同步(两个协程)
if kl.kubeClient != nil {
// 定期节点状态更新
go wait.JitterUntil(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, ...)
// 快速初始状态更新
go kl.fastStatusUpdateOnce()
// 节点租约同步
go kl.nodeLeaseController.Run(context.Background())
}

// 6. 定期更新运行时状态
go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)

// 7. 初始化 iptables 规则
if kl.makeIPTablesUtilChains {
kl.initNetworkUtil()
}

// 8. 启动状态管理器
kl.statusManager.Start()

// 9. 启动 RuntimeClass 管理器
if kl.runtimeClassManager != nil {
kl.runtimeClassManager.Start(wait.NeverStop)
}

// 10. 启动 PLEG(Pod 生命周期事件生成器)
go kl.pleg.Start()

// 11. 启动 Pod 同步循环
kl.syncLoop(updates, kl)
}

各 goroutine 启动顺序:

顺序 组件 说明
1 CloudResourceSyncManager 同步云提供商资源
2 initializeModules 初始化内部模块
3 VolumeManager 卷管理器
4 syncNodeStatus 节点状态同步
5 nodeLeaseController 节点租约
6 updateRuntimeUp 运行时状态更新
7 StatusManager 状态同步
8 RuntimeClassManager RuntimeClass 同步
9 PLEG 生命周期事件生成
10 syncLoop Pod 同步主循环

3.3 启动流程序列图

sequenceDiagram
    participant Main as main()
    participant Cobra as NewKubeletCommand()
    participant Server as RunKubelet()
    participant Runtime as PreInitRuntimeService
    participant Kubelet as NewMainKubelet
    participant Modules as Run()

    Note over Main,Cobra: 1. 命令行解析阶段
    Main->>Cobra: app.NewKubeletCommand()
    Cobra->>Cobra: NewKubeletFlags()
    Cobra->>Cobra: NewKubeletConfiguration()
    Cobra->>Cobra: loadConfigFile()
    Cobra->>Cobra: mergeKubeletConfigurations()
    Cobra->>Cobra: kubeletConfigFlagPrecedence()

    Note over Cobra,Server: 2. 依赖初始化阶段
    Cobra->>Server: RunE()
    Server->>Server: GetHostname()
    Server->>Server: getNodeName()
    Server->>Server: makeEventRecorder()
    Server->>Server: ParseNodeIPArgument()

    Note over Server,Runtime: 3. 运行时服务初始化
    Server->>Runtime: PreInitRuntimeService()
    Runtime->>Runtime: remote.NewRemoteRuntimeService()
    Runtime->>Runtime: remote.NewRemoteImageService()

    Note over Runtime,Kubelet: 4. Kubelet 实例创建
    Server->>Kubelet: createAndInitKubelet()
    Kubelet->>Kubelet: NewMainKubelet()
    Note over Kubelet: 初始化所有 Manager<br/>- PodManager<br/>- StatusManager<br/>- ProbeManager<br/>- VolumeManager<br/>- EvictionManager<br/>- PLEG
    Kubelet->>Kubelet: BirthCry()
    Kubelet->>Kubelet: StartGarbageCollection()

    Note over Kubelet,Modules: 5. 服务启动阶段
    Server->>Modules: k.Run(updates)
    Modules->>Modules: initializeModules()
    Modules->>Modules: volumeManager.Run()
    Modules->>Modules: syncNodeStatus()
    Modules->>Modules: nodeLeaseController.Run()
    Modules->>Modules: updateRuntimeUp()
    Modules->>Modules: statusManager.Start()
    Modules->>Modules: runtimeClassManager.Start()
    Modules->>Modules: pleg.Start()
    Modules->>Modules: syncLoop()

    Note over Modules,Server: 6. HTTP 服务启动
    Server->>Server: ListenAndServe()
    Server->>Server: ListenAndServeReadOnly()
    Server->>Server: ListenAndServePodResources()

3.4 关键代码路径

启动入口 (cmd/kubelet/kubelet.go):

1
2
3
4
5
6
func main() {
command := kubelet.NewKubeletCommand()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}

配置加载流程:

1
2
3
命令行参数 --config --> KubeletConfiguration
--config-file --> YAML 配置文件
--config-dir --> Drop-in 配置目录

核心初始化顺序:

  1. API Server 连接建立
  2. CRI 服务连接建立
  3. PodConfig 源注册(File、HTTP、API Server)
  4. 各 Manager 初始化
  5. 主循环启动

4. 核心功能原理

本章深入分析 Kubelet 的核心功能实现原理,包括 Pod 生命周期管理和 Pod 同步机制。

4.1 Pod 生命周期概述

Pod 是 Kubernetes 的最小调度单元,Kubelet 负责管理节点上 Pod 的完整生命周期。理解 Pod 状态转换和 Kubelet 的同步机制对于掌握 Kubernetes 容器编排至关重要。

4.1.1 Pod 状态阶段(Phase)

Pod 的生命周期由 v1.PodStatus.Phase 字段表示,主要有以下几种状态:

状态 描述 转换条件
Pending Pod 已被 API Server 接收,但尚未在节点上完成调度和容器创建 Pod 创建后进入此状态
Running Pod 已绑定到节点,所有容器已创建,至少有一个容器正在运行 所有容器启动成功后
Succeeded Pod 中的所有容器已成功终止,且不会重启 RestartPolicy 为 Never 且所有容器成功退出
Failed Pod 中的所有容器已终止,至少有一个容器因失败退出 容器以非零状态码退出且 RestartPolicy 不是 Always
Unknown 无法获取 Pod 状态,通常是由于与节点通信问题 API Server 无法获取 Pod 状态

状态转换图

stateDiagram-v2
    [*] --> Pending: Pod 创建
    Pending --> Running: 容器创建成功
    Running --> Succeeded: 所有容器成功退出
    Running --> Failed: 容器失败退出
    Failed --> [*]
    Succeeded --> [*]

4.1.2 Pod 生命周期管理接口

Kubelet 通过 podSyncer 接口(pkg/kubelet/kubelet.go)定义 Pod 生命周期管理的核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type podSyncer interface {
// SyncPod 配置 Pod 并启动/重启所有容器
SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod,
mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error)

// SyncTerminatingPod 尝试确保 Pod 的容器不再运行并收集最终状态
SyncTerminatingPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus,
gracePeriod *int64, podStatusFn func(*v1.PodStatus)) error

// SyncTerminatingRuntimePod 处理孤儿 Pod 的终止
SyncTerminatingRuntimePod(ctx context.Context, runningPod *kubecontainer.Pod) error

// SyncTerminatedPod 在所有运行容器停止后调用,负责释放资源
SyncTerminatedPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus) error
}

4.2 Pod 同步机制详解

Pod 同步是 Kubelet 的核心职责,确保节点上的容器状态与 API Server 中的 Pod 规范保持一致。

4.2.1 Pod Workers 架构

Pod Workers 是 Kubelet 中管理 Pod 同步的核心组件(pkg/kubelet/pod_workers.go)。每个 Pod 拥有独立的工作协程,实现了以下功能:

  • FIFO 顺序处理:保证同一 Pod 的更新按接收顺序处理
  • 状态机管理:跟踪 Pod 的当前状态(SyncPod/TerminatingPod/TerminatedPod)
  • 并发控制:避免同一 Pod 的并发同步

PodWorkerState 定义

1
2
3
4
5
6
7
8
const (
// SyncPod Pod 预期启动并运行
SyncPod PodWorkerState = iota
// TerminatingPod Pod 不再设置,但某些容器可能正在运行并正在关闭
TerminatingPod
// TerminatedPod Pod 已停止,不能有更多运行容器
TerminatedPod
)

4.2.2 syncPod() 方法分析

syncPod() 是 Kubelet 的核心同步方法(pkg/kubelet/kubelet.go:1942),负责将 Pod 规范同步到容器运行时。

主要流程

flowchart TD
    A[syncPod 开始] --> B{Phase 是 Succeeded/Failed?}
    B -->|Yes| C[设置终止状态并返回]
    B -->|No| D{Pod 是否可运行?}
    D -->|No| E[更新状态为 Pending<br/>杀死运行中的容器]
    D -->|Yes| F{网络插件就绪?}
    F -->|No| G[返回网络未就绪错误]
    F -->|Yes| H[注册 Secret/ConfigMap]
    H --> I[创建/更新 Pod Cgroups]
    I --> J{静态 Pod?}
    J -->|Yes| K[创建/更新镜像 Pod]
    J -->|No| L[创建 Pod 数据目录]
    L --> M[等待卷挂载]
    M --> N[添加 Pod 到探针管理器]
    N --> O[调用 CRI SyncPod]
    O --> P[返回结果]

关键代码路径pkg/kubelet/kubelet.go:1942-2283):

  1. 状态生成generateAPIPodStatus() 根据容器运行时状态生成 API Pod 状态
  2. Pod 可运行性检查canRunPod() 检查 Pod 是否可以在节点上运行
  3. 资源准备
    • Secret/ConfigMap 注册
    • Pod Cgroups 创建
    • 镜像 Pod 管理(静态 Pod)
    • 数据目录创建
    • 卷挂载等待
  4. 容器同步:调用 containerRuntime.SyncPod() 与 CRI 交互

4.2.3 Pod 状态计算

generateAPIPodStatus() 方法(pkg/kubelet/kubelet.go)负责计算 Pod 的最终状态:

1
2
3
4
5
6
7
8
9
func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.PodStatus,
isTermination bool) v1.PodStatus {
// 1. 根据容器状态计算每个容器的状态
// 2. 根据 Init 容器状态确定 InitContainerStatuses
// 3. 根据主容器状态确定 ContainerStatuses
// 4. 根据所有容器状态确定 Pod Phase
// 5. 设置 Pod IP(如果有)
// 6. 应用探针结果到 Ready 状态
}

状态计算规则

  • 如果任何容器处于运行状态,Phase 为 Running
  • 如果 Init 容器失败,Phase 为 Failed
  • 如果所有容器成功退出,Phase 为 Succeeded
  • 如果任何容器失败退出,Phase 为 Failed
  • 否则,Phase 为 Pending

4.2.4 卷挂载处理

卷挂载是 Pod 启动的关键步骤,volumeManager.WaitForAttachAndMount() 确保所有卷在容器启动前完成挂载:

1
2
3
4
5
6
// pkg/kubelet/kubelet.go:2211
if err := kl.volumeManager.WaitForAttachAndMount(ctx, pod); err != nil {
kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume,
"Unable to attach or mount volumes: %v", err)
return false, err
}

4.3 容器运行时同步

Kubelet 通过容器运行时接口(CRI)与具体的容器运行时(containerd、CRI-O 等)交互。kuberuntimeManager.SyncPod() 是具体的实现(pkg/kubelet/kuberuntime/kuberuntime_manager.go:1066)。

4.3.1 SyncPod 流程

flowchart TD
    A[SyncPod 开始] --> B[计算 Pod 操作]
    B --> C{需要创建沙箱?}
    C -->|Yes| D[停止旧沙箱]
    C -->|No| E{需要杀死容器?}
    E -->|Yes| F[杀死不需要的容器]
    E -->|No| G[创建 Pod 沙箱]
    D --> G
    F --> G
    G --> H[启动临时容器]
    H --> I{有可重启 Init 容器?}
    I -->|No| J[启动下一个 Init 容器]
    I -->|Yes| K[启动所有可重启 Init 容器]
    J --> L[启动主容器]
    K --> L
    L --> M[返回结果]

关键步骤pkg/kubelet/kuberuntime/kuberuntime_manager.go):

  1. 计算操作computePodActions() 确定需要执行的操作
  2. 沙箱管理:创建/更新/删除 Pod 沙箱
  3. Init 容器启动
    • 普通 Init 容器按顺序执行
    • 可重启 Init 容器可以并行执行
  4. 主容器启动:按 Pod Spec 中的顺序启动

4.3.2 Init 容器流程

Init 容器在主容器之前运行,必须按 Pod Spec 中的顺序执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pkg/kubelet/kuberuntime/kuberuntime_manager.go:1290-1318
if !types.HasRestartableInitContainer(pod) {
// Step 6: 启动下一个 Init 容器
if container := podContainerChanges.NextInitContainerToStart; container != nil {
if err := start(ctx, "init container", metrics.InitContainer,
containerStartSpec(container)); err != nil {
return // Init 容器失败,Pod 进入 Failed 状态
}
}
} else {
// 启动可重启 Init 容器
for _, idx := range podContainerChanges.InitContainersToStart {
// ...
}
}

Init 容器失败处理

  • 普通 Init 容器失败:Pod 进入 Failed 状态
  • 可重启 Init 容器失败:跳过并尝试下一个

4.4 Pod 终止流程

Pod 终止是一个复杂的多阶段过程,涉及优雅关闭、资源清理和状态上报。

4.4.1 终止阶段划分

Pod 终止分为三个主要阶段:

sequenceDiagram
    participant API as API Server
    participant Worker as Pod Worker
    participant Kubelet as Kubelet
    participant Runtime as 容器运行时

    API->>Worker: 删除 Pod 请求
    Worker->>Kubelet: SyncTerminatingPod
    Note over Kubelet: 1. 停止探针<br/>2. 杀死容器(优雅期)<br/>3. 等待容器停止
    Kubelet->>Runtime: KillContainer
    Runtime-->>Kubelet: 容器已停止
    Kubelet->>Worker: SyncTerminatedPod
    Note over Kubelet: 1. 卸载卷<br/>2. 清理资源<br/>3. 设置最终状态
    Kubelet->>API: 更新 Pod 状态

4.4.2 SyncTerminatingPod

SyncTerminatingPod()pkg/kubelet/kubelet.go:2294)负责优雅终止 Pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (kl *Kubelet) SyncTerminatingPod(ctx context.Context, pod *v1.Pod,
podStatus *kubecontainer.PodStatus, gracePeriod *int64,
podStatusFn func(*v1.PodStatus)) error {
// 1. 生成 API Pod 状态
apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, false)
kl.statusManager.SetPodStatus(pod, apiPodStatus)

// 2. 停止健康探针
kl.probeManager.StopLivenessAndStartup(pod)

// 3. 杀死 Pod 中的所有容器
p := kubecontainer.ConvertPodStatusToRunningPod(kl.getRuntime().Type(), podStatus)
if err := kl.killPod(ctx, pod, p, gracePeriod); err != nil {
return err
}

// 4. 验证所有容器已停止
stoppedPodStatus, err := kl.containerRuntime.GetPodStatus(ctx, pod.UID, ...)
}

关键操作

  1. 停止 Liveness 和 Startup 探针
  2. 使用优雅期(grace period)杀死容器
  3. 等待容器实际停止
  4. 清理动态资源(如有)

4.4.3 SyncTerminatedPod

SyncTerminatedPod()pkg/kubelet/kubelet.go:2441)执行最终清理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (kl *Kubelet) SyncTerminatedPod(ctx context.Context, pod *v1.Pod,
podStatus *kubecontainer.PodStatus) error {
// 1. 生成最终 Pod 状态
apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, true)
kl.statusManager.SetPodStatus(pod, apiPodStatus)

// 2. 等待卷卸载
if err := kl.volumeManager.WaitForUnmount(ctx, pod); err != nil {
return err
}

// 3. 清理 Pod 资源(可选保留终止 Pod 的卷)
if !kl.keepTerminatedPodVolumes {
// 清理卷
}
}

4.4.4 优雅终止机制

优雅终止允许容器完成正在进行的操作并清理资源:

1
2
3
4
// pkg/kubelet/kubelet.go:2322
if err := kl.killPod(ctx, pod, p, gracePeriod); err != nil {
// 处理错误
}

优雅终止流程

  1. 发送 SIGTERM 信号给容器内的主进程
  2. 等待优雅期(默认 30 秒)
  3. 如果容器未停止,发送 SIGKILL 信号强制终止

4.5 Pod 同步类型

Kubelet 处理多种类型的 Pod 同步,每种类型触发不同的处理逻辑:

同步类型 描述 触发时机
SyncPodCreate 新 Pod 创建 Pod 首次被 Kubelet 接收
SyncPodUpdate Pod 规格更新 Pod 定义发生变化
SyncPodSync 定期同步 定期触发或状态检查
SyncPodKill Pod 终止 删除请求或驱逐

4.6 CRI 接口(Container Runtime Interface)

CRI(Container Runtime Interface)是 Kubernetes 定义的一套标准接口,用于 Kubelet 与各种容器运行时(如 containerd、CRI-O、docker)进行通信。CRI 的设计实现了 Kubelet 与容器运行时的解耦,使得 Kubernetes 可以支持多种容器运行时而无需修改核心代码。

4.6.1 CRI 架构概述

CRI 采用 gRPC 协议进行通信,定义了两类主要的服务接口:

1
2
3
4
5
6
7
8
9
10
+----------------+     gRPC      +-------------------+
| Kubelet | <----------> | Container Runtime |
| | | (containerd/cri-o) |
+----------------+ +-------------------+
| |
| |
+-----+-----+ +------+------+
|Runtime | | CRI API |
|Service | | (v1alpha2/v1)|
+-----------+ +--------------+

CRI 核心组件(位于 staging/src/k8s.io/cri-api/pkg/apis/services.go):

接口 职责 核心方法
RuntimeService 容器和沙箱管理 CreateContainer、StartContainer、StopContainer、RunPodSandbox 等
ImageManagerService 镜像管理 PullImage、ListImages、RemoveImage、ImageStatus 等

4.6.2 RuntimeService 接口

RuntimeService 是 CRI 的核心接口,定义了容器和 Pod 沙箱的完整生命周期管理。以下是主要方法分类:

沙箱管理方法(PodSandboxManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// staging/src/k8s.io/cri-api/pkg/apis/services.go:69-85
type PodSandboxManager interface {
// RunPodSandbox 创建并启动一个 Pod 级别的沙箱
RunPodSandbox(ctx context.Context, config *runtimeapi.PodSandboxConfig, runtimeHandler string) (string, error)

// StopPodSandbox 停止沙箱,如果容器正在运行则强制终止
StopPodSandbox(ctx context.Context, podSandboxID string) error

// RemovePodSandbox 删除沙箱
RemovePodSandbox(ctx context.Context, podSandboxID string) error

// PodSandboxStatus 返回沙箱的状态
PodSandboxStatus(ctx context.Context, podSandboxID string, verbose bool) (*runtimeapi.PodSandboxStatusResponse, error)

// ListPodSandbox 返回沙箱列表
ListPodSandbox(ctx context.Context, filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error)

// PortForward 准备端口转发流式端点
PortForward(ctx context.Context, req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)
}

容器管理方法(ContainerManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// staging/src/k8s.io/cri-api/pkg/apis/services.go:34-65
type ContainerManager interface {
// CreateContainer 在指定沙箱中创建新容器
CreateContainer(ctx context.Context, podSandboxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error)

// StartContainer 启动容器
StartContainer(ctx context.Context, containerID string) error

// StopContainer 停止运行中的容器(带宽限期)
StopContainer(ctx context.Context, containerID string, timeout int64) error

// RemoveContainer 删除容器
RemoveContainer(ctx context.Context, containerID string) error

// ListContainers 列出容器
ListContainers(ctx context.Context, filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error)

// ContainerStatus 返回容器状态
ContainerStatus(ctx context.Context, containerID string, verbose bool) (*runtimeapi.ContainerStatusResponse, error)

// UpdateContainerResources 更新容器资源配置
UpdateContainerResources(ctx context.Context, containerID string, resources *runtimeapi.ContainerResources) error

// ExecSync 在容器中执行命令并返回输出
ExecSync(ctx context.Context, containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error)

// Exec 准备流式端点在容器中执行命令
Exec(ctx context.Context, req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error)

// Attach 附加到运行中的容器
Attach(ctx context.Context, req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error)

// ReopenContainerLog 重新打开容器日志
ReopenContainerLog(ctx context.Context, containerID string) error

// CheckpointContainer 检查点容器
CheckpointContainer(ctx context.Context, options *runtimeapi.CheckpointContainerRequest) error

// GetContainerEvents 获取容器事件流
GetContainerEvents(containerEventsCh chan *runtimeapi.ContainerEventResponse) error
}

统计管理方法(ContainerStatsManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// staging/src/k8s.io/cri-api/pkg/apis/services.go:89-104
type ContainerStatsManager interface {
// ContainerStats 返回容器统计信息
ContainerStats(ctx context.Context, containerID string) (*runtimeapi.ContainerStats, error)

// ListContainerStats 返回所有运行容器的统计
ListContainerStats(ctx context.Context, filter *runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error)

// PodSandboxStats 返回 Pod 统计信息
PodSandboxStats(ctx context.Context, podSandboxID string) (*runtimeapi.PodSandboxStats, error)

// ListPodSandboxStats 返回所有运行 Pod 的统计
ListPodSandboxStats(ctx context.Context, filter *runtimeapi.PodSandboxStatsFilter) ([]*runtimeapi.PodSandboxStats, error)
}

4.6.3 ImageManagerService 接口

ImageManagerService 接口负责容器镜像的管理,包括拉取、查询、删除镜像等操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// staging/src/k8s.io/cri-api/pkg/apis/services.go:125-136
type ImageManagerService interface {
// ListImages 列出已有镜像
ListImages(ctx context.Context, filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error)

// ImageStatus 返回镜像状态
ImageStatus(ctx context.Context, image *runtimeapi.ImageSpec, verbose bool) (*runtimeapi.ImageStatusResponse, error)

// PullImage 使用认证配置拉取镜像
PullImage(ctx context.Context, image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error)

// RemoveImage 删除镜像
RemoveImage(ctx context.Context, image *runtimeapi.ImageSpec) error

// ImageFsInfo 返回存储镜像的文件系统信息
ImageFsInfo(ctx context.Context) (*runtimeapi.ImageFsInfoResponse, error)
}

4.6.4 gRPC 通信机制

Kubelet 通过 pkg/kubelet/cri/remote 包中的远程客户端与容器运行时进行 gRPC 通信。

远程运行时服务初始化pkg/kubelet/cri/remote/remote_runtime.go:79-131):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func NewRemoteRuntimeService(endpoint string, connectionTimeout time.Duration, tp trace.TracerProvider) (internalapi.RuntimeService, error) {
// 1. 解析 endpoint 获取地址和 dialer
addr, dialer, err := util.GetAddressAndDialer(endpoint)

// 2. 配置 gRPC 连接选项
var dialOpts []grpc.DialOption
dialOpts = append(dialOpts,
grpc.WithTransportCredentials(insecure.NewCredentials()), // 不加密传输(本地 Unix Socket)
grpc.WithContextDialer(dialer), // 自定义 dialer
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)))

// 3. 添加 OpenTelemetry 追踪支持
if utilfeature.DefaultFeatureGate.Enabled(features.KubeletTracing) {
dialOpts = append(dialOpts,
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(tracingOpts...)),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(tracingOpts...)))
}

// 4. 配置连接参数(重试、退避)
connParams := grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
}
connParams.MinConnectTimeout = minConnectionTimeout // 默认 5 秒
connParams.Backoff.BaseDelay = baseBackoffDelay // 默认 100ms
connParams.Backoff.MaxDelay = maxBackoffDelay // 默认 3s

// 5. 建立 gRPC 连接
conn, err := grpc.DialContext(ctx, addr, dialOpts...)

// 6. 验证服务连接(调用 Version API)
service.validateServiceConnection(ctx, conn, endpoint)

return service, nil
}

gRPC 连接配置参数

参数 默认值 说明
minConnectionTimeout 5 秒 最小连接超时时间
baseBackoffDelay 100ms 基础退避延迟
maxBackoffDelay 3 秒 最大退避延迟
maxMsgSize 16MB 最大消息大小

gRPC 通信特点

  1. Unix Socket 通信:CRI 通常使用 Unix Socket(如 /var/run/containerd/containerd.sock)进行本地通信,提高性能和安全性
  2. 超时控制:每个 CRI 调用都设置超时,避免长时间阻塞
  3. 错误处理:使用 gRPC 状态码(如 codes.DeadlineExceededcodes.Unknown)处理错误
  4. 追踪支持:集成 OpenTelemetry 实现分布式追踪
  5. 日志抑制:使用 LogReduction 减少重复错误日志

4.6.5 KubeRuntimeManager 实现

KubeRuntimeManagerpkg/kubelet/kuberuntime/kuberuntime_manager.go)是 Kubelet 中 CRI 接口的具体实现。它封装了 RuntimeService 和 ImageManagerService,提供高级的容器管理功能。

核心结构pkg/kubelet/kuberuntime/kuberuntime_manager.go:101-150):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type kubeGenericRuntimeManager struct {
runtimeName string
recorder record.EventRecorder
osInterface kubecontainer.OSInterface

// 容器 GC 管理器
containerGC *containerGC

// 密钥环(镜像拉取认证)
keyring credentialprovider.DockerKeyring

// 生命周期事件执行器
runner kubecontainer.HandlerRunner

// 运行时助手
runtimeHelper kubecontainer.RuntimeHelper

// 探针结果管理器
livenessManager proberesults.Manager
readinessManager proberesults.Manager
startupManager proberesults.Manager

// 镜像拉取器
imagePuller images.ImageManager

// gRPC 服务客户端
runtimeService internalapi.RuntimeService
imageService internalapi.ImageManagerService

// 版本缓存
versionCache *cache.ObjectCache
}

KubeRuntimeManager 主要功能

方法 职责
SyncPod 同步 Pod 状态(创建沙箱、启动容器)
CreatePodSandbox 创建 Pod 沙箱
StartPodSandbox 启动 Pod 沙箱
StopPodSandbox 停止 Pod 沙箱
CreateContainer 创建容器
StartContainer 启动容器
StopContainer 停止容器
RemoveContainer 删除容器
PullImage 拉取镜像
ListImages 列出镜像
ListContainers 列出容器
ContainerStatus 获取容器状态

4.6.6 CRI 在 Pod 生命周期中的应用

CRI 接口贯穿 Pod 的整个生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
+------------------------------------------------------------------+
| CRI 接口调用流程 |
+------------------------------------------------------------------+
| |
| 1. RunPodSandbox() |
| +----------------------------------------------------------+ |
| | - 创建网络命名空间 | |
| | - 分配 IP 地址 | |
| | - 启动 pause 容器 | |
| +----------------------------------------------------------+ |
| | |
| 2. CreateContainer() / StartContainer() |
| +----------------------------------------------------------+ |
| | - 创建并启动 Init 容器 | |
| | - 创建并启动主容器 | |
| +----------------------------------------------------------+ |
| | |
| 3. ContainerStatus() / PodSandboxStatus() |
| +----------------------------------------------------------+ |
| | - 定期查询容器/沙箱状态 | |
| | - PLEG 使用此接口生成生命周期事件 | |
| +----------------------------------------------------------+ |
| | |
| 4. ExecSync() |
| +----------------------------------------------------------+ |
| | - 执行健康检查探针 | |
| | - 执行 readiness 探针 | |
| +----------------------------------------------------------+ |
| | |
| 5. StopContainer() |
| +----------------------------------------------------------+ |
| | - 发送 SIGTERM | |
| | - 等待优雅期 | |
| | - 发送 SIGKILL | |
| +----------------------------------------------------------+ |
| | |
| 6. RemoveContainer() / RemovePodSandbox() |
| +----------------------------------------------------------+ |
| | - 清理容器资源 | |
| | - 清理沙箱资源 | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

参考代码文件:

文件路径 说明
staging/src/k8s.io/cri-api/pkg/apis/services.go CRI 接口定义
pkg/kubelet/cri/remote/remote_runtime.go 远程运行时服务客户端实现
pkg/kubelet/cri/remote/remote_image.go 远程镜像服务客户端实现
pkg/kubelet/kuberuntime/kuberuntime_manager.go KubeRuntimeManager 实现
pkg/kubelet/kuberuntime/kuberuntime_sandbox.go 沙箱管理实现
pkg/kubelet/kuberuntime/kuberuntime_container.go 容器管理实现

参考代码文件:

  • /cmd/kubelet/app/server.go:135 - NewKubeletCommand 命令行解析
  • /cmd/kubelet/app/server.go:318 - PreInitRuntimeService 运行时服务
  • /cmd/kubelet/app/server.go:1207 - RunKubelet 启动入口
  • /cmd/kubelet/app/server.go:1317 - CreateAndInitKubelet 创建 Kubelet
  • /pkg/kubelet/kubelet.go:337 - NewMainKubelet 创建主实例
  • /pkg/kubelet/kubelet.go:1742 - Run 主循环

5. 关键子系统详解

本章深入分析 Kubelet 的核心子系统实现,包括 PodManager、StatusManager、ProbeManager、VolumeManager、PLEG 和 EvictionManager。

5.1 PodManager

PodManager 是 Kubelet 中负责管理 Pod 元数据的核心组件,维护静态 Pod 和镜像 Pod 之间的映射关系。它是 Kubelet 与 API Server 之间的重要桥梁。

5.1.1 功能概述

PodManager 的核心职责包括:

  1. Pod 元数据存储:维护内存中的 Pod 索引,支持多种查询方式
  2. 静态 Pod 和镜像 Pod 映射:建立并维护静态 Pod 与其对应的镜像 Pod 之间的关联
  3. Pod 查询接口:提供多种查询方法,支持按 UID、完整名称、命名空间和名称查询

Pod 来源说明

Kubelet 从三个来源发现 Pod 更新:

  • File:本地配置文件定义的 Pod
  • HTTP:通过 HTTP 端点接收的 Pod 规范
  • API Server:从 Kubernetes API Server 同步的 Pod

来自非 API Server 源的 Pod 被称为静态 Pod(Static Pod),API Server 并不知道静态 Pod 的存在。为了让 API Server 能够感知静态 Pod 的状态,Kubelet 会为每个静态 Pod 创建一个镜像 Pod(Mirror Pod)

5.1.2 接口定义

位置: pkg/kubelet/pod/pod_manager.go:30-55

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
type Manager interface {
// 按完整名称查询 Pod
GetPodByFullName(podFullName string) (*v1.Pod, bool)
// 按命名空间和名称查询 Pod
GetPodByName(namespace, name string) (*v1.Pod, bool)
// 按 UID 查询 Pod
GetPodByUID(types.UID) (*v1.Pod, bool)

// 查询静态 Pod 与镜像 Pod 之间的映射关系
GetPodByMirrorPod(*v1.Pod) (*v1.Pod, bool)
GetMirrorPodByPod(*v1.Pod) (*v1.Pod, bool)
GetPodAndMirrorPod(*v1.Pod) (pod, mirrorPod *v1.Pod, wasMirror bool)

// 获取所有 Pod
GetPods() []*v1.Pod
GetPodsAndMirrorPods() (allPods []*v1.Pod, allMirrorPods []*v1.Pod, orphanedMirrorPodFullnames []string)

// Pod 更新操作
SetPods(pods []*v1.Pod)
AddPod(pod *v1.Pod)
UpdatePod(pod *v1.Pod)
RemovePod(pod *v1.Pod)

// UID 转换
TranslatePodUID(uid types.UID) kubetypes.ResolvedPodUID
GetUIDTranslations() (podToMirror map[kubetypes.ResolvedPodUID]kubetypes.MirrorPodUID, mirrorToPod map[kubetypes.MirrorPodUID]kubetypes.ResolvedPodUID)
}

5.1.3 实现原理

PodManager 的实现 (basicManager) 使用多个索引 Map 来高效存储和查询 Pod:

位置: pkg/kubelet/pod/pod_manager.go:138-158

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type basicManager struct {
lock sync.RWMutex

// 通过 UID 索引的常规 Pod
podByUID map[kubetypes.ResolvedPodUID]*v1.Pod
// 通过 UID 索引的镜像 Pod
mirrorPodByUID map[kubetypes.MirrorPodUID]*v1.Pod

// 通过完整名称索引的 Pod,便于快速访问
podByFullName map[string]*v1.Pod
mirrorPodByFullName map[string]*v1.Pod

// 镜像 Pod UID 到 Pod UID 的映射
translationByUID map[kubetypes.MirrorPodUID]kubetypes.ResolvedPodUID
}

Pod 类型识别

  • 镜像 Pod 识别:通过检查 Pod 的 Annotations 中是否存在 mirror.kubelet.io/annotation-key 注解(pkg/kubelet/types/pod_update.go:144-149
  • 静态 Pod 识别:通过检查 Pod 的来源是否为 API Server(pkg/kubelet/types/pod_update.go:153-156
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IsMirrorPod 检查是否为镜像 Pod
func IsMirrorPod(pod *v1.Pod) bool {
if pod.Annotations == nil {
return false
}
_, ok := pod.Annotations[ConfigMirrorAnnotationKey]
return ok
}

// IsStaticPod 检查是否为静态 Pod
func IsStaticPod(pod *v1.Pod) bool {
source, err := GetPodSource(pod)
return err == nil && source != ApiserverSource
}

索引更新逻辑

updatePodsInternal 方法中(pkg/kubelet/pod/pod_manager.go:215-239),当更新 Pod 时:

  1. 如果是镜像 Pod:更新 mirrorPodByUIDmirrorPodByFullNametranslationByUID
  2. 如果是普通 Pod:更新 podByUIDpodByFullName,并建立与镜像 Pod 的映射关系

镜像 Pod 的生命周期

  1. Kubelet 为静态 Pod 在 API Server 中创建镜像 Pod
  2. 镜像 Pod 与静态 Pod 具有相同的名称和命名空间(但 UID 不同)
  3. Kubelet 使用镜像 Pod 的名称向 API Server 报告状态
  4. 当静态 Pod 被删除时,关联的镜像 Pod 也会被清理

5.1.4 核心方法示例

按 UID 查询 Pod

1
2
3
4
5
6
func (pm *basicManager) GetPodByUID(uid types.UID) (*v1.Pod, bool) {
pm.lock.RLock()
defer pm.lock.RUnlock()
pod, ok := pm.podByUID[kubetypes.ResolvedPodUID(uid)]
return pod, ok
}

UID 转换

1
2
3
4
5
6
7
8
9
10
11
12
func (pm *basicManager) TranslatePodUID(uid types.UID) kubetypes.ResolvedPodUID {
if uid == "" {
return kubetypes.ResolvedPodUID(uid)
}
pm.lock.RLock()
defer pm.lock.RUnlock()
// 如果 UID 属于镜像 Pod,返回对应的静态 Pod UID
if translated, ok := pm.translationByUID[kubetypes.MirrorPodUID(uid)]; ok {
return translated
}
return kubetypes.ResolvedPodUID(uid)
}

5.1.5 孤立镜像 Pod 检测

PodManager 提供检测孤立镜像 Pod 的能力(GetPodsAndMirrorPods 方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
func (pm *basicManager) GetPodsAndMirrorPods() (allPods []*v1.Pod, allMirrorPods []*v1.Pod, orphanedMirrorPodFullnames []string) {
pm.lock.RLock()
defer pm.lock.RUnlock()

// 遍历所有镜像 Pod,检查是否有对应的静态 Pod
for podFullName := range pm.mirrorPodByFullName {
if _, ok := pm.podByFullName[podFullName]; !ok {
// 没有对应的静态 Pod,说明是孤立的镜像 Pod
orphanedMirrorPodFullnames = append(orphanedMirrorPodFullnames, podFullName)
}
}
return allPods, allMirrorPods, orphanedMirrorPodFullnames
}

孤立镜像 Pod 出现的原因可能是:

  • 静态 Pod 的配置文件被删除
  • 静态 Pod 的定义发生变化,但镜像 Pod 尚未同步

5.1.6 设计要点

  1. 线程安全:使用 sync.RWMutex 保护所有内部 Map,支持并发读操作
  2. 多索引设计:通过 UID、完整名称、镜像 Pod 映射等多个维度建立索引,满足不同的查询需求
  3. 内存存储:PodManager 只维护内存中的 Pod 索引,不持久化状态
  4. 只读视图:所有查询方法都返回只读指针,确保数据一致性

5.1.7 参考代码文件

文件路径 说明
pkg/kubelet/pod/pod_manager.go PodManager 接口定义和 basicManager 实现
pkg/kubelet/types/pod_update.go Pod 类型识别函数(IsMirrorPod、IsStaticPod)
pkg/kubelet/container/pod.go GetPodFullName、BuildPodFullName 函数定义
staging/src/k8s.io/apimachinery/pkg/types/types.go UID 类型定义

5.2 StatusManager

StatusManager 是 Kubelet 中负责管理 Pod 状态的核心组件,承担着将 Pod 状态同步到 API Server 的关键职责。它维护 Pod 状态的本地缓存,并通过版本控制机制确保状态更新的正确性和顺序性。

5.2.1 功能概述

StatusManager 的核心职责包括:

  1. Pod 状态缓存:维护内存中的 Pod 状态,支持快速查询
  2. 状态同步到 API Server:定期将本地状态同步到 Kubernetes API Server
  3. 版本控制机制:通过单调递增的版本号防止过时的状态更新覆盖新状态
  4. 容器状态管理:管理容器的就绪状态、启动状态等

状态同步机制说明

StatusManager 采用两种同步策略:

  • 主动触发:当 Pod 状态发生变化时,立即触发同步
  • 定期同步:每 10 秒进行一次全量状态检查和同步(pkg/kubelet/status/status_manager.go:159 定义 syncPeriod = 10 * time.Second

5.2.2 接口定义

位置: pkg/kubelet/status/status_manager.go:69-88

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// manager 结构体
type manager struct {
kubeClient clientset.Interface
podManager PodManager
// Map from pod UID to sync status of the corresponding pod.
podStatuses map[types.UID]versionedPodStatus
podStatusesLock sync.RWMutex
podStatusChannel chan struct{}
// Map from (mirror) pod UID to latest status version successfully sent to the API server.
// apiStatusVersions must only be accessed from the sync thread.
apiStatusVersions map[kubetypes.MirrorPodUID]uint64
podDeletionSafety PodDeletionSafetyProvider

podStartupLatencyHelper PodStartupLatencyStateHelper
// state allows to save/restore pod resource allocation and tolerate kubelet restarts.
state state.State
// stateFileDirectory holds the directory where the state file for checkpoints is held.
stateFileDirectory string
}

版本化状态结构 (pkg/kubelet/status/status_manager.go:52-67):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// A wrapper around v1.PodStatus that includes a version to enforce that stale pod statuses are
// not sent to the API server.
type versionedPodStatus struct {
// version is a monotonically increasing version number (per pod).
version uint64
// Pod name & namespace, for sending updates to API server.
podName string
podNamespace string
// at is the time at which the most recent status update was detected
at time.Time

// True if the status is generated at the end of SyncTerminatedPod, or after it is completed.
podIsFinished bool

status v1.PodStatus
}

Manager 接口 (pkg/kubelet/status/status_manager.go:119-157):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
type Manager interface {
PodStatusProvider

// Start the API server status sync loop.
Start()

// SetPodStatus caches updates the cached status for the given pod, and triggers a status update.
SetPodStatus(pod *v1.Pod, status v1.PodStatus)

// SetContainerReadiness updates the cached container status with the given readiness, and
// triggers a status update.
SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool)

// SetContainerStartup updates the cached container status with the given startup, and
// triggers a status update.
SetContainerStartup(podUID types.UID, containerID kubecontainer.ContainerID, started bool)

// TerminatePod resets the container status for the provided pod to terminated and triggers
// a status update.
TerminatePod(pod *v1.Pod)

// RemoveOrphanedStatuses scans the status cache and removes any entries for pods not included in
// the provided podUIDs.
RemoveOrphanedStatuses(podUIDs map[types.UID]bool)

// GetContainerResourceAllocation returns checkpointed AllocatedResources value for the container
GetContainerResourceAllocation(podUID string, containerName string) (v1.ResourceList, bool)

// GetPodResizeStatus returns checkpointed PodStatus.Resize value
GetPodResizeStatus(podUID string) (v1.PodResizeStatus, bool)

// SetPodAllocation checkpoints the resources allocated to a pod's containers.
SetPodAllocation(pod *v1.Pod) error

// SetPodResizeStatus checkpoints the last resizing decision for the pod.
SetPodResizeStatus(podUID types.UID, resize v1.PodResizeStatus) error
}

5.2.3 实现原理

版本控制机制

StatusManager 使用版本号来防止过时的状态更新覆盖新的状态。每次状态更新时,版本号都会单调递增:

1
2
3
4
5
6
7
8
// pkg/kubelet/status/status_manager.go:697-703
newStatus := versionedPodStatus{
status: status,
version: cachedStatus.version + 1, // 版本号递增
podName: pod.Name,
podNamespace: pod.Namespace,
podIsFinished: podIsFinished,
}

syncBatch 方法中,只有当本地版本号大于等于已发送到 API Server 的版本号时才进行同步:

1
2
3
4
5
6
7
8
// pkg/kubelet/status/status_manager.go:808-811
if !all {
if m.apiStatusVersions[uidOfStatus] >= status.version {
continue
}
updatedStatuses = append(updatedStatuses, podSync{uid, uidOfStatus, status})
continue
}

状态同步流程 (pkg/kubelet/status/status_manager.go:194-235):

  1. 启动同步循环:在 Start() 方法中启动一个 goroutine,监听两种触发条件:
    • podStatusChannel:当 Pod 状态发生变化时收到通知
    • syncTicker:每 10 秒触发一次定期同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (m *manager) Start() {
// ...
syncTicker := time.NewTicker(syncPeriod).C

// syncPod and syncBatch share the same go routine to avoid sync races.
go wait.Forever(func() {
for {
select {
case <-m.podStatusChannel:
klog.V(4).InfoS("Syncing updated statuses")
m.syncBatch(false)
case <-syncTicker:
klog.V(4).InfoS("Syncing all statuses")
m.syncBatch(true)
}
}
}, 0)
}

状态更新内部逻辑 (pkg/kubelet/status/status_manager.go:603-721):

  1. 检查容器状态转换的合法性
  2. 更新各条件(Conditions)的 LastTransitionTime
  3. 设置或保持 StartTime 不变
  4. 规范化状态(normalizeStatus
  5. 如果状态未发生变化且不是强制更新,则跳过
  6. 创建新的版本化状态并缓存
  7. 触发同步

状态去重机制

使用带缓冲的 channel(容量为 1)来避免频繁触发同步:

1
2
3
4
5
6
// pkg/kubelet/status/status_manager.go:716-720
select {
case m.podStatusChannel <- struct{}{}:
default:
// there's already a status update pending
}

5.2.4 状态一致性保障

Pod 状态去重 (pkg/kubelet/status/status_manager.go:175-192):

使用 isPodStatusByKubeletEqual 函数比较状态,只有当 kubelet 管理的条件(Conditions)发生变化时才触发同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool {
oldCopy := oldStatus.DeepCopy()
for _, c := range status.Conditions {
// both owned and shared conditions are used for kubelet status equality
if kubetypes.PodConditionByKubelet(c.Type) || kubetypes.PodConditionSharedByKubelet(c.Type) {
_, oc := podutil.GetPodCondition(oldCopy, c.Type)
if oc == nil || oc.Status != c.Status || oc.Message != c.Message || oc.Reason != c.Reason {
return false
}
}
}
oldCopy.Conditions = status.Conditions
return apiequality.Semantic.DeepEqual(oldCopy, status)
}

容器状态转换合法性检查 (pkg/kubelet/status/status_manager.go:550-598):

防止容器从终止状态非法转换回非终止状态:

1
2
3
4
5
6
7
func checkContainerStateTransition(oldStatuses, newStatuses *v1.PodStatus, podSpec *v1.PodSpec) error {
// If we should always restart, containers are allowed to leave the terminated state
if podSpec.RestartPolicy == v1.RestartPolicyAlways {
return nil
}
// ... 检查容器状态转换是否合法
}

5.2.5 孤立状态清理

当 Pod 被删除时,StatusManager 需要清理相关的缓存状态。RemoveOrphanedStatuses 方法扫描状态缓存,移除不再存在的 Pod 的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pkg/kubelet/status/status_manager.go:750-762
func (m *manager) RemoveOrphanedStatuses(podUIDs map[types.UID]bool) {
m.podStatusesLock.Lock()
defer m.podStatusesLock.Unlock()
for key := range m.podStatuses {
if _, ok := podUIDs[key]; !ok {
klog.V(5).InfoS("Removing pod from status map.", "podUID", key)
delete(m.podStatuses, key)
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
m.state.Delete(string(key), "")
}
}
}
}

5.2.6 Pod 终止处理

TerminatePod 方法确保在 Pod 生命周期结束时,容器状态被正确设置为终止状态,并处理缺失的容器状态信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pkg/kubelet/status/status_manager.go:428-492
func (m *manager) TerminatePod(pod *v1.Pod) {
// ensure that all containers have a terminated state - because we do not know whether the container
// was successful, always report an error
// ...

// Make sure all pods are transitioned to a terminal phase.
if !kubetypes.IsStaticPod(pod) {
switch status.Phase {
case v1.PodSucceeded, v1.PodFailed:
// do nothing, already terminal
case v1.PodPending, v1.PodRunning:
status.Phase = v1.PodFailed
}
}

m.updateStatusInternal(pod, status, true, true)
}

5.2.7 设计要点

  1. 线程安全:使用 sync.RWMutex 保护 podStatuses Map,支持并发读操作
  2. 版本控制:通过单调递增的版本号防止过时的状态更新覆盖新状态
  3. 去重优化:使用带缓冲的 channel 避免频繁触发同步
  4. 定期同步:每 10 秒进行一次全量状态检查,确保最终一致性
  5. 状态持久化:支持将 Pod 资源分配状态 checkpoint 到磁盘,支持 Kubelet 重启后恢复
  6. Pod 删除安全:在删除 Pod 前确保容器已终止(通过 PodDeletionSafetyProvider 接口)

5.2.8 参考代码文件

文件路径 说明
pkg/kubelet/status/status_manager.go StatusManager 接口定义和 manager 实现
pkg/kubelet/status/state/state.go 状态持久化接口定义
pkg/kubelet/container/pod.go GetPodFullName、BuildPodFullName 函数定义
pkg/api/v1/pod/util.go Pod 工具函数

5.3 ProbeManager

ProbeManager 是 Kubelet 中负责管理容器健康检查探针的核心子系统。它为每个配置了探针的容器创建独立的探针工作线程,定期执行探针并缓存结果,用于更新 Pod 的 Ready 和 Started 状态。

5.3.1 功能概述

ProbeManager 主要负责以下功能:

  1. 探针生命周期管理:为每个容器的每种探针创建独立的工作线程(worker)
  2. 探针执行:定期执行 Liveness、Readiness 和 Startup 三种类型的探针
  3. 结果缓存:将探针结果存储在缓存中供其他组件查询
  4. 状态更新:根据探针结果更新 Pod 状态中的 Ready 和 Started 字段
  5. 指标暴露:通过 Prometheus 暴露探针执行结果和耗时指标

探针类型说明:

  • Liveness Probe(存活探针):检测容器是否存活,失败时会导致容器重启
  • Readiness Probe(就绪探针):检测容器是否准备好接收流量,失败时将容器从 Service 端点中移除
  • Startup Probe(启动探针):检测容器是否启动完成,失败时会导致容器重启,在启动完成前会阻止 Liveness 和 Readiness 探针执行

5.3.2 接口定义

ProbeManager 接口定义如下(pkg/kubelet/prober/prober_manager.go):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Manager interface {
// AddPod 为每个配置了探针的容器创建新的探针工作线程
AddPod(pod *v1.Pod)

// StopLivenessAndStartup 在终止期间停止 liveness 和 startup 探针
StopLivenessAndStartup(pod *v1.Pod)

// RemovePod 清理已移除的 Pod 状态,包括终止探针工作线程和删除缓存结果
RemovePod(pod *v1.Pod)

// CleanupPods 清理不再运行的 Pod
CleanupPods(desiredPods map[types.UID]sets.Empty)

// UpdatePodStatus 根据容器运行状态、缓存的探针结果和工作线程状态修改 PodStatus
UpdatePodStatus(*v1.Pod, *v1.PodStatus)
}

关键数据结构 - probeKey 用于唯一标识容器探针:

1
2
3
4
5
6
7
8
9
10
11
12
13
type probeKey struct {
podUID types.UID // Pod 唯一标识
containerName string // 容器名称
probeType probeType // 探针类型(liveness、readiness、startup)
}

type probeType int

const (
liveness probeType = iota
readiness
startup
)

5.3.3 实现原理

ProbeManager 的核心实现包含以下组件:

1. Manager 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type manager struct {
// 探针工作线程映射表
workers map[probeKey]*worker
workerLock sync.RWMutex

// 状态管理器,提供 Pod IP 和容器 ID
statusManager status.Manager

// 就绪探针结果管理器
readinessManager results.Manager
// 存活探针结果管理器
livenessManager results.Manager
// 启动探针结果管理器
startupManager results.Manager

// 探针执行器
prober *prober
}

2. 探针执行器(prober)

prober 负责实际执行探针逻辑,支持四种探测方式:

1
2
3
4
5
6
7
8
type prober struct {
exec execprobe.Prober // Exec 探针
http httpprobe.Prober // HTTP GET 探针
tcp tcpprobe.Prober // TCP 端口探针
grpc grpcprobe.Prober // gRPC 探针
runner kubecontainer.CommandRunner
recorder record.EventRecorder
}

3. 工作线程(worker)

每个探针由独立的工作线程处理,主要字段包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type worker struct {
stopCh chan struct{} // 停止信号
manualTriggerCh chan struct{} // 手动触发信号
pod *v1.Pod // 关联的 Pod
container v1.Container // 探针目标容器
spec *v1.Probe // 探针配置
probeType probeType // 探针类型
resultsManager results.Manager // 结果管理器
probeManager *manager // 探针管理器引用
containerID kubecontainer.ContainerID
lastResult results.Result
resultRun int // 连续相同结果次数
onHold bool // 是否暂停探针
}

4. 结果管理器(results.Manager)

结果管理器提供探针结果的缓存和更新通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Result int

const (
Unknown Result = iota - 1
Success
Failure
)

type Manager interface {
Get(kubecontainer.ContainerID) (Result, bool)
Set(kubecontainer.ContainerID, Result, *v1.Pod)
Remove(kubecontainer.ContainerID)
Updates() <-chan Update
}

5.3.4 三种探针类型

Startup Probe(启动探针)

  • 用途:检测容器是否成功启动
  • 初始值:results.Unknown
  • 成功时:设置为 results.Success,允许 Liveness 和 Readiness 探针开始执行
  • 失败时:容器会被重启
  • 适用场景:启动较慢的容器,避免在启动期间误判为存活失败

Liveness Probe(存活探针)

  • 用途:检测容器是否仍在运行
  • 初始值:results.Success(容器启动时默认为健康)
  • 失败时:容器会被重启
  • 适用场景:检测死锁、hang 等导致容器无法继续运行的情况
  • 重要:在 Pod 删除时会设置为 Success 以实现优雅关闭

Readiness Probe(就绪探针)

  • 用途:检测容器是否准备好接收流量
  • 初始值:results.Failure(容器启动时默认不健康)
  • 成功时:容器被添加到 Service 端点
  • 失败时:容器从 Service 端点中移除
  • 适用场景:检测依赖外部服务、初始化未完成等情况

5.3.5 探针执行流程

AddPod 流程

  1. 遍历 Pod 的所有容器和可重启的 Init 容器
  2. 为每个配置了探针的容器创建对应的 worker
  3. 启动 worker 的探针循环(go w.run()

Worker 探针循环(run 方法)

  1. 初始化定时器(PeriodSeconds 指定的间隔)
  2. 如果 Kubelet 最近启动,随机等待一段时间避免探针风暴
  3. 进入探测循环:
    • 调用 doProbe() 执行单次探测
    • 等待下一个探测周期或停止信号

单次探测流程(doProbe 方法)

  1. 获取 Pod 最新状态
  2. 检查 Pod 是否已终止,已终止则退出 worker
  3. 获取容器状态和容器 ID
  4. 如果容器重启(容器 ID 变化),重置探针状态
  5. 检查 InitialDelaySeconds,在初始延迟期内跳过探测
  6. 如果存在 Startup Probe 且未成功,阻止 Liveness 和 Readiness 探针执行
  7. 调用 prober.probe() 执行实际探测
  8. 根据 FailureThresholdSuccessThreshold 判断是否更新结果
  9. 如果 Liveness/Startup 探针失败,设置 onHold=true 暂停后续探测

探针探测方式

  1. Exec Probe:在容器内执行命令,检查退出码
  2. HTTP Get Probe:发送 HTTP GET 请求,检查状态码(2xx 为成功)
  3. TCP Socket Probe:尝试建立 TCP 连接,成功则通过
  4. gRPC Probe:使用 gRPC Health Check 协议

UpdatePodStatus 流程

  1. 遍历所有容器状态
  2. 检查容器是否已启动(通过 Startup Probe 结果)
  3. 如果容器已启动,检查 Readiness Probe 结果设置 Ready 状态
  4. 如果容器未启动,设置 Started=false,Ready=false
  5. 对于 readiness 探针,如果探针尚未执行,触发立即探测

5.3.6 设计要点

  1. 线程安全:使用 sync.RWMutex 保护 workers Map,支持并发读操作
  2. 独立工作线程:每种探针类型独立运行,避免相互影响
  3. 启动屏障:Startup Probe 成功前,Liveness 和 Readiness 探针不会执行
  4. 重试机制:探针失败会重试最多 3 次(maxProbeRetries = 3
  5. 防抖动设计:使用 FailureThresholdSuccessThreshold 避免临时波动导致误判
  6. 优雅关闭:Pod 删除时设置 Liveness 和 Startup 探针结果为 Success
  7. 指标暴露:通过 Prometheus 暴露探针总次数和耗时指标
  8. 容器重启处理:容器重启后自动重置探针状态,避免使用旧的容器 ID

5.3.7 参考代码文件

文件路径 说明
pkg/kubelet/prober/prober_manager.go ProbeManager 接口定义和 manager 实现
pkg/kubelet/prober/prober.go 探针执行器实现
pkg/kubelet/prober/worker.go 探针工作线程实现
pkg/kubelet/prober/results/results_manager.go 探针结果缓存管理
pkg/probe/exec/exec.go Exec 探针实现
pkg/probe/http/http.go HTTP 探针实现
pkg/probe/tcp/tcp.go TCP 探针实现
pkg/probe/grpc/grpc.go gRPC 探针实现

5.4 VolumeManager

5.4.1 功能概述

VolumeManager 是 Kubelet 中负责管理存储卷(Volume)的核心组件。它的主要职责是根据调度到当前节点的 Pod 的需求,异步地完成卷的挂载(Mount)、卸载(Unmount)、附加(Attach)和分离(Detach)操作。VolumeManager 确保 Pod 所需的存储卷在容器启动前准备就绪,并在 Pod 删除后正确清理。

VolumeManager 的核心设计理念是维护两个状态世界:

  • 期望状态(Desired State of World):根据当前调度的 Pod,应该存在哪些卷
  • 实际状态(Actual State of World):当前节点上实际附加和挂载的卷

通过不断调和这两个状态的差异,VolumeManager 确保存储卷的实际状态与 Pod 需求保持一致。

5.4.2 接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// VolumeManager 定义了卷管理器的核心接口
type VolumeManager interface {
// Run 启动卷管理器和所有异步循环
Run(sourcesReady config.SourcesReady, stopCh <-chan struct{})

// WaitForAttachAndMount 等待指定 Pod 的所有卷完成附加和挂载
WaitForAttachAndMount(ctx context.Context, pod *v1.Pod) error

// WaitForUnmount 等待指定 Pod 的所有卷完成卸载
WaitForUnmount(ctx context.Context, pod *v1.Pod) error

// GetMountedVolumesForPod 获取指定 Pod 成功挂载的卷映射
GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap

// GetPossiblyMountedVolumesForPod 获取可能挂载的卷(包括挂载中的卷)
GetPossiblyMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap

// GetExtraSupplementalGroupsForPod 获取 Pod 的额外补充组
GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64

// GetVolumesInUse 获取当前正在使用的所有卷
GetVolumesInUse() []v1.UniqueVolumeName

// ReconcilerStatesHasBeenSynced 返回 reconciler 状态是否已同步
ReconcilerStatesHasBeenSynced() bool

// VolumeIsAttached 检查指定卷是否已附加到当前节点
VolumeIsAttached(volumeName v1.UniqueVolumeName) bool

// MarkVolumesAsReportedInUse 标记卷已被报告为使用中
MarkVolumesAsReportedInUse(volumesReportedAsInUse []v1.UniqueVolumeName)
}

5.4.3 核心组件架构

VolumeManager 由以下核心组件构成:

  1. DesiredStateOfWorld(期望状态缓存)

    • 存储应该附加到当前节点的卷以及引用这些卷的 Pod
    • 由 DesiredStateOfWorldPopulator 定期更新
    • 文件位置:pkg/kubelet/volumemanager/cache/desired_state_of_world.go
  2. ActualStateOfWorld(实际状态缓存)

    • 存储当前节点上实际附加的卷以及已成功挂载的 Pod
    • 在 attach、detach、mount、unmount 操作成功完成后更新
    • 文件位置:pkg/kubelet/volumemanager/cache/actual_state_of_world.go
  3. DesiredStateOfWorldPopulator(期望状态填充器)

    • 定期扫描 PodManager 获取当前调度的 Pod
    • 将 Pod 所需的卷添加到期望状态
    • 处理 CSI 迁移(将 in-tree 卷规范转换为 CSI 规范)
    • 文件位置:pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go
  4. Reconciler(调和器)

    • 定期运行,比较期望状态和实际状态
    • 触发 attach、detach、mount、unmount 操作来消除差异
    • 执行顺序:先卸载 -> 再挂载/附加 -> 最后分离设备
    • 文件位置:pkg/kubelet/volumemanager/reconciler/reconciler.go
  5. OperationExecutor(操作执行器)

    • 实际执行卷的附加、分离、挂载、卸载操作
    • 提供操作重试、超时控制、错误处理机制
    • 位于 pkg/volume/util/operationexecutor/

5.4.4 卷挂载/卸载流程

卷挂载流程(Mount):

1
2
3
4
5
6
7
8
9
1. DesiredStateOfWorldPopulator 检测到新 Pod 调度到节点
2. 将 Pod 所需的卷添加到期望状态缓存
3. Reconciler 检测到期望状态中有新卷需要挂载
4. 调用 OperationExecutor 执行挂载操作:
a. 如果需要 Attach(卷插件支持),先执行 Attach
b. 等待卷附加到节点
c. 执行 Mount 操作,将卷挂载到 Pod 的指定路径
5. 挂载成功后更新 ActualStateOfWorld
6. Pod Worker 可以继续启动容器

卷卸载流程(Unmount):

1
2
3
4
5
6
7
1. Pod 被删除或卷不再需要
2. DesiredStateOfWorldPopulator 从期望状态中移除该 Pod
3. Reconciler 检测到卷不再被任何 Pod 引用
4. 调用 OperationExecutor 执行卸载操作:
a. 执行 Unmount 操作,从 Pod 路径卸载卷
b. 如果需要 Detach,执行设备分离操作
5. 卸载成功后更新 ActualStateOfWorld

关键时间常量:

  • reconcilerLoopSleepPeriod:100ms - Reconciler 循环执行间隔
  • desiredStateOfWorldPopulatorLoopSleepPeriod:100ms - 期望状态填充间隔
  • podAttachAndMountTimeout:2分3秒 - Pod 挂载超时时间
  • waitForAttachTimeout:10分钟 - 卷附加超时时间

5.4.5 CSI 驱动集成

VolumeManager 通过以下机制支持 CSI(Container Storage Interface)驱动:

  1. CSI 迁移(CSI Migration)

    • 将传统的 in-tree 卷规范转换为 CSI 规范
    • 使用 csimigration.PluginManager 判断是否需要迁移
    • 使用 intreeToCSITranslator 执行规范转换
  2. CSI 插件发现

    • 通过 CSIDriver informer 动态发现集群中的 CSI 驱动
    • 启动时运行 volumePluginMgr.Run(stopCh) 启动 CSI 驱动 informer
  3. 卷生命周期管理

    • CSI 卷通过 CSI 驱动实现 Attach/Mount/Detach/Unmount
    • OperationExecutor 调用 CSI 插件的具体方法完成操作

CSI 迁移处理流程:

1
2
3
4
5
6
// 在 desired_state_of_world_populator.go 中
migratable, err := dswp.csiMigratedPluginManager.IsMigratable(volumeSpec)
if migratable {
// 将 in-tree 规范转换为 CSI 规范
volumeSpec, err = csimigration.TranslateInTreeSpecToCSI(volumeSpec, pod.Namespace, dswp.intreeToCSITranslator)
}

5.4.6 卷状态管理

VolumeManager 维护详细的卷状态信息:

  1. 卷状态类型

    • VolumeMounted:卷已成功挂载到 Pod
    • VolumeMountUncertain:卷可能正在挂载中(用于处理重建场景)
    • VolumeUnmounted:卷已从 Pod 卸载
    • DeviceGloballyMounted:设备已全局挂载
    • DeviceMountUncertain:设备可能已全局挂载
  2. 状态存储

    • 期望状态:记录哪些卷应该被挂载
    • 实际状态:记录哪些卷实际已被挂载
    • 错误状态:记录挂载过程中的错误信息
  3. 卷使用报告

    • GetVolumesInUse() 返回当前使用的所有卷
    • 用于更新 Node.Status.VolumesInUse
    • 通过 MarkVolumesAsReportedInUse() 标记已报告的卷

5.4.7 设计要点

  1. 异步操作:所有卷操作都是异步执行的,不会阻塞主线程
  2. 幂等性:挂载/卸载操作设计为可重复执行,支持重试
  3. 错误恢复:通过定期调和机制自动恢复失败的操作
  4. 状态一致性
    • 卸载先于挂载执行,避免同一卷被多个 Pod 争用
    • 设备分离前确保所有 Pod 都已卸载卷
  5. 重建处理:Kubelet 重启后,Reconciler 会尝试重建卷的实际状态
  6. CSI 迁移透明性:对用户来说,CSI 迁移是无缝的,规范转换在内部完成
  7. SELinux 支持:支持 SELinux 上下文配置,确保安全策略正确应用

5.4.8 参考代码文件

文件路径 说明
pkg/kubelet/volumemanager/volume_manager.go VolumeManager 接口定义和主要实现
pkg/kubelet/volumemanager/cache/desired_state_of_world.go 期望状态缓存实现
pkg/kubelet/volumemanager/cache/actual_state_of_world.go 实际状态缓存实现
pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go 期望状态填充器实现
pkg/kubelet/volumemanager/reconciler/reconciler.go 调和器核心实现
pkg/volume/util/operationexecutor/operation_executor.go 操作执行器实现
pkg/volume/csimigration/plugin_manager.go CSI 迁移管理器
pkg/volume/csimigration/translator.go In-tree 到 CSI 规范转换器

5.5 EvictionManager

5.5.1 功能概述

EvictionManager 是 Kubelet 的资源压力管理组件,负责在节点资源(内存、磁盘、PID)不足时主动驱逐 Pod 以保障节点稳定性。当节点资源达到配置的驱逐阈值时,EvictionManager 会按照一定的策略选择并终止低优先级 Pod,释放被占用的资源。

EvictionManager 的核心功能包括:

  • 资源监控:持续监控节点的内存、磁盘空间、inode 和 PID 资源使用情况
  • 阈值评估:判断当前资源使用是否达到配置的驱逐阈值
  • 节点级回收:在驱逐 Pod 之前,优先尝试节点级资源回收(如镜像垃圾回收、容器垃圾回收)
  • Pod 驱逐:当节点级回收无法满足需求时,按照优先级排序驱逐 Pod
  • 节点状态管理:更新节点条件(如 MemoryPressure、DiskPressure)供调度器参考

5.5.2 接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Manager evaluates when an eviction threshold for node stability has been met on the node.
type Manager interface {
// Start starts the control loop to monitor eviction thresholds at specified interval.
Start(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc, podCleanedUpFunc PodCleanedUpFunc, monitoringInterval time.Duration)

// IsUnderMemoryPressure returns true if the node is under memory pressure.
IsUnderMemoryPressure() bool

// IsUnderDiskPressure returns true if the node is under disk pressure.
IsUnderDiskPressure() bool

// IsUnderPIDPressure returns true if the node is under PID pressure.
IsUnderPIDPressure() bool
}

核心配置结构

1
2
3
4
5
6
7
8
9
10
11
12
type Config struct {
// PressureTransitionPeriod 是 kubelet 离开压力状态需要等待的时间
PressureTransitionPeriod time.Duration
// MaxPodGracePeriodSeconds 是软驱逐时使用的最大宽限期(秒)
MaxPodGracePeriodSeconds int64
// Thresholds 定义了触发驱逐的条件集合
Thresholds []evictionapi.Threshold
// KernelMemcgNotification 是否与 kernel memcg 通知集成
KernelMemcgNotification bool
// PodCgroupRoot 是包含所有 Pod 的 cgroup 路径
PodCgroupRoot string
}

驱逐信号类型(定义于 pkg/kubelet/eviction/api/types.go):

信号 描述
memory.available 可用内存(capacity - workingSet),单位字节
allocatableMemory.available Pod 可分配的内存(allocatable - workingSet)
nodefs.available Kubelet 使用的文件系统可用空间
nodefs.inodesFree Kubelet 使用的文件系统可用 inode
imagefs.available 容器运行时存储镜像的文件系统可用空间
imagefs.inodesFree 容器运行时存储镜像的文件系统可用 inode
containerfs.available 容器可写层文件系统可用空间
containerfs.inodesFree 容器可写层文件系统可用 inode
pid.available 可用进程 ID 数量

5.5.3 驱逐阈值配置

阈值配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Kubelet 配置
evictionHard:
memory.available: "100Mi" # 内存少于 100MiB 时驱逐
nodefs.available: "10%" # 磁盘空间少于 10% 时驱逐
imagefs.available: "15%" # 镜像磁盘空间少于 15% 时驱逐
pid.available: "1000" # PID 少于 1000 时驱逐

evictionSoft:
memory.available: "500Mi"
nodefs.available: "15%"

evictionSoftGracePeriod:
memory.available: "1m30s"
nodefs.available: "2m"

evictionMinimumReclaim:
memory.available: "50Mi"
nodefs.available: "100Mi"

# 节点压力转换周期(节点离开压力状态需要等待的时间)
evictionPressureTransitionPeriod: "30s"

# 驱逐时 Pod 的最大宽限期
maxPodGracePeriodSeconds: 30

阈值配置说明

  • evictionHard:硬驱逐阈值,一旦触发立即执行驱逐
  • evictionSoft:软驱逐阈值,需要配合 GracePeriod 使用,达到阈值后等待指定时间才执行驱逐
  • evictionMinimumReclaim:最小回收量,驱逐后需要确保回收的资源量
  • 阈值支持两种格式
    • 绝对值:memory.available: "100Mi"
    • 百分比:nodefs.available: "10%"

5.5.4 驱逐决策算法

EvictionManager 的核心决策流程如下(pkg/kubelet/eviction/eviction_manager.go:synchronize):

1. 资源观测

1
2
// 从 cAdvisor 获取节点资源统计信息
observations, statsFunc := makeSignalObservations(summary)

2. 阈值评估

1
2
3
4
5
6
// 判断哪些阈值已被满足(忽略宽限期)
thresholds = thresholdsMet(thresholds, observations, false)

// 判断之前满足的阈值是否已满足最小回收量
thresholdsNotYetResolved := thresholdsMet(m.thresholdsMet, observations, true)
thresholds = mergeThresholds(thresholds, thresholdsNotYetResolved)

3. 节点条件更新

1
2
3
4
// 根据触发的阈值更新节点条件
nodeConditions := nodeConditions(thresholds)
// 判断是否在压力转换周期内
nodeConditions = nodeConditionsObservedSince(nodeConditionsLastObservedAt, m.config.PressureTransitionPeriod, now)

4. 节点级资源回收(在驱逐 Pod 之前):

1
2
3
4
// 尝试通过镜像垃圾回收和容器垃圾回收来释放资源
if m.reclaimNodeLevelResources(ctx, thresholdToReclaim.Signal, resourceToReclaim) {
return nil, nil // 回收成功,无需驱逐 Pod
}

5. Pod 驱逐执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 按优先级排序阈值
sort.Sort(byEvictionPriority(thresholds))
thresholdToReclaim, resourceToReclaim, foundAny := getReclaimableThreshold(thresholds)

// 获取对应资源的排序函数
rank, ok := m.signalToRankFunc[thresholdToReclaim.Signal]

// 对 Pod 进行排序
rank(activePods, statsFunc)

// 驱逐排序后的第一个 Pod
for i := range activePods {
pod := activePods[i]
if m.evictPod(pod, gracePeriodOverride, message, annotations, condition) {
return []*v1.Pod{pod}, nil
}
}

驱逐决策关键点

  • 每个驱逐周期(默认 10 秒)最多驱逐一个 Pod
  • 优先处理内存压力,然后是其他资源压力
  • 驱逐 Pod 后会等待 Pod 资源清理完成再继续下一个周期

5.5.5 Pod 驱逐顺序

EvictionManager 根据不同的资源压力类型使用不同的排序策略:

1. 内存压力驱逐顺序rankMemoryPressure):

1
2
3
func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {
orderedBy(exceedMemoryRequests(stats), priority, memory(stats)).Sort(pods)
}

排序优先级:

  1. 是否超过内存请求:先驱逐超过内存请求的 Pod
  2. Pod 优先级:优先级低的 Pod 优先被驱逐
  3. 内存使用量:超过请求量越大的 Pod 优先被驱逐

2. 磁盘压力驱逐顺序rankDiskPressureFunc):

1
2
3
4
5
func rankDiskPressureFunc(fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) rankFunc {
return func(pods []*v1.Pod, stats statsFunc) {
orderedBy(exceedDiskRequests(stats, fsStatsToMeasure, diskResource), priority, disk(stats, fsStatsToMeasure, diskResource)).Sort(pods)
}
}

排序优先级:

  1. 是否超过磁盘请求
  2. Pod 优先级
  3. 磁盘使用量

3. PID 压力驱逐顺序rankPIDPressure):

1
2
3
func rankPIDPressure(pods []*v1.Pod, stats statsFunc) {
orderedBy(priority, process(stats)).Sort(pods)
}

排序优先级:

  1. Pod 优先级
  2. 进程数量

Pod QoS 级别对驱逐的影响

QoS 级别 说明 驱逐优先级
Guaranteed 所有容器都有 CPU/内存 limits 最低
Burstable 至少一个容器有 CPU/内存 requests 中等
BestEffort 没有设置任何 requests/limits 最高

特殊保护

  • Critical Pod:标记为 Critical 的静态 Pod 不会被驱逐
  • PodDisruptionConditions:启用后,驱逐的 Pod 会获得 DisruptionTarget 条件,说明被驱逐的原因

5.5.6 本地存储驱逐

除了节点级资源压力,EvictionManager 还处理 Pod 级别的本地存储驱逐:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 本地存储驱逐检查
func (m *managerImpl) localStorageEviction(pods []*v1.Pod, statsFunc statsFunc) []*v1.Pod {
for _, pod := range pods {
// EmptyDir 容量限制检查
if m.emptyDirLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
}
// Pod 临时存储限制检查
if m.podEphemeralStorageLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
}
// 容器临时存储限制检查
if m.containerEphemeralStorageLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
}
}
return evicted
}

5.5.7 设计要点

  1. 优雅驱逐:支持配置驱逐宽限期,让 Pod 有时间完成清理工作
  2. 节点级回收优先:在驱逐 Pod 之前先尝试镜像和容器垃圾回收,减少对业务的影响
  3. 压力转换周期:节点离开压力状态需要等待一段时间,避免资源波动导致的频繁驱逐
  4. Critical Pod 保护:系统关键 Pod(如 kube-proxy、dns)不会被驱逐
  5. 调度器协同:通过节点条件(MemoryPressure、DiskPressure)通知调度器,避免新 Pod 被调度到压力节点
  6. 事件记录:驱逐事件会记录到 API Server,便于问题排查和监控

5.5.8 参考代码文件

文件路径 说明
pkg/kubelet/eviction/eviction_manager.go EvictionManager 接口定义和主要实现
pkg/kubelet/eviction/types.go Manager 接口和类型定义
pkg/kubelet/eviction/helpers.go 驱逐阈值评估、Pod 排序等辅助函数
pkg/kubelet/eviction/api/types.go 驱逐信号、阈值等 API 类型定义
pkg/kubelet/eviction/memory_threshold_notifier.go 内存阈值通知器实现(基于 cgroup)

5.6 Pod 生命周期事件生成器(PLEG)

Pod Lifecycle Event Generator(PLEG)是 Kubelet 的核心组件之一,负责检测 Pod 和容器的状态变化,并生成相应的事件通知给 Pod Worker 进行处理。PLEG 解决了 Kubelet 与容器运行时之间的状态同步问题,是实现 Pod 生命周期管理的关键机制。

5.6.1 PLEG 架构概述

PLEG 的核心功能是定期轮询容器运行时,获取当前 Pod 和容器的状态,并与内部缓存进行比较,从而检测状态变化并生成事件。这种设计使得 Kubelet 能够及时感知容器状态的变化,并触发相应的处理逻辑。

PLEG 在 Kubelet 架构中的位置十分关键:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────────┐
│ Kubelet │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ PLEG │───>│ Pod Worker │───>│ Status Manager │ │
│ │ │ │ │ │ (API Server) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Runtime │<───│ Cache │ │
│ │ (CRI/Docker)│ │ │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘

PLEG 通过以下接口定义与 Kubelet 其他组件交互:

1
2
3
4
5
6
7
// pkg/kubelet/pleg/pleg.go
type PodLifecycleEventGenerator interface {
Start()
Watch() chan *PodLifecycleEvent
Healthy() (bool, error)
UpdateCache(*kubecontainer.Pod, types.UID) (error, bool)
}

5.6.2 事件类型定义

PLEG 定义了以下几种生命周期事件类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
// pkg/kubelet/pleg/pleg.go
const (
// ContainerStarted - 容器启动事件
ContainerStarted PodLifeCycleEventType = "ContainerStarted"
// ContainerDied - 容器退出事件
ContainerDied PodLifeCycleEventType = "ContainerDied"
// ContainerRemoved - 容器移除事件
ContainerRemoved PodLifeCycleEventType = "ContainerRemoved"
// PodSync - 同步触发事件
PodSync PodLifeCycleEventType = "PodSync"
// ContainerChanged - 容器状态变化事件
ContainerChanged PodLifeCycleEventType = "ContainerChanged"
)

每个事件包含以下信息:

1
2
3
4
5
type PodLifecycleEvent struct {
ID types.UID // Pod ID
Type PodLifeCycleEventType // 事件类型
Data interface{} // 附加数据(通常是容器ID)
}

5.6.3 Generic PLEG 实现

Generic PLEG 是 PLEG 的基础实现,它通过定期轮询(relist)容器运行时来检测状态变化。这种设计简单可靠,适用于各种容器运行时。

核心数据结构

1
2
3
4
5
6
7
8
9
10
11
12
// pkg/kubelet/pleg/generic.go
type GenericPLEG struct {
runtime kubecontainer.Runtime // 容器运行时接口
eventChannel chan *PodLifecycleEvent // 事件通道
podRecords podRecords // Pod 状态记录(包含旧状态和新状态)
relistTime atomic.Value // 上次轮询时间
cache kubecontainer.Cache // 缓存
clock clock.Clock // 时钟(用于测试)
podsToReinspect map[types.UID]*kubecontainer.Pod // 需要重新检查的 Pod
stopCh chan struct{} // 停止通道
relistLock sync.Mutex // 轮询锁
}

轮询机制(Relist)

Generic PLEG 的核心是 Relist() 方法,它定期执行以下步骤:

  1. 获取所有 Pod:调用容器运行时接口获取当前运行的 Pod 和容器列表
  2. 状态比较:将当前状态与上一次记录的状态进行比较
  3. 事件生成:根据状态变化生成相应的事件
  4. 缓存更新:更新内部缓存和 Pod 状态
  5. 事件发送:将事件发送到事件通道
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
// pkg/kubelet/pleg/generic.go
func (g *GenericPLEG) Relist() {
// 获取所有 Pod
podList, err := g.runtime.GetPods(ctx, true)
if err != nil {
klog.ErrorS(err, "GenericPLEG: Unable to retrieve pods")
return
}

pods := kubecontainer.Pods(podList)
g.podRecords.setCurrent(pods)

// 比较旧状态和新状态,生成事件
eventsByPodID := map[types.UID][]*PodLifecycleEvent{}
for pid := range g.podRecords {
oldPod := g.podRecords.getOld(pid)
pod := g.podRecords.getCurrent(pid)
allContainers := getContainersFromPods(oldPod, pod)
for _, container := range allContainers {
events := computeEvents(oldPod, pod, &container.ID)
for _, e := range events {
updateEvents(eventsByPodID, e)
}
}
}
// ... 发送事件和更新缓存
}

状态转换与事件生成

Generic PLEG 使用以下容器状态模型:

1
2
3
4
5
6
7
8
type plegContainerState string

const (
plegContainerRunning plegContainerState = "running"
plegContainerExited plegContainerState = "exited"
plegContainerUnknown plegContainerState = "unknown"
plegContainerNonExistent plegContainerState = "non-existent"
)

状态转换时会生成相应的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pkg/kubelet/pleg/generic.go
func generateEvents(podID types.UID, cid string, oldState, newState plegContainerState) []*PodLifecycleEvent {
if newState == oldState {
return nil
}

switch newState {
case plegContainerRunning:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerStarted, Data: cid}}
case plegContainerExited:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}}
case plegContainerUnknown:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerChanged, Data: cid}}
case plegContainerNonExistent:
switch oldState {
case plegContainerExited:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerRemoved, Data: cid}}
default:
return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid},
{ID: podID, Type: ContainerRemoved, Data: cid}}
}
}
return nil
}

健康检查

PLEG 提供了健康检查机制,用于监控 PLEG 是否正常工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pkg/kubelet/pleg/generic.go
func (g *GenericPLEG) Healthy() (bool, error) {
relistTime := g.getRelistTime()
if relistTime.IsZero() {
return false, fmt.Errorf("pleg has yet to be successful")
}

elapsed := g.clock.Since(relistTime)
if elapsed > g.relistDuration.RelistThreshold {
return false, fmt.Errorf("pleg was last seen active %v ago; threshold is %v",
elapsed, g.relistDuration.RelistThreshold)
}
return true, nil
}

5.6.4 Evented PLEG 实现

Evented PLEG 是 Generic PLEG 的增强版本,它利用容器运行时的事件接口(CRI 事件流)来实现实时事件通知,减少了轮询带来的延迟和资源消耗。

架构设计

Evented PLEG 通过订阅容器运行时的事件流来实现实时事件处理:

1
2
3
4
5
6
7
8
9
10
// pkg/kubelet/pleg/evented.go
type EventedPLEG struct {
runtime kubecontainer.Runtime
runtimeService internalapi.RuntimeService // CRI 运行时服务
eventChannel chan *PodLifecycleEvent
cache kubecontainer.Cache
genericPleg podLifecycleEventGeneratorHandler // 回退到 Generic PLEG
stopCh chan struct{}
stopCacheUpdateCh chan struct{}
}

事件处理流程

Evented PLEG 的事件处理流程如下:

  1. 建立事件流:通过 GetContainerEvents 接口订阅容器事件
  2. 事件解析:将 CRI 事件转换为 PLEG 事件
  3. 状态更新:更新缓存和生成指标
  4. 事件发送:将事件发送到事件通道
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
// pkg/kubelet/pleg/evented.go
func (e *EventedPLEG) watchEventsChannel() {
containerEventsResponseCh := make(chan *runtimeapi.ContainerEventResponse, cap(e.eventChannel))

// 从运行时获取容器事件
go func() {
for {
err := e.runtimeService.GetContainerEvents(containerEventsResponseCh)
if err != nil {
metrics.EventedPLEGConnErr.Inc()
numAttempts++
e.Relist() // 回退到 Generic PLEG
}
}
}()

e.processCRIEvents(containerEventsResponseCh)
}

func (e *EventedPLEG) processCRIEvents(containerEventsResponseCh chan *runtimeapi.ContainerEventResponse) {
for event := range containerEventsResponseCh {
// 生成 Pod 状态
status, err := e.runtime.GeneratePodStatus(event)
// 更新缓存
e.cache.Set(podID, status, err, time.Unix(event.GetCreatedAt(), 0))
// 处理事件
e.processCRIEvent(event)
}
}

CRI 事件类型映射

Evented PLEG 将 CRI 事件类型映射为 PLEG 事件:

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
// pkg/kubelet/pleg/evented.go
func (e *EventedPLEG) processCRIEvent(event *runtimeapi.ContainerEventResponse) {
switch event.ContainerEventType {
case runtimeapi.ContainerEventType_CONTAINER_STOPPED_EVENT:
e.sendPodLifecycleEvent(&PodLifecycleEvent{
ID: types.UID(event.PodSandboxStatus.Metadata.Uid),
Type: ContainerDied,
Data: event.ContainerId,
})
case runtimeapi.ContainerEventType_CONTAINER_STARTED_EVENT:
e.sendPodLifecycleEvent(&PodLifecycleEvent{
ID: types.UID(event.PodSandboxStatus.Metadata.Uid),
Type: ContainerStarted,
Data: event.ContainerId,
})
case runtimeapi.ContainerEventType_CONTAINER_DELETED_EVENT:
e.sendPodLifecycleEvent(&PodLifecycleEvent{
ID: types.UID(event.PodSandboxStatus.Metadata.Uid),
Type: ContainerDied,
Data: event.ContainerId,
})
e.sendPodLifecycleEvent(&PodLifecycleEvent{
ID: types.UID(event.PodSandboxStatus.Metadata.Uid),
Type: ContainerRemoved,
Data: event.ContainerId,
})
}
}

降级机制

Evented PLEG 具备完善的降级机制,当事件流出现问题时会自动回退到 Generic PLEG:

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
// pkg/kubelet/pleg/evented.go
func (e *EventedPLEG) watchEventsChannel() {
go func() {
numAttempts := 0
for {
if numAttempts >= e.eventedPlegMaxStreamRetries {
if isEventedPLEGInUse() {
// 回退到 Generic PLEG
klog.V(4).InfoS("Fall back to Generic PLEG relisting since Evented PLEG is not working")
e.Stop()
e.genericPleg.Stop()
e.Update(e.relistDuration)
e.genericPleg.Start()
break
}
}

err := e.runtimeService.GetContainerEvents(containerEventsResponseCh)
if err != nil {
numAttempts++
e.Relist()
}
}
}()
}

全局缓存时间更新

Evented PLEG 定期更新缓存的全局时间戳,以防止 Pod Worker 在等待状态更新时死锁:

1
2
3
4
// pkg/kubelet/pleg/evented.go
func (e *EventedPLEG) updateGlobalCache() {
e.cache.UpdateTime(time.Now())
}

5.6.5 Generic PLEG 与 Evented PLEG 对比

特性 Generic PLEG Evented PLEG
实现方式 定期轮询 事件流订阅
响应延迟 受轮询周期影响(默认1秒) 实时响应
资源消耗 定期全量查询 仅处理变化事件
可靠性 高(无外部依赖) 依赖运行时事件接口
回退机制 降级到 Generic PLEG
适用场景 所有环境 支持 CRI 事件流的运行时

性能对比

  • Generic PLEG:每次轮询需要遍历所有 Pod 和容器,在大规模集群中可能产生显著开销
  • Evented PLEG:仅处理变化的事件,大幅降低 CPU 和网络开销,事件响应延迟从秒级降低到毫秒级

5.6.6 事件处理流程

PLEG 生成的事件最终由 Pod Worker 处理,完整的处理流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Container │ │ PLEG │ │ Pod Worker │
│ Runtime │────>│ │────>│ │
│ │ │ 状态比较 │ │ 处理事件 │
│ 容器状态 │ │ 事件生成 │ │ 同步 Pod │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Cache │ │ Status │
│ 状态缓存 │ │ Manager │
└──────────────┘ └──────────────┘
  1. 容器运行时检测到容器状态变化
  2. PLEG通过轮询或事件流获取变化
  3. PLEG比较状态差异,生成事件
  4. Pod Worker接收事件,触发同步
  5. Pod Worker执行同步操作(创建/删除/更新容器)
  6. Status Manager将状态同步到 API Server

5.6.7 设计要点与最佳实践

  1. 事件可靠性

    • Generic PLEG 假设容器不会在一个轮询周期内被创建、终止和垃圾回收
    • 如果发生这种情况,PLEG 会错过事件,因此轮询周期不宜过长
  2. 缓存一致性

    • PLEG 与 Pod Worker 通过缓存进行通信
    • Evented PLEG 使用 CRI 事件中的时间戳来保证缓存时间戳的准确性
  3. 健康监控

    • PLEG 定期更新 PLEGLastSeen 指标,供监控系统检测 PLEG 是否正常工作
    • 可以配置告警:time() - plef_last_seen_seconds > nn
  4. 性能优化

    • 使用 Evented PLEG 可以显著降低事件处理延迟
    • 定期轮询仍然作为降级方案和指标更新的保障
  5. 容器状态追踪

    • PLEG 追踪所有容器和沙箱(Sandbox)的状态变化
    • 对于已退出的容器,保留其状态作为记录(tombstone)

5.6.8 参考代码文件

文件路径 说明
pkg/kubelet/pleg/pleg.go PLEG 接口定义、Generic PLEG 实现
pkg/kubelet/pleg/generic.go Generic PLEG 详细实现(轮询机制)
pkg/kubelet/pleg/evented.go Evented PLEG 实现(事件流机制)
pkg/kubelet/kubelet.go PLEG 在 Kubelet 中的初始化和调用
pkg/kubelet/container/runtime.go 容器运行时接口定义

6. 边界情况与错误处理

Kubelet 在运行过程中会遇到各种异常情况,包括容器运行时故障、网络问题、资源不足等。本章分析 Kubelet 的错误处理机制和恢复策略。

6.1 常见错误场景

6.1.1 容器运行时不可用

当容器运行时(Container Runtime)不可用时,Kubelet 需要优雅地处理这种情况:

错误定义pkg/kubelet/errors.go):

1
2
3
4
5
6
7
8
9
const (
// NetworkNotReadyErrorMsg is used to describe the error that network is not ready
NetworkNotReadyErrorMsg = "network is not ready"
)

var (
// ErrNetworkUnknown indicates the network state is unknown
ErrNetworkUnknown = errors.New("network state unknown")
)

启动等待机制pkg/kubelet/kubelet.go):

1
2
3
4
5
6
7
8
const (
// Max amount of time to wait for the container runtime to come up.
maxWaitForContainerRuntime = 30 * time.Second

// nodeReadyGracePeriod is the period to allow for before fast status update is
// terminated and container runtime not being ready is logged without verbosity guard.
nodeReadyGracePeriod = 120 * time.Second
)

Kubelet 在启动时会等待容器运行时就绪,最长等待 30 秒。如果容器运行时在 120 秒内未就绪,会记录非 verbose 日志警告。

6.1.2 API Server 连接失败

Kubelet 需要与 API Server 保持通信以获取 Pod 规范和上报节点状态。当连接失败时:

节点状态重试机制pkg/kubelet/kubelet_node_status.go):

1
2
3
4
5
6
7
8
9
const (
// nodeStatusUpdateRetry specifies how many times kubelet retries when posting node status failed.
nodeStatusUpdateRetry = 5
)

for i := 0; i < nodeStatusUpdateRetry; i++ {
// 尝试上报节点状态
// ...
}

Kubelet 在上报节点状态失败时会重试 5 次,每次重试之间有时间间隔(由客户端库控制),确保在临时网络故障时不会立即失败。

6.1.3 资源不足

当节点资源(内存、磁盘、CPU)不足时,Kubelet 的 EvictionManager 会触发资源回收:

驱逐监控周期pkg/kubelet/kubelet.go):

1
2
3
4
5
const (
// Period for performing eviction monitoring.
// ensure this is kept in sync with internal cadvisor housekeeping.
evictionMonitoringPeriod = time.Second * 10
)

EvictionManager 每 10 秒检查一次资源使用情况,当达到驱逐阈值时会按优先级顺序驱逐 Pod。

6.1.4 网络问题

网络不可用是常见错误场景,Kubelet 对网络问题有特殊处理:

Pod 同步时的网络错误处理pkg/kubelet/pod_workers.go):

1
2
3
case strings.Contains(syncErr.Error(), NetworkNotReadyErrorMsg):
// Network is not ready; back off for short period of time and retry as network might be ready soon.
p.workQueue.Enqueue(podUID, wait.Jitter(backOffOnTransientErrorPeriod, workerBackOffPeriodJitterFactor))

当 Pod 同步失败且错误信息包含 “network is not ready” 时,Kubelet 会使用较短的后退间隔(1 秒)进行重试,因为网络可能很快就会恢复。

6.2 恢复机制

6.2.1 重试策略

Kubelet 采用多种重试策略来处理不同类型的错误:

基础后退周期pkg/kubelet/kubelet.go):

1
2
3
4
5
6
7
8
9
const (
// MaxContainerBackOff is the max backoff period, exported for the e2e test
MaxContainerBackOff = 300 * time.Second

// backOffPeriod is the period to back off when pod syncing results in an
// error. It is also used as the base period for the exponential backoff
// container restarts and image pulls.
backOffPeriod = time.Second * 10
)

Pod Worker 中的错误处理pkg/kubelet/pod_workers.go):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const (
// jitter factor for backOffPeriod and backOffOnTransientErrorPeriod
workerBackOffPeriodJitterFactor = 0.5

// backOffOnTransientErrorPeriod is the duration to back off when there is a transient error.
backOffOnTransientErrorPeriod = time.Second
)

// 同步失败时的重试逻辑
switch {
case strings.Contains(syncErr.Error(), NetworkNotReadyErrorMsg):
// 网络未就绪:短时间后退重试
p.workQueue.Enqueue(podUID, wait.Jitter(backOffOnTransientErrorPeriod, workerBackOffPeriodJitterFactor))
default:
// 其他错误:标准后退重试
p.workQueue.Enqueue(podUID, wait.Jitter(p.backOffPeriod, workerBackOffPeriodJitterFactor))
}
错误类型 后退周期 最大重试时间
网络未就绪 1 秒 取决于网络恢复
容器运行时错误 10 秒 5 分钟
其他同步错误 10 秒 5 分钟

6.2.2 指数退避

Kubelet 使用指数退避算法来避免频繁重试:

1
2
3
4
5
6
7
8
// wait.Jitter 函数实现
func Jitter(duration time.Duration, maxFactor float64) time.Duration {
if maxFactor <= 0 {
return duration
}
wait := int64(float64(duration.Nanoseconds()) * maxFactor * rand.Float64())
return time.Duration(wait) + duration
}

通过添加随机抖动(jitter),Kubelet 避免了多个 Pod 同时重试导致的”惊群效应”(thundering herd)。

6.2.3 状态恢复

Kubelet 具备完整的状态恢复能力,确保在故障恢复后能够继续正常工作:

Housekeeping 周期清理pkg/kubelet/kubelet.go):

1
2
3
4
const (
// Period for performing global cleanup tasks.
housekeepingPeriod = time.Second * 2
)

Housekeeping 每 2 秒执行一次,负责:

  • 清理已终止的 Pod
  • 清理孤儿容器
  • 同步期望状态与实际状态

PLEG 状态同步pkg/kubelet/pleg/generic.go):

1
2
3
4
5
6
7
8
const (
// Generic PLEG relies on relisting for discovering container events.
// A longer period means that kubelet will take longer to detect container
// changes and to update pod status. On the other hand, a shorter period
// will cause more frequent relisting.
genericPlegRelistPeriod = time.Second * 1
genericPlegRelistThreshold = time.Minute * 3
)

PLEG 每秒轮询一次容器状态,如果超过 3 分钟未收到任何事件,会触发告警。这确保了即使在事件丢失的情况下,状态也能在合理时间内被同步。

6.3 错误处理最佳实践

  1. 区分临时错误和永久错误

    • 临时错误(如网络抖动)使用短后退间隔
    • 永久错误(如配置错误)记录日志但不持续重试
  2. 资源限制保护

    • 使用驱逐阈值保护节点资源
    • 优先驱逐低优先级 Pod
  3. 监控和告警

    • 监控 PLEGLastSeen 指标
    • 监控节点状态更新失败次数
    • 监控容器重启次数
  4. 优雅关闭

    • Kubelet 关闭时先停止接收新 Pod
    • 等待现有 Pod 优雅终止后再退出

6.4 参考代码文件

文件路径 说明
pkg/kubelet/errors.go 错误定义常量
pkg/kubelet/kubelet.go 超时和重试常量定义
pkg/kubelet/pod_workers.go Pod 同步重试逻辑
pkg/kubelet/kubelet_node_status.go 节点状态上报重试
pkg/kubelet/eviction/eviction_manager.go 资源驱逐管理
pkg/kubelet/pleg/generic.go PLEG 轮询机制

7. 性能优化

Kubelet 作为节点上的核心组件,其性能直接影响 Pod 的启动速度、资源利用率和系统稳定性。本章分析关键性能指标、性能瓶颈以及优化策略。

7.1 关键性能指标

Kubelet 暴露了丰富的性能指标,可通过 Prometheus 监控进行采集和分析。以下是核心性能指标:

7.1.1 Pod 启动延迟

Pod 启动延迟是衡量 Kubelet 性能的最重要指标之一。Kubelet 暴露了多个相关指标:

指标名称 描述 监控重要性
kubelet_pod_start_duration_seconds Pod 从被 Kubelet 首次发现到容器启动的总时间
kubelet_pod_start_sli_duration_seconds Pod 启动 SLI(排除镜像拉取时间)
kubelet_pod_start_total_duration_seconds Pod 完整启动时间(含镜像拉取)
kubelet_pod_worker_start_duration_seconds Kubelet 发现 Pod 到启动 Worker 的时间
kubelet_pod_worker_duration_seconds 单个 Pod 同步操作的耗时,按操作类型分类

SLO 参考

  • 99% Pod 应在 30 秒内完成启动(排除大镜像拉取)
  • 99% Pod 应在 15 分钟内完成完整启动(含镜像拉取)

7.1.2 状态同步延迟

Pod 状态同步延迟影响 Kubernetes 控制平面对 Pod 状态的感知:

指标名称 描述
kubelet_pod_status_sync_duration_seconds Pod 状态从生成到成功同步到 API Server 的耗时
kubelet_pleg_relist_duration_seconds PLEG 重新列出容器状态所花费的时间
kubelet_pleg_relist_interval_seconds PLEG 两次 relist 操作之间的时间间隔
kubelet_pleg_last_seen_seconds PLEG 最后一次活动的 Unix 时间戳

7.1.3 运行时操作延迟

容器运行时操作的性能直接影响 Pod 生命周期管理:

指标名称 描述
kubelet_runtime_operations_duration_seconds 运行时操作耗时,按操作类型分类
kubelet_runtime_operations_total 运行时操作总数,按类型统计
kubelet_runtime_operations_errors_total 运行时操作错误数
kubelet_run_podsandbox_duration_seconds Pod Sandbox 创建耗时

7.2 核心性能常量

Kubelet 代码中定义了多个关键的时间常量,直接影响性能行为(定义于 pkg/kubelet/kubelet.go):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 清理任务周期
housekeepingPeriod = time.Second * 2 // 全局清理任务执行周期
housekeepingWarningDuration = time.Second * 1 // 清理任务耗时警告阈值

// 运行时缓存刷新周期
runtimeCacheRefreshPeriod = housekeepingPeriod + housekeepingWarningDuration // 3秒

// PLEG 相关周期
genericPlegRelistPeriod = time.Second * 1 // Generic PLEG 轮询周期(默认1秒)
genericPlegRelistThreshold = time.Minute * 3 // Generic PLEG 阈值(3分钟)
eventedPlegRelistPeriod = time.Second * 300 // Evented PLEG 轮询周期(5分钟)
eventedPlegRelistThreshold = time.Minute * 10 // Evented PLEG 阈值(10分钟)

// 驱逐监控周期
evictionMonitoringPeriod = time.Second * 10 // 资源驱逐检查周期

// 垃圾回收周期
ContainerGCPeriod = time.Minute // 容器垃圾回收周期
ImageGCPeriod = 5 * time.Minute // 镜像垃圾回收周期

关键性能影响

  • housekeepingPeriod(2秒)决定了 Pod 状态同步、探针执行等关键任务的频率
  • genericPlegRelistPeriod(1秒)影响容器状态变化的检测延迟
  • eventedPlegRelistPeriod(300秒)使用事件驱动模式时大幅降低 CPU 占用

7.3 性能优化策略

7.3.1 并行处理优化

Kubelet 内部采用多种并行处理机制提升吞吐量:

  1. Pod Worker 池

    • 每个 Pod 分配独立的 Worker 进行处理
    • Worker 之间相互独立,可并行处理不同 Pod
    • 通过 --max-pods 配置控制节点最大 Pod 数量,避免过载
  2. 容器操作并行化

    • 同一 Pod 内的多个容器可并行创建
    • 镜像拉取支持并行下载
    • Volume 挂载操作可与其他容器操作并行进行
  3. 批量状态更新

    • 批量同步多个 Pod 状态到 API Server
    • 减少 API Server 请求次数和网络开销

代码位置pkg/kubelet/pod_workers.go

7.3.2 缓存策略优化

Kubelet 使用多层缓存减少重复计算和 API Server 交互:

  1. Pod 缓存

    • 本地维护 Pod 状态缓存
    • 减少对 API Server 的 List 请求
    • 通过 Informer 机制保持缓存与 API Server 一致
  2. 运行时状态缓存

    • runtimeCacheRefreshPeriod = 3秒
    • 缓存容器运行时状态,减少与容器运行时交互
    • Housekeeping 任务显式刷新缓存
  3. 镜像缓存

    • 预拉取常用镜像
    • 本地镜像仓库加速拉取

代码位置pkg/kubelet/pod/pod_manager.gopkg/kubelet/container/runtime.go

7.3.3 批量操作优化

  1. Pod 批量处理

    • Watch 事件触发时,批量处理多个 Pod 变化
    • 减少同步次数和锁竞争
  2. 状态批量上报

    • 节点状态信息批量上报
    • 使用 Node Lease 机制降低心跳频率
  3. 运行时操作批量化

    • 合并多个容器操作请求
    • 减少与容器运行时的 RPC 调用次数

7.3.4 Evented PLEG 优化

Evented PLEG 是 Kubernetes 1.22 引入的重要优化,通过事件流机制替代传统轮询:

对比 Generic PLEG

特性 Generic PLEG Evented PLEG
轮询周期 1秒 300秒(作为后备)
CPU 占用 较高 极低
事件延迟 ~1秒 实时
可靠性 依赖 CRI 流接口

启用方式

  • 需要容器运行时支持事件流接口(containerd 1.6+、CRI-O 等)
  • 通过 Kubelet 配置 --evented-pleg-enabled=true 启用
  • 自动降级机制:当事件流断开时回退到 Generic PLEG

监控指标

  • kubelet_evented_pleg_connection_success_count:成功建立事件流连接次数
  • kubelet_evented_pleg_connection_error_count:事件流连接错误次数
  • kubelet_evented_pleg_connection_latency_seconds:事件流连接延迟

7.4 性能调优参数

以下是影响 Kubelet 性能的关键配置参数:

7.4.1 Pod 调度相关

参数 默认值 说明 调优建议
--max-pods 110 节点最大 Pod 数量 根据节点资源调整
--max-container-pods - 最大容器数量(已废弃) 使用 max-pods
--pod-per-core - 每个 CPU 核心的 Pod 限制 超大规模集群优化

7.4.2 资源管理相关

参数 默认值 说明 调优建议
--kube-reserved - 为系统预留的资源 建议设置以保障系统稳定性
--system-reserved - 为系统进程预留的资源 建议设置以避免资源耗尽
--eviction-hard memory.available<100Mi 硬驱逐阈值 根据工作负载调整

7.4.3 运行时相关

参数 默认值 说明 调优建议
--runtime-request-timeout 2m 运行时请求超时 网络不稳定时增大
--image-pull-progress-deadline 1m 镜像拉取超时 大镜像时增大
--image-gc-high-threshold 85% 镜像垃圾回收阈值 根据磁盘空间调整
--image-gc-low-threshold 80% 镜像垃圾回收下限 -

7.5 性能问题诊断

7.5.1 高延迟问题排查

当发现 Pod 启动延迟较高时,可按以下步骤排查:

  1. 检查 PLEG 状态

    1
    2
    3
    4
    5
    # 查看 PLEG relist 延迟
    kubectl get --raw "/api/v1/nodes/{node}/proxy/metrics" | grep pleg_relist_duration

    # 查看 PLEG 最后活动时间
    kubectl get --raw "/api/v1/nodes/{node}/proxy/metrics" | grep pleg_last_seen
  2. 检查 Pod Worker 队列

    • 查看是否有大量 Pod 等待处理
    • 检查 Pod Worker 耗时分布
  3. 检查运行时操作

    • 查看镜像拉取耗时
    • 查看容器创建/启动耗时
  4. 检查资源压力

    • 查看节点 CPU、内存使用率
    • 检查是否存在资源争用

7.5.2 高 CPU 占用排查

当 Kubelet CPU 占用较高时:

  1. 检查 PLEG 轮询频率

    • Generic PLEG 默认 1 秒轮询
    • 确认是否启用了 Evented PLEG
  2. 检查 Housekeeping 任务

    • 每 2 秒执行一次清理任务
    • 确认是否有大量 Pod 状态变化
  3. 检查探针执行

    • 大量探针会增加 CPU 负担
    • 优化探针配置减少不必要的检查
  4. 检查镜像 GC

    • ImageGCPeriod = 5 分钟
    • 确认镜像缓存策略是否合理

7.5.3 内存泄漏排查

可能导致内存泄漏的组件:

  1. Pod 缓存

    • 大量历史 Pod 信息未清理
    • 检查 pod_workers.go 中的缓存策略
  2. 容器状态缓存

    • 退出的容器状态未及时清理
    • 检查 PLEG 的 tombstone 机制
  3. 运行时缓存

    • 容器运行时状态缓存未刷新
    • 检查 runtimeCacheRefreshPeriod 设置

7.6 参考代码文件

文件路径 说明
pkg/kubelet/kubelet.go 性能相关常量定义
pkg/kubelet/metrics/metrics.go 性能指标定义
pkg/kubelet/pod_workers.go Pod Worker 并行处理
pkg/kubelet/pleg/generic.go Generic PLEG 实现
pkg/kubelet/pleg/evented.go Evented PLEG 实现
pkg/kubelet/pod/pod_manager.go Pod 缓存管理
pkg/kubelet/container/runtime.go 运行时缓存

9. 面试题与详细解答

问题 1:Kubelet 的启动流程分为哪几个阶段?每个阶段主要做什么?

回答要点

Kubelet 启动五个阶段

1
1. 命令行解析 → 2. 依赖初始化 → 3. 运行时服务初始化 → 4. Kubelet 实例创建 → 5. 服务启动

详细说明

阶段 主要工作 关键代码位置
命令行解析 解析参数、加载配置文件 cmd/kubelet/app/server.go
依赖初始化 获取主机名、构建 EventRecorder RunKubelet()
运行时服务初始化 连接 CRI Runtime 和 Image Service PreInitRuntimeService()
Kubelet 实例创建 初始化所有 Manager(Pod/Status/Probe/Volume/Eviction/PLEG) NewMainKubelet()
服务启动 启动各 Manager 和主循环 syncLoop k.Run()

关键启动顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pkg/kubelet/kubelet.go
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// 1. 启动 VolumeManager
kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)

// 2. 同步节点状态
kl.syncNodeStatus()

// 3. 启动 NodeLease Controller
kl.nodeLeaseController.Run(wait.NeverStop)

// 4. 更新运行时状态
kl.updateRuntimeUp()

// 5. 启动 StatusManager
kl.statusManager.Start()

// 6. 启动 PLEG
kl.pleg.Start()

// 7. 启动主循环
kl.syncLoop(updates, kl)
}

为什么这个顺序很重要

  1. VolumeManager 先启动:确保卷可用
  2. RuntimeUp 先检查:确保容器运行时可用
  3. PLEG 在 syncLoop 前:确保能检测容器变化
  4. syncLoop 最后:开始处理 Pod 事件

问题 2:Kubelet 的 syncLoop 是如何工作的?它是如何保证 Pod 状态与期望一致的?

回答要点

syncLoop 核心设计

syncLoop 是 Kubelet 的主循环,采用事件驱动 + 定期同步的方式确保 Pod 状态与期望一致。

事件来源

1
2
3
4
5
6
7
// pkg/kubelet/kubelet.go
func (kl *Kubelet) syncLoop(...) {
// 1. 配置源(File/HTTP/API)
// 2. PLEG 事件(容器状态变化)
// 3. 定时器(定期同步)
// 4. 手动触发(健康检查恢复等)
}

事件处理流程

1
事件入队 → Pod Worker 分发 → syncPod/syncTerminatingPod/syncTerminatedPod

syncPod 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (kl *Kubelet) syncPod(...) {
// 1. 生成 Pod 状态
apiPodStatus := kl.generateAPIPodStatus(pod, podStatus)

// 2. 检查 Pod 是否可运行
if !kl.canRunPod(pod) {
return err
}

// 3. 准备资源
// - 注册 Secret/ConfigMap
// - 创建 Pod Cgroups
// - 创建数据目录
// - 等待卷挂载

// 4. 调用 CRI 同步容器
result := kl.containerRuntime.SyncPod(pod, podStatus, ...)
}

状态一致性保障

  1. 定期同步:Housekeeping 每 2 秒执行一次
  2. PLEG 事件:容器状态变化立即触发同步
  3. 幂等操作:syncPod 可重复执行
  4. 状态对比:只在实际状态变化时更新

关键代码位置

  • 主循环:pkg/kubelet/kubelet.go:syncLoop()
  • Pod Worker:pkg/kubelet/pod_workers.go
  • syncPod:pkg/kubelet/kubelet.go:syncPod()

问题 3:PLEG(Pod Lifecycle Event Generator)的作用是什么?Generic PLEG 和 Evented PLEG 有什么区别?

回答要点

PLEG 的作用

  1. 检测容器状态变化:通过轮询或事件流获取容器状态
  2. 生成生命周期事件:ContainerStarted/ContainerDied/ContainerRemoved
  3. 触发 Pod 同步:将事件发送给 Pod Worker 处理

Generic PLEG vs Evented PLEG

特性 Generic PLEG Evented PLEG
实现方式 定期轮询(1秒) 事件流订阅
CPU 占用 较高 极低
事件延迟 ~1秒 实时(毫秒级)
可靠性 依赖 CRI 事件接口
回退机制 自动回退到 Generic

Generic PLEG 工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// pkg/kubelet/pleg/generic.go
func (g *GenericPLEG) Relist() {
// 1. 获取所有 Pod
podList := g.runtime.GetPods(true)

// 2. 对比新旧状态
for pid := range g.podRecords {
oldPod := g.podRecords.getOld(pid)
newPod := g.podRecords.getCurrent(pid)

// 3. 生成事件
events := computeEvents(oldPod, newPod)
}

// 4. 发送事件
for _, event := range events {
g.eventChannel <- event
}
}

Evented PLEG 工作原理

1
2
3
4
5
6
7
8
9
10
// pkg/kubelet/pleg/evented.go
func (e *EventedPLEG) watchEventsChannel() {
// 订阅 CRI 事件流
e.runtimeService.GetContainerEvents(eventCh)

// 处理事件
for event := range eventCh {
e.processCRIEvent(event)
}
}

启用 Evented PLEG

1
2
3
# kubelet 配置
featureGates:
EventedPLEG: true

降级机制

1
2
3
4
5
Evented PLEG 事件流断开

重试连接(最多 N 次)

仍然失败 → 降级到 Generic PLEG

问题 4:Kubelet 如何处理 Pod 的优雅终止(Graceful Termination)?完整流程是什么?

回答要点

优雅终止完整流程

1
2
3
4
5
6
7
8
9
10
11
1. API Server 收到 DELETE 请求
2. Pod 标记为 deletionTimestamp
3. Kubelet 收到 Pod 更新
4. 进入 SyncTerminatingPod 状态
5. 停止探针
6. 发送 SIGTERM
7. 等待 grace period
8. 强制终止(SIGKILL)
9. 进入 SyncTerminatedPod 状态
10. 卸载卷、清理资源
11. 从 API Server 删除

三个终止状态

1
2
3
4
5
6
// pkg/kubelet/pod_workers.go
const (
SyncPod // Pod 正常运行
TerminatingPod // Pod 正在终止(容器可能还在运行)
TerminatedPod // Pod 已终止(所有容器已停止)
)

SyncTerminatingPod 处理

1
2
3
4
5
6
7
8
9
10
func (kl *Kubelet) SyncTerminatingPod(...) {
// 1. 生成最终状态
apiPodStatus := kl.generateAPIPodStatus(pod, podStatus, true)

// 2. 停止探针
kl.probeManager.StopLivenessAndStartup(pod)

// 3. 杀死所有容器
kl.killPod(ctx, pod, runningPod, gracePeriod)
}

优雅终止时间计算

1
2
3
4
5
effectiveGracePeriod = min(
deletionGracePeriodSeconds, // DELETE 请求中指定
pod.spec.terminationGracePeriodSeconds, // Pod 配置
maxPodGracePeriodSeconds // Kubelet 配置上限
)

最佳实践

1
2
3
4
5
6
7
8
9
# Pod 配置
spec:
terminationGracePeriodSeconds: 60 # 优雅终止时间
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "cleanup.sh"] # 预停止钩子

preStop 钩子执行时机

1
2
3
4
5
6
1. 收到终止请求
2. 执行 preStop 钩子
3. 等待钩子完成(计入 grace period)
4. 发送 SIGTERM
5. 等待容器退出
6. 超时后发送 SIGKILL

问题 5:Kubelet 的 ProbeManager 如何管理三种探针?它们的初始值和失败行为有什么区别?

回答要点

三种探针对比

探针类型 初始值 失败行为 用途
Startup Unknown 容器重启 检测启动完成
Liveness Success 容器重启 检测存活状态
Readiness Failure 从 Service 移除 检测就绪状态

初始值差异的原因

  1. Startup 初始 Unknown:启动完成前不知道状态
  2. Liveness 初始 Success:假设容器启动时是健康的,避免立即重启
  3. Readiness 初始 Failure:假设容器启动时未就绪,避免过早接收流量

探针执行流程

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
// pkg/kubelet/prober/worker.go
func (w *worker) doProbe() {
// 1. 检查容器状态
if !w.containerID.IsValid() {
return
}

// 2. 检查 Startup Probe 状态
if w.probeType != startup && !w.startupOK {
// Startup 未成功,跳过 Liveness 和 Readiness
return
}

// 3. 执行探针
result, err := w.probeManager.prober.probe(ctx, w.probeType, w.pod, ...)

// 4. 判断是否更新结果
if result == w.lastResult {
w.resultRun++
} else {
w.resultRun = 1
}

if w.resultRun >= w.spec.failureThreshold {
w.resultsManager.Set(w.containerID, result, w.pod)
}
}

探针类型影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Startup Probe - 保护慢启动应用
startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 最多等待 300 秒

# Liveness Probe - 检测死锁
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3 # 连续 3 次失败后重启

# Readiness Probe - 控制流量
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 1 # 1 次失败就从 Service 移除

Startup Probe 屏蔽效果

1
2
3
4
5
6
7
Startup Probe 运行中

Liveness/Readiness 探针暂停

Startup 成功

Liveness/Readiness 开始执行

问题 6:Kubelet 的 StatusManager 是如何将 Pod 状态同步到 API Server 的?如何保证状态一致性?

回答要点

StatusManager 核心功能

  1. 缓存 Pod 状态:维护 podStatuses Map
  2. 去重更新:避免重复发送相同状态
  3. 版本控制:防止旧状态覆盖新状态
  4. 定期同步:每 10 秒全量同步一次

状态同步流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pkg/kubelet/status/status_manager.go
func (m *manager) Start() {
// 定期同步
go wait.Forever(func() {
m.syncBatch()
}, 10*time.Second)

// 处理状态更新请求
for {
select {
case <-m.podStatusChannel:
m.syncBatch()
}
}
}

状态去重机制

1
2
3
4
5
6
7
8
9
10
11
12
func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool {
// 只比较 kubelet 管理的 Conditions
for _, c := range status.Conditions {
if kubetypes.PodConditionByKubelet(c.Type) {
_, oc := podutil.GetPodCondition(oldCopy, c.Type)
if oc == nil || oc.Status != c.Status {
return false
}
}
}
return apiequality.Semantic.DeepEqual(oldCopy, status)
}

版本控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (m *manager) updateStatusInternal(pod *v1.Pod, status v1.PodStatus, forceUpdate bool) {
// 检查版本号
if oldStatus.version > newVersion && !forceUpdate {
return // 旧版本,忽略
}

// 更新缓存
m.podStatuses[pod.UID] = versionedStatus{
version: newVersion,
status: status,
}

// 触发同步
select {
case m.podStatusChannel <- struct{}{}:
default: // 已有待处理的更新
}
}

一致性保障措施

  1. 线程安全:使用 sync.RWMutex 保护缓存
  2. 版本递增:每次更新版本号 +1
  3. 定期同步:确保最终一致性
  4. 重试机制:API Server 更新失败时重试

关键代码位置

  • StatusManager:pkg/kubelet/status/status_manager.go
  • 状态生成:pkg/kubelet/kubelet.go:generateAPIPodStatus()

问题 7:如何调优 Kubelet 性能以支持高密度 Pod 部署?

回答要点

性能调优维度

  1. 并发控制
  2. 缓存优化
  3. PLEG 选择
  4. 资源限制

关键配置参数

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
# kubelet 配置
# 1. Pod 数量限制
maxPods: 250 # 节点最大 Pod 数(默认 110)
podsPerCore: 0 # 每 CPU 核心限制(0=不限制)

# 2. 镜像拉取并发
registryPullQPS: 10 # 镜像拉取 QPS(默认 5)
registryBurst: 20 # 镜像拉取突发(默认 10)
serializeImagePulls: false # 并行拉取镜像

# 3. 运行时配置
runtimeRequestTimeout: "5m" # 运行时请求超时
imagePullProgressDeadline: "5m" # 镜像拉取超时

# 4. Evented PLEG
featureGates:
EventedPLEG: true # 启用事件驱动 PLEG

# 5. 状态同步
syncFrequency: "1m" # 同步频率(默认 1m)

# 6. 资源预留
kubeReserved:
cpu: "500m"
memory: "2Gi"
systemReserved:
cpu: "500m"
memory: "1Gi"

高密度部署最佳实践

  1. 启用 Evented PLEG

    • 减少 CPU 占用
    • 降低事件延迟
  2. 优化镜像拉取

    1
    2
    3
    registryPullQPS: 20
    registryBurst: 50
    serializeImagePulls: false
  3. 增加资源预留

    1
    2
    3
    kubeReserved:
    cpu: "1"
    memory: "4Gi" # 高密度时需要更多
  4. 调整驱逐阈值

    1
    2
    evictionHard:
    memory.available: "1Gi" # 留更多缓冲

性能监控指标

1
2
3
4
5
6
7
8
# Pod 启动延迟
kubelet_pod_start_duration_seconds

# PLEG 延迟
kubelet_pleg_relist_duration_seconds

# 运行时操作
kubelet_runtime_operations_duration_seconds

调优验证

1
2
3
4
5
# 压测:快速创建大量 Pod
kubectl create deployment test --image=nginx --replicas=100

# 监控指标
watch 'kubectl get --raw "/api/v1/nodes/node-1/proxy/metrics" | grep pod_start'

性能问题排查

问题 可能原因 解决方法
Pod 启动慢 镜像拉取慢 配置镜像预热、并行拉取
CPU 占用高 PLEG 轮询频繁 启用 Evented PLEG
内存占用高 Pod 数量多 增加资源预留
状态同步慢 API Server 压力 检查网络和 API Server

8. 最佳实践

本章提供 Kubelet 配置、调优和故障排查的最佳实践建议,帮助运维人员构建稳定、高性能的 Kubernetes 集群。

8.1 配置建议

正确的 Kubelet 配置是确保节点稳定运行的基础。以下是生产环境的推荐配置。

8.1.1 资源预留

生产环境中必须为系统组件和 Kubernetes 本身预留资源,避免因资源不足导致节点不稳定。

核心配置参数

参数 说明 推荐值
--kube-reserved 为 Kubernetes 系统组件预留的资源 cpu=100m,memory=1Gi,ephemeral-storage=1Gi
--system-reserved 为系统进程预留的资源 cpu=100m,memory=512Mi,ephemeral-storage=1Gi
--allocatable 节点可分配资源(自动计算) kube-reserved + system-reserved

配置示例

1
2
3
4
5
6
7
8
9
# kubelet-config.yaml
kubeReserved:
cpu: "100m"
memory: "1Gi"
ephemeral-storage: "1Gi"
systemReserved:
cpu: "100m"
memory: "512Mi"
ephemeral-storage: "1Gi"

资源预留计算原则

  1. CPU 预留

    • 小型集群(< 50 节点):每节点预留 0.1-0.2 核心
    • 大型集群(> 50 节点):每节点预留 0.2-0.5 核心
  2. 内存预留

    • 基础系统:512Mi - 1Gi
    • Kubelet 自身:1Gi(处理大量 Pod 时)
    • 容器运行时:2-4Gi(containerd、CRI-O 等)
  3. 存储预留

    • 操作系统日志:5-10Gi
    • 镜像存储:20-50Gi
    • 临时存储:10-20Gi

8.1.2 驱逐阈值配置

驱逐(Eviction)是 Kubelet 保护节点的关键机制,当资源不足时主动回收资源。

硬驱逐 vs 软驱逐

类型 触发条件 行为
硬驱逐 资源低于硬阈值 立即终止 Pod,无宽限期
软驱逐 资源低于软阈值持续一段时间 有宽限期,可等待 Pod 优雅终止

推荐驱逐配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 硬驱逐阈值 - 立即触发
evictionHard:
memory.available: "500Mi"
nodefs.available: "10%"
imagefs.available: "15%"
nodefs.inodesFree: "5%"

# 软驱逐阈值 - 可恢复
evictionSoft:
memory.available: "1Gi"
nodefs.available: "15%"
imagefs.available: "20%"

# 软驱逐宽限期
evictionSoftGracePeriod:
memory.available: "1m"
nodefs.available: "1m"
imagefs.available: "1m"

# 驱逐压力缓和期(驱逐后等待时间)
evictionPressureTransitionPeriod: "5m"

# 最小驱逐回收资源量
evictionMinimumReclaim:
memory.available: "0Mi"
nodefs.available: "500Mi"
imagefs.available: "2Gi"

驱逐优先级

Kubelet 按以下顺序驱逐 Pod:

  1. BestEffort Pod(无资源请求)
  2. Burstable Pod(低于请求量的资源请求)
  3. Guaranteed Pod(完全指定资源请求)

最佳实践

  • 设置 memory.available 至少为 500Mi-1Gi
  • 监控驱逐事件频率,如果频繁驱逐考虑扩容
  • 使用 PriorityClass 控制关键 Pod 的驱逐顺序

8.1.3 日志配置

合理的日志配置对于问题排查和性能优化至关重要。

Kubelet 日志级别

级别 数值 使用场景
0 ERROR 生产环境最小日志
1 WARNING 生产环境推荐
2 INFO 一般问题排查
4 DEBUG 深度调试
6 TRACE 详细跟踪
1
2
3
4
5
6
# 生产环境推荐配置
--v=2
--vmodule*=3

# 问题排查时临时提高
--v=4

日志输出格式

1
2
# 使用 JSON 格式便于日志收集
logFormat: "json"

容器日志配置

1
2
3
4
5
6
7
# 容器日志轮转
containerLogMaxSize: "10Mi"
containerLogMaxFiles: 5

# 日志驱动配置
logging:
format: text # text 或 json

日志收集建议

  1. 将 Kubelet 日志输出到标准输出,由容器运行时管理
  2. 配置日志轮转,避免磁盘空间耗尽
  3. 使用集中式日志收集系统(如 Loki、ELK)

8.2 调优指南

针对不同工作负载场景,可以通过调整参数优化 Kubelet 性能。

8.2.1 并发参数

Pod 并发控制

参数 默认值 说明 调整建议
--max-pods 110 节点最大 Pod 数量 根据节点规格调整,高密度场景可增大
--pod-per-core - 每 CPU 核心最大 Pod 超大规模集群使用
--pods-per-core - 每核心 Pod 限制 替代 pod-per-core
1
2
3
# 高密度部署示例
--max-pods=250
--pods-per-core=0 # 禁用按核心限制

Pod Worker 配置

参数 默认值 说明
--num-workers - Pod Worker 数量,默认自动计算
--serialize-image-pulls true 串行拉取镜像,建议保持

并发优化策略

  1. Pod 启动并发:高频率 Pod 创建场景可提高并发度
  2. 镜像拉取:配置私有镜像仓库加速
  3. 卷挂载:使用 CSI 驱动优化

8.2.2 缓存大小

运行时缓存

1
2
# 运行时状态缓存刷新周期
runtimeCacheRefreshPeriod: "10s" # 默认 3s,可增大减少 CPU

镜像缓存配置

1
2
3
4
5
# 镜像垃圾回收阈值
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
# 最小回收镜像大小
imageGCMinimumDeadAge: "2h"

内存优化

  • --low-diskspace-threshold-mb:低磁盘空间阈值,默认 256MB
  • 增大缓存可减少与容器运行时和 API Server 的交互,但会增加内存使用

8.2.3 超时设置

运行时超时

参数 默认值 说明 调整建议
--runtime-request-timeout 2m 运行时请求超时 网络不稳定时增大到 5m
--image-pull-progress-deadline 1m 镜像拉取超时 大镜像时增大到 5m
--default-timeout - 默认容器操作超时 -
1
2
3
# 大镜像场景
--image-pull-progress-deadline=5m
--runtime-request-timeout=5m

健康检查超时

1
2
3
4
5
6
7
8
9
10
11
12
# 健康检查配置
readinessProbe:
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3

livenessProbe:
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3
initialDelaySeconds: 10

优雅终止

1
2
3
4
5
# Pod 优雅终止超时
terminationGracePeriodSeconds: 30 # 默认,可根据应用调整

# 容器优雅终止
containerDelteteTimeout: 30s

8.3 故障排查指南

本节提供常见 Kubelet 问题的排查方法和调试技巧。

8.3.1 日志分析

关键日志关键词

关键词 含义 排查方向
Failed to pull image 镜像拉取失败 检查镜像地址、网络、凭证
network is not ready 网络未就绪 检查 CNI 插件状态
Eviction manager: attempting to reclaim 触发驱逐 检查节点资源
PLEG is not healthy PLEG 不健康 检查容器运行时
Failed to create pod sandbox 沙箱创建失败 检查 Runtime 配置

日志查看方法

1
2
3
4
5
6
7
8
9
10
11
# 查看 Kubelet 日志(systemd)
journalctl -u kubelet -f

# 查看最近 100 行日志
journalctl -u kubelet -n 100

# 按关键字过滤
journalctl -u kubelet | grep "Failed to"

# 查看特定级别日志
journalctl -u kubelet -p err

日志级别调试

1
2
3
4
5
# 临时提高日志级别
kubectl exec -it {kubelet-pod} -c kubelet -- kill -SIGUSR1 1

# 或使用 curl
curl -X PUT http://localhost:10250/logs/kubelet --data-urlencode "v=6"

8.3.2 状态检查

节点状态检查

1
2
3
4
5
6
7
8
# 查看节点状态
kubectl get nodes {node-name} -o wide

# 查看节点详情
kubectl describe node {node-name}

# 检查条件状态
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .status.conditions[*]}{.type}{": "}{.status}{"\n"}{end}{"\n"}{end}'

Kubelet 端点检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 健康检查
kubectl get --raw "/healthz"

# 节点代理健康
kubectl get --raw "/api/v1/nodes/{node}/proxy/healthz"

# 获取 Kubelet 指标
kubectl get --raw "/api/v1/nodes/{node}/proxy/metrics"

# 获取运行时状态
kubectl get --raw "/api/v1/nodes/{node}/proxy/stats/summary"

# 获取 Pod 状态
kubectl get --raw "/api/v1/nodes/{node}/proxy/pods"

关键指标检查

1
2
3
4
5
6
7
8
# PLEG 健康状态
curl -s localhost:10250/metrics | grep pleg

# Pod 启动延迟
curl -s localhost:10250/metrics | grep pod_start

# 容器运行时操作
curl -s localhost:10250/metrics | grep runtime_operations

8.3.3 调试技巧

Pod 调试流程

  1. 确认 Pod 状态

    1
    2
    kubectl get pod {pod-name} -o wide
    kubectl describe pod {pod-name}
  2. 查看 Pod 事件

    1
    kubectl get events --field-selector involvedObject.name={pod-name}
  3. 检查容器日志

    1
    2
    kubectl logs {pod-name} -c {container}
    kubectl logs {pod-name} -c {container} --previous # 之前的容器
  4. 进入容器调试

    1
    kubectl exec -it {pod-name} -c {container} -- /bin/sh

Kubelet 问题诊断

  1. Kubelet 未启动

    1
    2
    3
    4
    5
    6
    7
    8
    # 检查 Kubelet 服务状态
    systemctl status kubelet

    # 检查启动日志
    journalctl -u kubelet -xe

    # 检查配置文件
    kubelet --config=/etc/kubernetes/kubelet.conf --validate
  2. 节点 NotReady

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 检查 Kubelet 日志
    journalctl -u kubelet -f | grep -i error

    # 检查容器运行时
    crictl info
    crictl pods

    # 检查 CNI 插件
    ip link
    cat /etc/cni/net.d/*
  3. Pod 无法启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 查看 Kubelet 中的 Pod 状态
    curl -s localhost:10250/pods | jq

    # 检查镜像是否可拉取
    crictl images
    crictl pull {image}

    # 检查存储空间
    df -h
    docker system df
  4. 驱逐频繁发生

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 查看驱逐事件
    kubectl get events --field-selector reason=Eviction

    # 检查节点资源
    kubectl top nodes
    kubectl describe nodes | grep -A 10 "Allocated resources"

    # 检查磁盘使用
    df -h /var/lib/containerd
    df -h /var/lib/kubelet

调试工具

  1. crictl - 容器运行时调试:

    1
    2
    3
    4
    crictl info           # 查看运行时信息
    crictl ps -a # 查看所有容器
    crictl logs {id} # 查看容器日志
    crictl inspect {id} # 查看容器详情
  2. cAdvisor - 资源监控:

    1
    curl localhost:4194/metrics
  3. Kubelet 性能分析

    1
    2
    3
    4
    5
    # 启用 pprof
    curl -s localhost:10250/debug/pprof/

    # 获取堆内存分析
    curl -s localhost:10250/debug/pprof/heap > heap.prof

8.4 生产环境配置示例

以下是生产环境推荐的完整 Kubelet 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# /etc/kubernetes/kubelet-config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

# 基础配置
address: "0.0.0.0"
port: 10250
readOnlyPort: 10255
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: "2m"
enabled: true
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: "5m"
cacheUnauthorizedTTL: "30s"

# 资源管理
maxPods: 110
podsPerCore: 0
registryPullQPS: 5
registryBurst: 10
serializeImagePulls: true

# 资源预留
kubeReserved:
cpu: "100m"
memory: "1Gi"
ephemeral-storage: "1Gi"
systemReserved:
cpu: "100m"
memory: "512Mi"
ephemeral-storage: "1Gi"
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved

# 驱逐配置
evictionHard:
memory.available: "500Mi"
nodefs.available: "10%"
imagefs.available: "15%"
evictionSoft:
memory.available: "1Gi"
nodefs.available: "15%"
evictionSoftGracePeriod:
memory.available: "1m"
nodefs.available: "1m"
evictionPressureTransitionPeriod: "5m"

# 日志配置
logging:
format: text
verbosity: 2

# 运行时配置
runtimeRequestTimeout: "2m"
imagePullProgressDeadline: "1m"
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80

# 健康检查
healthzBindAddress: "0.0.0.0"
healthzPort: 10248
enableDebuggingHandlers: true

# 容器管理
containerLogMaxSize: "10Mi"
containerLogMaxFiles: 5

# 网络
hairpinMode: "hairpin-veth"
maxContainerCount: -1

# 特性开关
featureGates:
EventedPLEG: true
GracefulNodeShutdown: true
NodeSwap: false

8.5 参考资料