4. 镜像拉取原理:kubelet 与 CRI、containerd/CRI-O 的交互流程

理解镜像拉取的完整链路,是进行容量规划与拉取加速优化的前提。当用户通过 kubectl run 或 Deployment 创建 Pod 时,kubelet 并不会直接调用容器运行时去拉取镜像,而是通过一套标准化的接口协议——CRI(Container Runtime Interface),与底层的 containerd 或 CRI-O 进行交互。整个流程涉及多个组件的有序协作,任何一个环节的瓶颈都会直接影响 Pod 启动速度。

4.1 核心组件与角色定义

在深入流程之前,先明确参与镜像拉取的四个关键角色:

组件 角色 职责
kubelet 节点代理 监听 API Server,管理 Pod 生命周期,调用 CRI 创建/删除容器
CRI shim(containerd/CRI-O) CRI 实现层 实现 CRI 接口(gRPC),将 kubelet 请求翻译为运行时操作
containerd / CRI-O 守护进程 容器运行时核心 管理镜像存储层、容器生命周期、快照(snapshotter)
镜像仓库(Registry) 镜像存储 提供镜像层(layer)的拉取与认证服务
关键认知: kubelet 并不关心底层是 containerd 还是 CRI-O,它只通过 CRI 的 ImageService 和 RuntimeService 两个 gRPC 服务进行通信。这种解耦设计使得 Kubernetes 可以灵活替换容器运行时。

4.2 镜像拉取完整交互流程

从 kubelet 发起 Pod 创建请求到镜像层全部落盘,共经历以下 7 个核心步骤:

  1. kubelet 触发 SyncPod:kubelet 的 Pod 管理工作循环检测到新 Pod 需要调度到本节点,调用 SyncPod 方法。
  2. kubelet 调用 CRI ImageService.PullImage:kubelet 根据 Pod Spec 中的 image 字段,构造 PullImageRequest,通过 gRPC 发送给 CRI shim。
  3. CRI shim 解析请求:containerd 或 CRI-O 的 CRI 插件接收请求,解析镜像名称、标签、认证信息(imagePullSecrets)。
  4. CRI shim 调用底层运行时 PullImage:将解析后的请求转发给 containerd 核心或 CRI-O 的镜像管理模块。
  5. 镜像层下载与解压:运行时根据 manifest 获取镜像层列表,并发或顺序下载每一层(layer),并解压到 snapshotter 管理的快照目录。
  6. 返回镜像引用:下载完成后,运行时返回镜像的 digest 和元数据给 CRI shim。
  7. CRI shim 返回 PullImageResponse:kubelet 收到响应,确认镜像已就绪,继续执行后续的容器创建流程。
镜像拉取交互流程(kubelet → CRI → containerd/CRI-O) kubelet CRI shim 运行时核心 Snapshotter 镜像仓库 PullImage (gRPC) 内部调用 PullImage HTTP 拉取镜像层 返回层数据 解压并写入快照 返回镜像 digest PullImageResponse 说明: ① kubelet 通过 CRI 的 ImageService.PullImage 发起请求,不直接操作镜像层。 ② containerd/CRI-O 内部负责并发下载、解压、缓存,并返回镜像唯一标识。

4.3 CRI 接口详解:ImageService 与 RuntimeService

CRI 定义了两种核心 gRPC 服务,镜像拉取属于 ImageService 的范畴:

服务 关键方法 用途
ImageService PullImage、ListImages、ImageStatus、RemoveImage 镜像拉取、查询、删除
RuntimeService RunPodSandbox、CreateContainer、StartContainer Pod 沙箱与容器生命周期管理

kubelet 在 SyncPod 中会先调用 PullImage,确保镜像存在后,再调用 CreateContainer 和 StartContainer。如果镜像已经存在于节点本地缓存,kubelet 会跳过拉取步骤,直接使用已有镜像。

4.4 containerd 与 CRI-O 的内部实现差异

虽然两者都实现了 CRI 接口,但在镜像拉取的内部实现上存在关键差异,这些差异会影响拉取性能和资源消耗:

维度 containerd CRI-O
镜像存储后端 自研 content store + snapshotter 直接使用 containers/storage(与 Podman 共享)
镜像拉取并发度 默认并发拉取所有层(可配置) 串行拉取层,但支持并行拉取不同镜像
解压方式 使用 containerd 自带的解压器 调用系统 tar/gzip 命令
镜像缓存策略 基于 content-based 寻址,自动去重 基于引用计数,支持镜像分层缓存
对 CRI 的响应 通过 CRI plugin 嵌入 containerd 进程 独立进程 crio 实现 CRI 接口
容量规划启示: containerd 的并发拉取策略在带宽充足时能显著缩短拉取时间,但会瞬时消耗大量网络和磁盘 I/O。CRI-O 的串行拉取更温和,适合 I/O 受限的节点。在规划节点资源预留时,需要根据所选运行时调整网络带宽和磁盘 IOPS 的预留量。

4.5 镜像拉取的关键性能瓶颈点

从交互流程中可以识别出以下四个主要瓶颈,它们直接影响 Pod 启动速度和节点资源消耗:

  • 网络带宽与延迟:镜像层从 Registry 下载的速度受限于节点出口带宽和 Registry 的响应能力。大镜像(>1GB)在带宽不足时可能成为 Pod 启动的瓶颈。
  • 磁盘 I/O 与解压性能:镜像层下载后需要解压并写入 snapshotter 目录。使用 HDD 或 IOPS 受限的磁盘时,解压和写入会成为瓶颈。
  • 镜像层去重与缓存命中率:如果节点上多个 Pod 使用相同的基础镜像层,containerd/CRI-O 的 content-based 寻址可以避免重复下载。缓存命中率低时,每次拉取都需要完整下载。
  • Registry 认证与限流:频繁的拉取请求可能触发 Registry 的速率限制(rate limit),导致拉取失败或排队等待。
实战注意: 在容量规划时,不要只关注 CPU 和内存。镜像拉取阶段的网络带宽和磁盘 IOPS 同样需要预留。建议为每个节点预留至少 100Mbps 的带宽用于镜像拉取,并优先使用 SSD 作为容器镜像存储盘。

4.6 镜像拉取流程中的关键数据结构

kubelet 发送给 CRI shim 的 PullImageRequest 包含以下关键字段,理解这些字段有助于排查拉取问题:

// PullImageRequest 是 kubelet 通过 CRI 发送给运行时的请求
message PullImageRequest {
  // 镜像的完整引用,如 docker.io/library/nginx:1.25
  ImageSpec image = 1;

  // 认证信息,来自 imagePullSecrets
  AuthConfig auth = 2;

  // Pod 沙箱配置,用于确定拉取镜像时的平台架构
  PodSandboxConfig sandbox_config = 3;
}

// ImageSpec 定义镜像的标识
message ImageSpec {
  // 镜像引用字符串
  string image = 1;

  // 可选的注解,用于传递额外元数据
  map<string, string> annotations = 2;
}

// AuthConfig 包含 Registry 认证凭据
message AuthConfig {
  string username = 1;
  string password = 2;
  string auth = 3;
  string server_address = 4;
  string identity_ = 5;
  string registry_ = 6;
}

当 kubelet 调用 PullImage 后,CRI shim 会返回 PullImageResponse,其中包含镜像的唯一标识(digest),用于后续的容器创建引用:

message PullImageResponse {
  // 镜像的引用,包含 digest,如 docker.io/library/nginx@sha256:abc123...
  string image_ref = 1;
}

4.7 镜像拉取流程的优化切入点

基于上述原理,可以从以下四个方向进行镜像拉取加速:

优化方向 具体措施 影响环节
镜像层缓存 部署节点级镜像缓存(如 Harbor 代理缓存、Kraken) 减少远程 Registry 拉取次数
并发拉取调优 调整 containerd 的 concurrent_downloads 参数 提升网络利用率
镜像分层优化 使用多阶段构建,减少镜像层数,合并 RUN 指令 减少需要下载的层数量
预拉取与预热 在节点初始化或低负载时预拉取常用镜像 避免 Pod 启动时的拉取等待
容量规划关联: 镜像拉取加速的最终目的是减少 Pod 启动时间,从而降低节点资源预留的峰值需求。通过缓存和预拉取,可以将镜像拉取的资源消耗从“突发高峰”转变为“平稳持续”,使容量规划更加精确。