4、镜像拉取流程详解:从客户端发起 docker pull 到服务端响应,完整追踪镜像下载的每一步
说实话,很多人用 docker pull 就跟点外卖一样——下单、等餐、开吃,中间厨房怎么运作的完全不关心。但镜像拉取这个流程,恰恰是理解容器运行时最关键的入口。我在生产环境排查过不少拉取慢、失败、认证过期的问题,每次都得把这条链路从头到尾捋一遍。
今天我们就完整走一遍:从你在终端敲下 docker pull nginx:latest 开始,到本地磁盘上出现完整的镜像层数据,中间到底发生了什么。
4.1 整体流程概览
先看一张我画的流程图,把整个拉取过程拆成 6 个阶段。你对照着看后面的细节,会清晰很多。
4.2 第一步:解析镜像名
你敲下 docker pull nginx:latest,Docker CLI 第一件事就是解析这个字符串。别小看这一步,镜像名的格式其实藏着不少信息。
完整的镜像名格式是:[registry-host[:port]/][namespace/]repository[:tag|@digest]。举个例子:
nginx:latest→ 默认走 Docker Hub,namespace 为 library,tag 为 latestmyregistry.com:5000/team/app:v1.2→ 指定私有仓库、端口、命名空间和 tagalpine@sha256:abc123...→ 用 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 后,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..."
}
]
}
.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 镜像拉取第二次往往比第一次快得多。
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-streamContent-Length: 28112345(层的大小)Docker-Content-Digest: sha256:def456...(用于校验)
下载过程中,Docker 客户端会做几件事:
- 流式写入临时文件:数据不落盘到最终位置,先写到
/var/lib/docker/tmp/下的临时文件 - 实时计算 sha256:边下载边计算 digest,下载完成后比对
- 断点续传:如果连接中断,下次请求带上
Range头,只下载未完成的部分 - 并发下载:Docker 默认同时下载 3 个层(可配置),充分利用带宽
4.7 第六步:解包与组装
所有层下载完成并校验通过后,Docker 开始解包组装。这一步在本地完成,不涉及网络。
具体流程:
- 把每个层的
.tar.gzip文件解压到对应的 overlay2 层目录 - 按照 Manifest 中的层顺序,从下往上叠加
- 创建容器的读写层(
diff目录) - 写入镜像的元数据(配置、历史等)到
/var/lib/docker/image/overlay2/
组装完成后,你执行 docker images 就能看到新镜像了。整个拉取流程结束。
4.8 完整时序图
最后,我用一个时序图把整个流程串起来,方便你对照理解:
整个流程走下来,你会发现 Docker 的镜像拉取设计得非常精巧。它把认证、元数据获取、数据传输、本地校验拆成了独立的阶段,每个阶段都可以独立优化和容错。理解了这套机制,你在排查拉取慢、失败、认证问题时,就能快速定位到具体是哪个环节出了问题。