bigywater.tech
Docker · 20

Registry v2 API:一次 docker pull 发出了哪些 HTTP 请求

docker pull 到底向 registry 发了什么?这篇在本机起一个 registry,直接读它的访问日志,把 push 和 pull 的每一个 HTTP 请求列出来,再亲手用 HTTP 上传一个镜像、读 manifest 和 blob。

14 个请求
docker push 一个 3 层的镜像:4 次 HEAD blob、4 次 POST、4 次 PUT blob、HEAD 和 PUT manifest 各一次
1 个请求
本机还留着层内容时,docker pull 只发了一个 HEAD manifests/<tag>
4 个请求
从零拉取:HEAD manifest、GET manifest、GET 两个 blob

入口 /v2/

实测 · 本机执行输出
  GET /v2/ → 200;响应头 Docker-Distribution-Api-Version = registry/2.0;正文 = {}

registry:2 是一个普通的 HTTP 服务。GET /v2/ 返回 200 和响应头 Docker-Distribution-Api-Version: registry/2.0。我是在本机的 registry:2 容器上做的实验,不同的 registry 实现(Docker Hub、Harbor 等)的细节可能不同,没有测。

push 的顺序

实测 · 本机执行输出
  push 退出码 0;共 14 个请求,按首次出现顺序:
    HEAD  /v2/c15/app/blobs/sha256:7be9bb…  → 404
    HEAD  /v2/c15/app/blobs/sha256:db95e1…  → 404
    HEAD  /v2/c15/app/blobs/sha256:3f26bc…  → 404
    HEAD  /v2/c15/app/blobs/sha256:db4141…  → 404
    POST  /v2/c15/app/blobs/uploads/  → 202  (共 4 次)
    PUT   /v2/c15/app/blobs/uploads/<uuid>  → 201  (共 4 次)
    HEAD  /v2/c15/app/manifests/1  → 404
    PUT   /v2/c15/app/manifests/1  → 201

先对每个 blob 发 HEAD,404 就说明 registry 里没有;再 POST 开启上传、PUT 写入;所有 blob 到位后,最后才 PUT manifest。顺序保证了 manifest 引用到的内容在它出现时一定已经存在。请求次数来自 registry 的访问日志。

本地有层时的 pull

实测 · 本机执行输出
  pull 退出码 0;输出里出现 Already exists 的行数 2;共 1 个请求:
    HEAD  /v2/c15/app/manifests/1  → 200
  镜像已经是最新时再 pull 一次:1 个请求:
    HEAD  /v2/c15/app/manifests/1  → 200

删掉镜像的名字后重新 pull,registry 只收到一个 HEAD manifests/1,docker 输出 2 行 Already exists——本机还留着内容。别用这种方式去估算“拉一个镜像要多少请求”。

从零拉取

实测 · 本机执行输出
  手工上传:层 119 字节(上传返回 201),config 194 字节(返回 201),manifest 418 字节(返回 201)
  docker pull 退出码 0;输出里 Already exists 的行数 0;共 4 个请求:
    HEAD  /v2/c15/hand/manifests/v1  → 200
    GET   /v2/c15/hand/manifests/sha256:596dee…  → 200
    GET   /v2/c15/hand/blobs/sha256:0046d1…  → 200
    GET   /v2/c15/hand/blobs/sha256:de1bc8…  → 200
  拉下来的镜像:docker image inspect 的层数 = 1,diff_id 与我写进 config 的一致吗?True

要看完整的 pull,就得拉本机从没有过的内容。我用 Python 手工往 registry 上传一个只有一层的镜像(用到的正是 c14 讲的结构:config、layer 两个 blob 加一个 manifest),再 docker pull:HEAD manifest(按 tag)、GET manifest(按摘要)、GET config blob、GET layer blob,共 4 个请求。

自己读 registry

实测 · 本机执行输出
  GET /v2/_catalog → {"repositories":["c15/app","c15/hand"]}
  GET /v2/c15/app/tags/list → {"name":"c15/app","tags":["1"]}
  GET manifests/1(Accept: OCI manifest)→ 200;Content-Type = application/vnd.oci.image.manifest.v1+json;正文 856 字节;Docker-Content-Digest = sha256:86a95bc35fcb…;自己对正文算 sha256 一致吗?True
  不带 Accept 头再请求同一个 tag → 404;Content-Type = application/json; charset=utf-8;正文是同一份吗?False
  只接受 Docker schema2 manifest 类型(Accept: application/vnd.docker.distribution.manifest.v2+json)→ 404;Content-Type = application/json; charset=utf-8
  HEAD manifests/<digest> → 200;Content-Length = 856
  GET blobs/<config digest> → 200;1038 字节;sha256 与 digest 一致吗?True
  GET manifests/nope → 404;正文 = {"errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown","detail":{"Tag":"nope"}}]}
  GET blobs/<不存在的摘要> → 404

_catalog、tags/list 返回 JSON;manifest 的响应头 Docker-Content-Digest 就是正文的 sha256,blob 同理,客户端可以自己校验。这个 registry 在 Accept 头不包含 OCI manifest 类型时返回 404 和一份 JSON(正文我没有记录),不是 406,所以自己写客户端要带对 Accept。

分步演示

Registry v2 API
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。registry 用 registry:2(经 dpull.py 下载后 docker load),运行在本机 127.0.0.1:9700,所有请求来自它自己的访问日志(路径里的 uuid 和摘要缩写显示);HTTP 客户端用 Python urllib;镜像和容器在实验后已删除
    • 没有验证:带认证的 registry(token 流程、401 与 WWW-Authenticate)、Docker Hub 的行为、分块上传(PATCH)、跨仓库 blob 挂载(mount)、并发拉取多个层的顺序、HTTPS 与证书、registry 的 GC
    京ICP备2026067502号-1京公网安备11010502063487号