4. 镜像拉取流程剖析:kubelet → CRI → containerd → registry 交互、镜像层与 manifest

镜像拉取是容器运行时最频繁的操作之一,其延迟直接影响 Pod 启动时间,进而影响 etcd 备份流程中依赖的 sidecar 容器就绪速度。理解从 kubelet 到 registry 的完整调用链,以及镜像层(Layer)与清单(Manifest)的数据结构,是优化 RPO 的基础。

4.1 端到端调用链:kubelet → CRI → containerd → registry

当 kubelet 决定在节点上启动一个 Pod 时,若所需镜像不存在于本地缓存,会触发以下四层调用:

  1. kubelet:通过 CRI(Container Runtime Interface)gRPC 接口调用 ImageService.PullImage()。
  2. CRI shim(containerd 内置):将 CRI 请求转换为 containerd 内部任务,调用 criService.PullImage()。
  3. containerd:核心守护进程,通过 images.Store 检查本地缓存,若未命中则调用 puller.Pull()。
  4. registry 交互:containerd 使用 distribution 客户端与远端 registry(如 Docker Hub、Harbor、ECR)进行 HTTP/HTTPS 通信,拉取 manifest 和 blob(镜像层)。
关键延迟点: 网络往返(registry 延迟)、层解压(decompress)、层验证(digest 校验)。在 etcd 备份场景中,若备份 sidecar 镜像较大,这些延迟会直接推高 RPO。
kubelet → CRI → containerd → registry 调用链 镜像拉取端到端调用链 kubelet CRI (gRPC) containerd Registry ImageService.PullImage() criService.PullImage() distribution API containerd 本地缓存检查(content store) 🔍 调用链关键点 • kubelet 不直接与 registry 通信,所有镜像操作委托给 CRI 实现。 • containerd 内部使用 content-based addressing,通过 digest 定位层数据。 • 若本地缓存命中(layer 已存在),则跳过 registry 拉取,仅更新 manifest 引用。

4.2 镜像层(Layer)与 Manifest 数据结构

容器镜像由一组只读层(Layer)和一个清单文件(Manifest)组成。每一层对应 Dockerfile 中的一条指令(如 RUN、COPY),层之间通过内容寻址(Content-Addressable Storage, CAS)关联。

4.2.1 镜像层(Layer)

  • 定义:每个层是一个 tar 归档文件,包含文件系统的增量变更。
  • 标识:通过 SHA256 digest 唯一标识,格式为 sha256:<64位十六进制>。
  • 存储:registry 和 containerd 均以 blob 形式存储层数据,解压后挂载到联合文件系统(overlayfs)。
  • 复用:多个镜像可共享相同层(如基础镜像层),减少拉取和存储开销。
RPO 优化启示: 若 etcd 备份 sidecar 镜像与集群中已有镜像共享基础层(如 alpine:3.18),则拉取时只需下载差异层,大幅缩短就绪时间。建议在构建备份镜像时固定基础镜像版本,最大化层复用率。

4.2.2 Manifest(清单)

Manifest 是一个 JSON 文档,描述镜像的元数据、层列表和配置。OCI 规范定义了两种格式:

属性 说明 示例值
schemaVersion 清单格式版本,目前固定为 2 2
mediaType 清单的媒体类型 application/vnd.docker.distribution.manifest.v2+json
config 镜像配置对象的 digest 和大小 {"mediaType":"...","size":7023,"digest":"sha256:..."}
layers 层列表,按从底到顶顺序排列 [{"mediaType":"...","size":12345,"digest":"sha256:..."}, ...]

以下是一个简化的 Manifest 示例:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 7023,
    "digest": "sha256:a1b2c3d4e5f6..."
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 2812345,
      "digest": "sha256:layer1_digest..."
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 567890,
      "digest": "sha256:layer2_digest..."
    }
  ]
}

4.3 拉取流程的并行与串行阶段

containerd 在拉取镜像时,并非所有步骤都是串行的。理解并行度有助于设计 RPO 优化策略:

  • 串行阶段:获取 Manifest(必须首先完成,以获取层列表和 digest)。
  • 并行阶段:层下载(blob fetch)可并发进行,containerd 默认使用 3 个并发 goroutine。
  • 串行阶段:层解压和挂载(需按顺序,因为 overlayfs 依赖层叠顺序)。
注意: 若 registry 对并发请求有限制(如 Docker Hub 的匿名拉取限速),并行下载反而可能触发限速,导致整体拉取时间增加。生产环境建议使用镜像缓存(如 Harbor 代理缓存)或 P2P 分发(如 Dragonfly)。
镜像层与 Manifest 结构 镜像层(Layer)与 Manifest 结构 Manifest (JSON) Config (JSON) Layers [] Layer 1 (base) Layer 2 (app) Layer 3 (config) • 每个 Layer 是一个 tar.gz blob,通过 digest 唯一标识。 • Manifest 先被拉取,然后 containerd 根据 layers 列表并发拉取各层 blob。

4.4 拉取流程中的 RPO 关键指标

在 etcd 备份场景中,以下指标直接影响 RPO:

指标 定义 对 RPO 的影响
镜像大小 所有层压缩后总字节数 越大则拉取时间越长,RPO 窗口扩大
层复用率 本地已缓存层占总层数的比例 复用率越高,需下载的层越少,RPO 越低
registry 延迟 从发起请求到收到第一个字节的时间 高延迟会显著增加拉取总时间
并发下载数 containerd 同时下载的层数(默认 3) 适当增加可加速,但需注意 registry 限速
解压时间 层下载完成后解压到文件系统的时间 CPU 密集型,大层解压可能成为瓶颈
优化建议: 对于 etcd 备份 sidecar 镜像,建议将镜像大小控制在 50MB 以内,使用多阶段构建(multi-stage build)仅保留必要二进制文件,并固定基础镜像版本以提升层复用率。若集群规模较大,可部署镜像缓存节点(如 Harbor 代理)或使用 P2P 分发工具(如 Dragonfly)来降低 registry 延迟和并发限速影响。