2. 容器镜像基础:镜像分层原理、Registry 与 Repository 概念、镜像拉取流程详解

2.1 镜像分层原理

容器镜像并非一个巨大的单体文件,而是由一系列只读层(Layer)堆叠而成的联合文件系统。每一层代表 Dockerfile 中的一条指令(如 FROM、RUN、COPY),层与层之间通过内容寻址(Content-Addressable)和写时复制(Copy-on-Write, CoW)机制实现高效存储与分发。

核心要点:镜像层是只读的,容器运行时在镜像顶部添加一个可写层(容器层)。所有对文件的修改都发生在可写层,底层镜像层保持不变,从而实现多个容器共享同一镜像层,节省磁盘空间。

2.1.1 分层结构详解

层级位置 内容来源 特性
基础层(Base Layer) FROM ubuntu:22.04 最底层,包含操作系统基础文件系统(如 /bin、/lib)
中间层(Intermediate Layer) RUN apt-get update && apt-get install -y nginx 每次 RUN、COPY、ADD 指令生成一个新层,包含增量变更
配置层(Config Layer) CMD ["nginx", "-g", "daemon off;"] 存储元数据(入口点、环境变量、暴露端口),不占用文件系统空间
容器可写层(Container Layer) 容器运行时动态产生 容器删除后该层随之销毁,不持久化

2.1.2 分层存储的优势

  • 空间复用:多个容器共享相同的基础层和中间层,拉取一次即可被所有容器使用。
  • 增量传输:镜像推送/拉取时只传输变更的层,而非整个镜像。
  • 构建缓存:Docker 构建时若某层未变更,直接使用缓存层,加速 CI/CD 流水线。
运维建议:将频繁变更的指令(如 COPY . /app)放在 Dockerfile 底部,将不常变更的指令(如 RUN apt-get install)放在顶部,以最大化利用构建缓存。

2.1.3 镜像分层结构图

容器镜像分层结构示意图 容器可写层(Container Layer) 配置层(CMD / ENV / EXPOSE) 中间层 3:COPY app /app 中间层 2:RUN apt-get install -y nginx 中间层 1:RUN apt-get update 基础层:FROM ubuntu:22.04 写时复制(CoW) 修改文件时,先将文件 从下层复制到可写层 再修改,下层不变

2.2 Registry 与 Repository 概念

2.2.1 核心定义

术语 定义 类比
Registry(注册表) 存储和管理容器镜像的服务端,提供镜像的推送、拉取、删除等 API 类似 Maven 的 Nexus、Python 的 PyPI
Repository(仓库) Registry 内用于组织镜像的逻辑命名空间,一个 Repository 包含同一应用的不同版本(Tag) 类似 GitHub 上的一个代码仓库
Tag(标签) 镜像的版本标识符,通常为语义化版本号(如 v1.2.3)或环境标识(如 latest) 类似 Git 的 Tag
Manifest(清单) 描述镜像的元数据文件,包含层列表、配置、架构信息等 类似 Dockerfile 的编译产物索引

2.2.2 镜像地址结构

一个完整的容器镜像地址遵循以下格式:

[Registry 域名]/[命名空间]/[Repository 名称]:[Tag]

# 示例
docker.io/library/nginx:1.25.3
└──────┘ └──────┘ └───┘ └─────┘
  Registry  命名空间  仓库   标签

# 私有 Registry 示例
harbor.internal.com/production/web-app:v2.1.0
└───────────────┘ └────────┘ └─────┘ └─────┘
    Registry 域名     命名空间   仓库   标签
常见误区:很多运维人员混淆 Repository 和 Registry。Registry 是服务端进程(如 Docker Hub、Harbor、Quay),Repository 是 Registry 内的一个镜像集合。一个 Registry 可以包含成百上千个 Repository。

2.2.3 Registry 与 Repository 关系图

Registry 与 Repository 层级关系 Registry:docker.io(Docker Hub) Repository: nginx Tag: 1.25.3 Tag: latest Tag: alpine (3 个版本) Repository: redis Tag: 7.2.4 Tag: 7.0 Tag: 6.2 (3 个版本) Repository: ubuntu Tag: 22.04 Tag: 20.04 Tag: 18.04 (3 个版本) 一个 Registry 包含多个 Repository,每个 Repository 包含多个 Tag 版本的镜像

2.3 镜像拉取流程详解

当执行 docker pull nginx:1.25.3 或 kubelet 通过 CRI 接口拉取镜像时,背后经历了一套完整的客户端-服务端交互流程。理解该流程对排查拉取超时、认证失败、层损坏等问题至关重要。

2.3.1 拉取流程步骤

  1. 解析镜像地址:客户端解析镜像名称,提取 Registry 地址、Repository 名称和 Tag。若未指定 Registry,默认使用 docker.io。
  2. 认证(Authentication):若 Registry 需要认证,客户端向 Registry 的 /v2/ 端点发起请求,获取 Bearer 或 Basic 令牌。
  3. 获取 Manifest:客户端请求 /v2/<repository>/manifests/<tag>,Registry 返回镜像的 Manifest 文件(JSON 格式),包含层列表、配置摘要、架构信息。
  4. 解析 Manifest:客户端解析 Manifest,提取所有层的 digest(SHA256 哈希值)和大小。
  5. 并行拉取层:客户端根据 Manifest 中的层列表,并发请求 /v2/<repository>/blobs/<digest> 下载每个层。若本地已有相同 digest 的层(缓存命中),则跳过下载。
  6. 验证层完整性:下载完成后,客户端计算层的 SHA256 值,与 Manifest 中的 digest 比对,确保数据未被篡改或损坏。
  7. 解压与组装:客户端将下载的压缩层(通常为 .tar.gz)解压,按顺序叠加到本地镜像存储中,生成最终的镜像文件系统。

2.3.2 拉取流程时序图

镜像拉取流程时序图 Docker 客户端 Registry 服务端 本地存储 ① 解析镜像地址 ② 认证请求(/v2/) ③ 返回 Bearer ④ 请求 Manifest ⑤ 返回 Manifest(含层列表) ⑥ 检查本地缓存(digest 比对) ⑦ 返回缓存命中/未命中结果 ⑧ 并行请求缺失的 Blob 层 ⑨ 返回压缩层数据 ⑩ 验证 SHA256、解压、组装镜像 关键优化点:步骤 ⑥ 的本地缓存检查可大幅减少网络传输;步骤 ⑧ 的并行拉取可提升大镜像下载速度

2.3.3 拉取过程中的关键优化点

优化环节 技术手段 效果
层缓存 节点本地缓存已拉取的层,通过 containerd 或 dockerd 的 snapshotter 管理 避免重复下载相同层,减少 60%-80% 的拉取流量
并发拉取 客户端同时发起多个 Blob 下载请求(默认 3-6 并发) 充分利用带宽,大镜像拉取时间可缩短 50% 以上
P2P 分发 使用 Dragonfly、Kraken 等 P2P 网络,节点间共享层数据 减轻 Registry 压力,大规模集群拉取速度提升 10 倍
镜像预热 在业务高峰期前,提前将镜像拉取到所有节点 消除拉取延迟,实现容器秒级启动
排障经验:当遇到 manifest unknown 错误时,通常是因为 Tag 不存在或 Manifest 格式不兼容(如 OCI 与 Docker V2 格式混用)。使用 docker manifest inspect <image> 可查看 Manifest 详情。