3. 镜像拉取流程详解:从 kubelet 到容器运行时(containerd/docker)的完整链路
当 Pod 被调度到节点后,kubelet 负责执行 Pod 生命周期管理,其中最关键的一步就是拉取容器镜像。理解从 kubelet 发起请求到容器运行时(CRI)最终完成镜像下载的完整链路,是定位“镜像拉取失败”类问题的核心基础。本节将拆解这一链路中的每个环节,并指出常见的故障点。
3.1 链路总览:四个关键阶段
镜像拉取请求从 kubelet 发出到最终写入本地存储,依次经过以下四个阶段:
- kubelet 同步循环(SyncLoop):kubelet 通过 List/Watch 机制感知 Pod 变更,触发 Pod 创建流程。
- Pod 工作队列与状态管理:kubelet 将待创建的 Pod 加入工作队列,调用容器运行时接口(CRI)。
- CRI shim 层(containerd/docker):CRI shim 将 kubelet 的 gRPC 请求转换为容器运行时内部调用。
- 镜像拉取引擎:容器运行时根据镜像名称、认证信息、镜像仓库地址执行拉取操作,最终解压并存储到本地。
🔍 排查要点: 镜像拉取失败通常发生在阶段 3 或阶段 4。阶段 1 和 2 的问题更多表现为 Pod 无法调度或 kubelet 未响应。
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 请求到达后的处理流程如下:
- containerd 的 CRI 插件接收 gRPC 请求。
- CRI 插件调用 containerd 的 Image Service(
imagesAPI)。 - Image Service 检查本地镜像缓存(
/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%)
• 镜像层大小与数量(层越多,拉取越慢)
• 镜像仓库带宽与并发限制(通常 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" 等具体原因。