3. 镜像拉取原理
容器镜像的拉取速度直接决定了 Pod 冷启动的延迟。要理解预热为何能加速、削峰为何有效,必须先深入镜像的底层存储模型与拉取协议。本节从分层结构、Manifest 清单、Blob 存储和完整拉取流程四个维度拆解镜像拉取原理。
3.1 镜像分层结构:只读层的堆叠与共享
Docker 与 containerd 均遵循 OCI(Open Container Initiative)镜像规范。镜像由一组只读层(Layer)堆叠而成,每一层代表 Dockerfile 中的一条指令(如 RUN、COPY、ADD)。
- 基础层(Base Layer):通常来自
FROM指令,如ubuntu:22.04,是整个镜像的根文件系统。 - 增量层(Diff Layer):后续每条指令生成一个增量层,仅保存与上一层的差异文件。
- 容器层(Container Layer):运行容器时,在只读层之上叠加一个可写层(
upperdir),所有写操作写入该层,遵循写时复制(CoW)策略。
关键指标: 同一宿主机上,多个容器共享相同的基础层和中间层,磁盘占用仅一份,拉取时也只需拉取本地缺失的层。
预热原理: 预热工具(如
kubelet image pull 或 spegel)提前将各层 Blob 下载到节点本地,使 Pod 调度时无需等待远程 Registry 响应,实现秒级启动。
3.2 Manifest 清单:镜像的元数据索引
Manifest 是镜像的“目录文件”,描述了镜像包含哪些层、各层的摘要(Digest)、大小、媒体类型以及配置信息。OCI 规范定义了两种 Manifest 格式:
| Manifest 类型 | 适用场景 | 核心字段 |
|---|---|---|
| Image Manifest (V2) | 单架构镜像 | config、layers[](每个 layer 包含 digest、size、mediaType) |
| Image Index (OCI Index) | 多架构镜像(如 amd64 + arm64) | manifests[](每个 manifest 指向一个平台特定的 Image Manifest) |
拉取时,客户端首先通过 tag 或 digest 获取 Manifest,解析出所有层的 Digest 列表,然后按顺序拉取每一层。
3.3 Blob 存储:内容寻址与去重
每一层在 Registry 中存储为一个 Blob(二进制大对象),其唯一标识符是 Digest(sha256:xxxx)。Blob 存储的核心特性:
- 内容寻址:Digest 由内容哈希计算得出,内容不变则 Digest 不变,天然支持去重。
- 不可变性:Blob 一旦上传即不可修改,只能通过新 Digest 发布新版本。
- 压缩存储:Registry 通常存储 gzip 压缩后的 Blob,拉取时按需解压。
避坑提示: 如果镜像层数过多(超过 50 层),Manifest 体积增大,拉取时解析时间线性增长。建议合并 RUN 指令,减少层数。
3.4 镜像拉取流程:从 Registry 到本地存储
以下 SVG 图展示了 containerd 从 Registry 拉取镜像的完整流程,包含认证、Manifest 获取、Blob 并行下载、解压与挂载。
3.5 拉取流程关键步骤详解
- 认证握手:客户端向 Registry 发起
GET /v2/,Registry 返回401 Unauthorized并携带Bearer realm,客户端使用凭证获取 。 - Manifest 获取:客户端发送
GET /v2/<name>/manifests/<tag>,Registry 返回 JSON 格式的 Manifest,包含layers[]数组。 - 层依赖解析:客户端按
layers顺序遍历,检查本地/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs是否已存在相同 Digest 的 Blob。 - 并行下载:对缺失的层,containerd 使用 goroutine 池 并发发起
GET /v2/<name>/blobs/<digest>请求,默认并发数为 3(可通过max_concurrent_downloads调整)。 - 解压与校验:下载完成后,计算 Blob 的 SHA256 摘要与 Manifest 中的 Digest 比对,一致后解压为
tar格式,写入 snapshotter 的挂载目录。 - overlayfs 挂载:containerd 将所有层按顺序挂载为 overlayfs 的
lowerdir,最终呈现为完整的根文件系统。
3.6 拉取性能对比:分层 vs 扁平镜像
| 维度 | 分层镜像(推荐) | 扁平镜像(单层) |
|---|---|---|
| 拉取时间 | 可并行下载,仅拉取增量层 | 必须串行下载整个大 Blob |
| 磁盘占用 | 共享基础层,多容器节省空间 | 每个容器独立存储完整文件系统 |
| 缓存命中率 | 基础层变更少,缓存命中率高 | 任何修改都导致整个镜像缓存失效 |
| 预热效率 | 只需预热缺失的增量层 | 必须预热整个镜像 |
实践建议: 在 Backstage 插件或 CI/CD 流水线中,优先使用
docker pull --platform 指定架构,避免拉取多架构索引中不必要的 Manifest。预热时使用 containerd 的 ctr image pull 命令,可跳过 Docker Engine 的额外开销。
3.7 冷启动削峰的关键:Blob 本地缓存
当大量 Pod 同时调度到同一节点时,若镜像层未缓存,Registry 会承受巨大的并发拉取压力,导致带宽瓶颈和拉取超时。预热策略的核心就是提前将 Blob 缓存到节点本地,使拉取流程中的步骤 3-6 几乎零耗时。
以下 SVG 图对比了无预热与有预热场景下的拉取时间线:
3.8 小结
镜像拉取的核心瓶颈在于 Blob 网络下载。理解分层结构、Manifest 索引和并行拉取机制后,可以精准设计预热策略:
- 预热时优先拉取 基础层 和 公共层,最大化共享缓存。
- 使用 Digest 引用 而非 Tag,避免因 Tag 变更导致缓存失效。
- 在 Backstage 插件中集成预热调度,在集群低负载时段提前拉取热门镜像。