2. Kubernetes 镜像拉取基础:kubelet 镜像拉取流程、镜像层与 manifest 结构

2.1 镜像拉取的核心参与者

在 Kubernetes 集群中,镜像拉取并非由 kube-apiserver 或 scheduler 直接执行,而是由每个节点上的 kubelet 组件负责。kubelet 通过 CRI(Container Runtime Interface)与容器运行时(如 containerd、CRI-O)交互,最终由运行时完成镜像层的下载与解包。

从容量规划角度看,理解这一流程有助于定位拉取瓶颈:是 kubelet 的并发控制不足,还是运行时下载带宽受限,或是镜像仓库的限流策略导致。

容量规划要点: 镜像拉取是节点启动 Pod 时最耗时的操作之一。大规模集群中,若多个 Pod 同时调度到同一节点,kubelet 的串行拉取队列可能成为性能瓶颈。

2.2 kubelet 镜像拉取流程

kubelet 内部维护一个 imageManager,负责管理镜像拉取请求。其核心流程如下:

  1. Pod 调度到节点:kubelet 从 apiserver 监听到 Pod 绑定事件,开始创建 Pod 沙箱。
  2. 解析容器镜像:kubelet 读取 Pod Spec 中的 image 字段,提取镜像名称与标签(如 nginx:1.25)。
  3. 检查本地缓存:通过 CRI 调用 ImageStatus 检查镜像是否已存在于节点本地存储。
  4. 拉取决策:若本地不存在,或 imagePullPolicy 为 Always,则触发拉取。
  5. 加入拉取队列:kubelet 的 imageManager 将请求加入一个 串行队列(默认串行拉取,可通过 --serialize-image-pulls 调整)。
  6. CRI 调用 PullImage:kubelet 调用容器运行时的 PullImage gRPC 接口,传递镜像引用与认证信息。
  7. 运行时下载镜像层:容器运行时根据 manifest 获取镜像层列表,逐层下载并解包到本地存储。
  8. 返回结果:拉取完成后,kubelet 更新 Pod 状态,继续后续的容器创建流程。
并发控制关键参数: kubelet 的 --serialize-image-pulls 默认为 true(串行拉取)。若设置为 false,可并行拉取多个镜像,但会增加磁盘 I/O 与网络带宽压力。容量规划时需根据节点规格与镜像大小权衡。
kubelet 镜像拉取流程 Pod 调度到节点 解析容器镜像引用 检查本地镜像缓存 拉取决策 (Policy) 加入串行拉取队列 CRI PullImage 调用 运行时逐层下载 容量规划关注点: • 串行队列长度 • 镜像大小与层数 • 仓库限流与带宽

2.3 镜像层与 manifest 结构

容器镜像由多个只读层(Layer)叠加而成,每一层对应 Dockerfile 中的一条指令。镜像的元数据由 manifest(清单)描述,它是镜像仓库中最重要的索引文件。

2.3.1 镜像层结构

每个镜像层是一个压缩的 tar 包,包含文件系统的增量变更。层与层之间通过 内容寻址(Content-Addressable)方式标识,使用 SHA256 摘要作为唯一 ID。

层类型 说明 示例
基础层 来自基础镜像(如 ubuntu:22.04),包含操作系统文件 sha256:abc123...
中间层 由 RUN、COPY、ADD 等指令生成 sha256:def456...
可写层 容器运行时创建的临时层,容器删除后消失 (非镜像层)
容量规划陷阱: 镜像层数过多(超过 50 层)会导致 manifest 解析变慢,且拉取时需逐层校验摘要。建议将 RUN 指令合并,减少层数。

2.3.2 Manifest 结构

Manifest 是镜像的“目录文件”,包含以下关键字段:

  • schemaVersion:清单格式版本,目前主流为 2。
  • mediaType:媒体类型,如 application/vnd.docker.distribution.manifest.v2+json。
  • config:指向镜像配置文件的引用,包含环境变量、入口点、默认命令等。
  • layers:镜像层列表,每个元素包含 digest(摘要)、size(压缩后大小)、mediaType。

以下是一个简化版的 manifest 示例:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 7023,
    "digest": "sha256:config_digest_here"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 28000000,
      "digest": "sha256:layer1_digest"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 1500000,
      "digest": "sha256:layer2_digest"
    }
  ]
}
镜像层与 Manifest 结构 Manifest (清单) schemaVersion: 2 mediaType: ...manifest.v2+json config: { digest, size } layers: [ layer1, layer2, ... ] Config (配置) 环境变量 / CMD / ENTRYPOINT Layer 1: 基础层 (ubuntu:22.04) Layer 2: 中间层 (安装依赖) Layer 3: 中间层 (复制应用) 层缓存与容量规划: • 相同摘要的层可在节点间共享 • 层大小影响拉取时间与磁盘占用 • 建议将不变的基础层放在前面 • 利用 docker history 分析层大小 拉取顺序: 1. 先拉取 Manifest 2. 解析 Config 与 Layers 3. 逐层下载并校验摘要

2.4 镜像拉取策略与容量影响

Kubernetes 支持三种 imagePullPolicy,直接影响拉取频率与节点资源消耗:

策略 行为 容量规划建议
Always 每次启动 Pod 都拉取镜像 适用于开发环境;生产环境应避免,除非镜像标签为 :latest
IfNotPresent 仅当本地不存在时拉取 生产环境推荐,减少不必要的网络与磁盘开销
Never 仅使用本地镜像,不存在则报错 适用于预加载镜像的离线环境
最佳实践: 生产环境使用 IfNotPresent 并配合 imagePullSecrets 认证。对于频繁更新的应用,使用语义化版本标签(如 v1.2.3)而非 :latest,避免触发不必要的 Always 拉取。

2.5 镜像拉取性能关键指标

从容量规划角度,以下指标需要持续监控:

  • 拉取延迟(Pull Latency):从发起拉取到镜像就绪的时间,受镜像大小、层数、带宽影响。
  • 拉取并发数(Concurrent Pulls):kubelet 同时处理的拉取请求数量,默认串行。
  • 镜像缓存命中率(Cache Hit Rate):本地已有镜像的比例,越高则拉取越少。
  • 仓库限流(Rate Limiting):镜像仓库(如 Docker Hub)对匿名或免费用户的拉取次数限制。
容量规划公式: 节点启动 Pod 的总时间 ≈ 镜像拉取时间 + 容器创建时间 + 应用启动时间。其中镜像拉取时间 = 镜像大小 / 有效带宽 × 层数因子(因逐层校验增加约 10%~20% 开销)。