3. 镜像拉取流程详解:从 kubelet 到容器运行时(containerd/docker)的完整链路

当 Pod 被调度到节点后,kubelet 负责执行 Pod 生命周期管理,其中最关键的一步就是拉取容器镜像。理解从 kubelet 发起请求到容器运行时(CRI)最终完成镜像下载的完整链路,是定位“镜像拉取失败”类问题的核心基础。本节将拆解这一链路中的每个环节,并指出常见的故障点。

3.1 链路总览:四个关键阶段

镜像拉取请求从 kubelet 发出到最终写入本地存储,依次经过以下四个阶段:

  1. kubelet 同步循环(SyncLoop):kubelet 通过 List/Watch 机制感知 Pod 变更,触发 Pod 创建流程。
  2. Pod 工作队列与状态管理:kubelet 将待创建的 Pod 加入工作队列,调用容器运行时接口(CRI)。
  3. CRI shim 层(containerd/docker):CRI shim 将 kubelet 的 gRPC 请求转换为容器运行时内部调用。
  4. 镜像拉取引擎:容器运行时根据镜像名称、认证信息、镜像仓库地址执行拉取操作,最终解压并存储到本地。
🔍 排查要点: 镜像拉取失败通常发生在阶段 3 或阶段 4。阶段 1 和 2 的问题更多表现为 Pod 无法调度或 kubelet 未响应。
镜像拉取完整链路架构图 镜像拉取完整链路架构图 kubelet SyncLoop List/Watch Pod 变更 Pod 工作队列 调用 CRI gRPC 接口 CRI shim 层 containerd / dockerd 镜像拉取引擎 下载 / 解压 / 存储 📌 各阶段常见故障点 • 阶段1:kubelet 未启动、Pod 未调度到本节点、节点 NotReady • 阶段2:CRI 接口超时、kubelet 与容器运行时版本不匹配 • 阶段3:containerd/docker 服务异常、CRI shim 崩溃、gRPC 连接断开 • 阶段4:镜像仓库不可达、认证失败、磁盘空间不足、镜像层损坏

3.2 阶段一:kubelet SyncLoop 与 Pod 创建触发

kubelet 的核心组件 SyncLoop 通过以下机制感知 Pod 变化:

  • List/Watch API Server:kubelet 启动时通过 List 获取全量 Pod 列表,之后通过 Watch 持续监听增量变更。
  • Pod 状态更新:当 Pod 被调度到当前节点(spec.nodeName == nodeName),kubelet 将其加入内部 Pod 缓存。
  • SyncHandler 触发:SyncLoop 定期(默认 1 秒)检查待同步 Pod 列表,调用 syncPod() 方法。

在 syncPod() 中,kubelet 会依次执行:创建 Pod 沙箱(Sandbox)、配置网络、挂载卷、拉取业务容器镜像、启动容器。镜像拉取请求在此阶段被封装为 CRI 调用。

⚠️ 常见问题: 如果 kubelet 的 --node-status-update-frequency 设置过长(默认 10s),或节点与 API Server 网络不稳定,可能导致 Pod 变更延迟感知,表现为 Pod 长时间处于 Pending 状态。

3.3 阶段二:CRI gRPC 调用与请求封装

kubelet 通过 CRI(Container Runtime Interface) 与容器运行时通信。CRI 是基于 gRPC 的协议,kubelet 作为客户端,容器运行时作为服务端。关键调用接口如下:

CRI 接口 调用时机 主要参数
RunPodSandbox 创建 Pod 网络命名空间 Pod 名称、命名空间、DNS 配置
CreateContainer 创建容器实例 容器配置、挂载点、环境变量
StartContainer 启动容器进程 容器 ID
PullImage 拉取镜像 镜像名称、认证信息、Sandbox 配置

当 kubelet 调用 PullImage 时,会传入以下关键信息:

// PullImageRequest 简化结构
message PullImageRequest {
  // 镜像名称,如 "nginx:1.25"
  ImageSpec image = 1;
  // 认证信息,来自 imagePullSecrets
  AuthConfig auth = 2;
  // Pod 沙箱配置,用于传递代理等环境
  PodSandboxConfig sandbox_config = 3;
}
💡 排查技巧: 使用 crictl pull 命令可以手动模拟 kubelet 的镜像拉取请求,验证容器运行时是否正常工作。例如:crictl pull nginx:1.25。

3.4 阶段三:CRI shim 层——containerd 与 docker 的差异

CRI shim 是 kubelet 与容器运行时之间的适配层。不同容器运行时的实现方式不同:

容器运行时 CRI shim 实现 镜像管理工具 关键特性
containerd 内置 CRI 插件(cri) crictl、nerdctl 轻量、直接调用 containerd 镜像服务
Docker dockershim(K8s v1.24 前) docker CLI 通过 Docker Engine 间接管理镜像
CRI-O 原生 CRI 实现 crictl、podman 专为 Kubernetes 优化

以 containerd 为例,CRI 请求到达后的处理流程如下:

  1. containerd 的 CRI 插件接收 gRPC 请求。
  2. CRI 插件调用 containerd 的 Image Service(images API)。
  3. Image Service 检查本地镜像缓存(/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs)。
  4. 如果本地不存在,则触发远程拉取流程。
containerd 内部镜像拉取流程 containerd 内部镜像拉取流程 kubelet CRI gRPC CRI Plugin Image Service 本地缓存 远程仓库拉取 🔍 排查关键:检查 containerd 日志 journalctl -u containerd -n 100,搜索 "pull image" 或 "image not found" 关键字。 本地缓存路径:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/

3.5 阶段四:镜像拉取引擎——下载、解压与存储

当容器运行时确认本地无缓存镜像后,进入远程拉取流程。该流程包含以下子步骤:

步骤 操作 涉及组件 可能失败原因
1 解析镜像名称与标签 镜像解析器 镜像名称格式错误、标签不存在
2 获取认证信息 认证管理器 imagePullSecrets 缺失、Secret 过期
3 连接镜像仓库 HTTP/HTTPS 客户端 网络不通、DNS 解析失败、TLS 证书错误
4 下载 Manifest 文件 Manifest 解析器 Manifest 格式错误、镜像仓库返回 404
5 下载镜像层(Layer) 层下载器 磁盘空间不足、网络中断、层损坏
6 解压与快照 快照器(Snapshotter) 文件系统错误、inode 耗尽
7 更新本地缓存索引 元数据存储 数据库损坏、并发写入冲突

在 containerd 中,镜像层默认存储在 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/ 目录下。每个镜像层对应一个子目录,通过 overlayfs 实现联合挂载。

📊 关键指标: 镜像拉取速度受以下因素影响:
• 镜像层大小与数量(层越多,拉取越慢)
• 镜像仓库带宽与并发限制(通常 Docker Hub 有 5MB/s 限制)
• 节点磁盘 I/O 性能(解压大镜像时尤为明显)
• 网络延迟与丢包率(建议 < 50ms,丢包率 < 0.1%)

3.6 链路故障排查命令速查表

以下命令覆盖了从 kubelet 到容器运行时的每个环节,用于快速定位镜像拉取失败的原因:

排查目标 命令 预期输出
kubelet 日志 journalctl -u kubelet -n 200 --no-pager 搜索 "Failed to pull image" 或 "ErrImagePull"
Pod 事件 kubectl describe pod <pod-name> -n <ns> Events 字段显示拉取失败原因
containerd 状态 systemctl status containerd Active: active (running)
containerd 镜像列表 crictl images 显示本地已缓存镜像
手动拉取测试 crictl pull nginx:1.25 Image is up to date 或下载进度
磁盘空间检查 df -h /var/lib/containerd Avail 列应大于镜像大小
网络连通性 curl -I https://registry-1.docker.io/v2/ HTTP/2 200 或 401(有认证)
⚠️ 关键提醒: 当镜像拉取失败时,不要只查看 Pod 事件。Pod 事件中的错误信息往往被截断或过于笼统。必须结合 kubelet 日志和容器运行时日志(如 containerd 日志)才能获取完整的错误栈。例如,Pod 事件显示 "Failed to pull image",但 containerd 日志中可能包含 "no space left on device" 或 "x509: certificate signed by unknown authority" 等具体原因。