4、镜像拉取流程详解:从客户端发起 docker pull 到服务端响应,完整追踪镜像下载的每一步

说实话,很多人用 docker pull 就跟点外卖一样——下单、等餐、开吃,中间厨房怎么运作的完全不关心。但镜像拉取这个流程,恰恰是理解容器运行时最关键的入口。我在生产环境排查过不少拉取慢、失败、认证过期的问题,每次都得把这条链路从头到尾捋一遍。

今天我们就完整走一遍:从你在终端敲下 docker pull nginx:latest 开始,到本地磁盘上出现完整的镜像层数据,中间到底发生了什么。

核心观点:镜像拉取不是简单的文件下载,而是一个基于 HTTP 协议、分步协商、逐层校验的分布式传输过程。每一步都有明确的协议约定和容错机制。

4.1 整体流程概览

先看一张我画的流程图,把整个拉取过程拆成 6 个阶段。你对照着看后面的细节,会清晰很多。

docker pull 完整流程(6 阶段) 1. 解析镜像名 2. 认证与授权 3. 获取 Manifest 4. 层列表解析 5. 逐层下载 6. 解包与组装 关键说明: • 阶段 1-3 是元数据交互,数据量小但协议复杂 • 阶段 5 是真正的数据传输,占 99% 的时间和带宽 • 阶段 6 在本地完成,不涉及网络 各阶段涉及的核心组件: Docker CLI → Docker Daemon → containerd → distribution 客户端 → Registry 服务端 认证流程涉及:Token 服务、OAuth2 Bearer 令牌、Www-Authenticate 质询 传输协议:HTTPS + HTTP/1.1 分块传输,支持断点续传和并发拉取 校验机制:每个层下载完成后计算 digest,与 Manifest 中的 sha256 比对

4.2 第一步:解析镜像名

你敲下 docker pull nginx:latest,Docker CLI 第一件事就是解析这个字符串。别小看这一步,镜像名的格式其实藏着不少信息。

完整的镜像名格式是:[registry-host[:port]/][namespace/]repository[:tag|@digest]。举个例子:

  • nginx:latest → 默认走 Docker Hub,namespace 为 library,tag 为 latest
  • myregistry.com:5000/team/app:v1.2 → 指定私有仓库、端口、命名空间和 tag
  • alpine@sha256:abc123... → 用 digest 精确锁定版本,不可变引用
我的习惯:生产环境尽量用 digest 拉取镜像,而不是 tag。tag 可以被覆盖,digest 是内容寻址的,拉到的内容一定是你期望的。我曾经遇到过 CI 构建的镜像 tag 被覆盖导致回滚失败的事故,从那以后所有核心服务都改用 digest 了。

解析完成后,CLI 会构造出完整的 Registry URL。比如 nginx:latest 会被解析为:

https://registry-1.docker.io/v2/library/nginx/manifests/latest

4.3 第二步:认证与授权

CLI 拿着这个 URL 去请求 Registry。但 Registry 不会直接给你数据——它会先返回一个 401 Unauthorized,并在响应头里带上 Www-Authenticate 质询。

这个质询长这样:

Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/nginx:pull"

CLI 看到这个,就知道要去哪个 Token 服务认证、申请什么权限。然后它拿着你的本地凭证(~/.docker/config.json 里存的用户名密码或 token),去请求一个 Bearer Token。

Token 服务返回的 JSON 大概是这样:

{
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "expires_in": 300,
  "issued_at": "2024-01-15T10:00:00Z"
}
注意:Token 有过期时间(默认 5 分钟)。如果拉取大镜像耗时超过这个时间,后续的层下载请求会失败。Docker 客户端会自动重新认证,但会引入额外的延迟。我建议在拉取超大镜像(比如 AI 模型镜像)时,先确认网络带宽足够在 Token 过期前完成。

拿到 Token 后,CLI 把它放在 Authorization: Bearer <token> 头里,重新请求 Manifest。

4.4 第三步:获取 Manifest

这一步是关键中的关键。Manifest 就是镜像的"目录文件",它描述了镜像包含哪些层、每层的 digest、大小、以及配置信息。

Docker Registry 支持两种 Manifest 格式:

格式 版本 特点 适用场景
Image Manifest V2 Schema 1 已废弃 不支持 digest 引用,安全性差 旧版 Docker(1.9 之前)
Image Manifest V2 Schema 2 当前主流 支持 digest,支持多架构 Docker 1.10+
OCI Image Manifest 最新标准 与 Schema 2 兼容,更通用 containerd、Podman 等
Manifest List (Fat Manifest) 多架构 包含多个平台的 Manifest 引用 跨平台镜像(如 nginx:latest)

以 nginx:latest 为例,它返回的是一个 Manifest List。里面列出了 amd64、arm64、s390x 等平台的 Manifest。Docker 客户端会根据当前系统的架构,选择对应的子 Manifest 去拉取。

子 Manifest 的内容大致如下:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 7023,
    "digest": "sha256:abc123..."
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 28112345,
      "digest": "sha256:def456..."
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 5123456,
      "digest": "sha256:ghi789..."
    }
  ]
}
注意这里的 mediaType:它告诉客户端层的压缩格式。常见的有 .tar.gzip(gzip 压缩)、.tar.zstd(zstd 压缩,更新更快)。Docker 20.10+ 支持 zstd,拉取速度能提升 30% 左右。

4.5 第四步:层列表解析与本地缓存比对

拿到 Manifest 后,Docker Daemon 会解析出所有层的 digest。然后它做一件很重要的事——检查本地是否已经存在这些层。

检查路径是 /var/lib/docker/overlay2/<layer_id>/(overlay2 驱动下)。每个层对应一个目录,目录名就是 digest 的截断哈希。如果本地已经存在,就跳过下载。

这一步就是"增量拉取"的核心。你想想看,如果你本地已经有 ubuntu:22.04 的基础层,再拉 nginx:latest 时只需要下载 nginx 自己的那几层,基础层直接复用。这就是为什么 Docker 镜像拉取第二次往往比第一次快得多。

避坑指南:我曾经遇到过一个诡异的问题——拉取同一个镜像,在不同机器上拉到的层数量不一样。后来发现是 Registry 端做了层合并优化。有些 Registry 实现会把多个小层合并成一个层返回。所以不要假设 Manifest 里的层列表是固定不变的。

4.6 第五步:逐层下载

这是最耗时的阶段。Docker 会为每个需要下载的层发起一个 GET 请求:

GET /v2/library/nginx/blobs/sha256:def456...
Authorization: Bearer <token>
Accept: application/vnd.docker.distribution.manifest.v2+json

Registry 返回的响应头里包含:

  • Content-Type: application/octet-stream
  • Content-Length: 28112345(层的大小)
  • Docker-Content-Digest: sha256:def456...(用于校验)

下载过程中,Docker 客户端会做几件事:

  1. 流式写入临时文件:数据不落盘到最终位置,先写到 /var/lib/docker/tmp/ 下的临时文件
  2. 实时计算 sha256:边下载边计算 digest,下载完成后比对
  3. 断点续传:如果连接中断,下次请求带上 Range 头,只下载未完成的部分
  4. 并发下载:Docker 默认同时下载 3 个层(可配置),充分利用带宽
性能陷阱:并发下载不是越多越好。我见过有人把并发数调到 32,结果 Registry 端限流,反而比默认的 3 并发更慢。建议根据 Registry 的负载能力和网络延迟来调整。一般 5-8 并发是比较合理的值。

4.7 第六步:解包与组装

所有层下载完成并校验通过后,Docker 开始解包组装。这一步在本地完成,不涉及网络。

具体流程:

  1. 把每个层的 .tar.gzip 文件解压到对应的 overlay2 层目录
  2. 按照 Manifest 中的层顺序,从下往上叠加
  3. 创建容器的读写层(diff 目录)
  4. 写入镜像的元数据(配置、历史等)到 /var/lib/docker/image/overlay2/

组装完成后,你执行 docker images 就能看到新镜像了。整个拉取流程结束。

4.8 完整时序图

最后,我用一个时序图把整个流程串起来,方便你对照理解:

docker pull 时序图 Docker CLI Docker Daemon Registry Token 服务 docker pull nginx:latest GET /v2/.../manifests/latest 401 + Www-Authenticate 请求 Token 返回 Bearer Token GET /v2/.../manifests/latest (带 Token) 返回 Manifest List 解析 Manifest,比对本地缓存 GET /v2/.../blobs/<digest> (逐层) 返回层数据(流式) 校验 digest,解包组装 返回拉取成功

整个流程走下来,你会发现 Docker 的镜像拉取设计得非常精巧。它把认证、元数据获取、数据传输、本地校验拆成了独立的阶段,每个阶段都可以独立优化和容错。理解了这套机制,你在排查拉取慢、失败、认证问题时,就能快速定位到具体是哪个环节出了问题。

一句话总结:docker pull 的本质是一个基于 HTTP 协议、分步协商、逐层校验、支持断点续传和增量复用的分布式内容传输系统。