docker 命令背后到底发了什么?Engine API 是什么样的?这篇不用 docker 命令,直接用 Python 对 Docker 的 unix socket 发 HTTP,完整地走一遍容器的生命周期,并用一个代理看 docker run 在背后发出的请求。
GET /_ping → 200;正文 'OK';响应头 Api-Version = 1.56,Docker-Experimental = false,Ostype = linux GET /version → 200;Version=29.8.2 ApiVersion=1.56 MinAPIVersion=1.40 Os=linux Arch=arm64 GoVersion=go1.26.8 GET /info → 200;59 个顶层字段;NCPU=10 Containers=8 Driver=overlayfs CgroupVersion=2
Docker 守护进程监听一个 unix socket(Mac 上是 ~/.docker/run/docker.sock),说的就是普通 HTTP/1.1 加 JSON。/_ping 探测版本,/version 和 /info 返回守护进程信息。
POST /containers/create → 201;返回 Id = 7dda27b7871e…(共 64 个字符);Warnings = []
POST /containers/<id>/start → 204(无正文:0 字节)
POST /containers/<id>/wait → 200;{"StatusCode":0}
GET /containers/<id>/logs → 200;Content-Type = application/vnd.docker.multiplexed-stream;正文 44 字节;前 16 字节(十六进制):01 00 00 00 00 00 00 0f 68 65 6c 6c 6f 2d 66 72
按 8 字节帧头拆开:[('stdout', 'hello-from-api'), ('stderr', 'err-from-api')]
GET /containers/<id>/json → 200;State.Status=exited ExitCode=0
DELETE /containers/<id> → 204
再 GET → 404;{'message': 'No such container: 7dda27b7871eed1966aee73a6dfcc1575b6fdd306bd27b4c23f5e3198a287764'}创建(POST create,JSON 描述容器)、启动(POST start 返回 204)、等待、读日志、删除,都是单独的接口。日志接口返回多路复用流:每帧 8 字节头(类型 1 是 stdout、2 是 stderr,后 4 字节是大端长度)加数据,没有 TTY 时用这种格式区分两个流。
GET /v1.40/containers/json → 200;返回 8 个容器
GET /v1.24/containers/json → 400;{"message":"client version 1.24 is too old. Minimum supported API version is 1.40, please upgrade your client
GET /v1.12/containers/json → 400;client version 1.12 is too old. Minimum supported API version is 1.40, please upgrade your client to a newer v
GET /v1.54/containers/json → 200;返回 8 个容器
GET /v9.99/containers/json → 400;{"message":"client version 9.99 is too new. Maximum supported API version is 1.56"}API 版本写在路径前缀 /v1.xx/;服务端支持一个范围,超出就 400 并说明范围。不带版本前缀会怎样,我没有测。
docker -H unix://<代理> run --rm alpine echo via-proxy:输出 'via-proxy'
代理记录到 3 个连接、共 6 个请求(按连接分组列出,不严格按时间):
HEAD /_ping HTTP/1.1
HEAD /_ping HTTP/1.1
POST /v1.56/containers/create HTTP/1.1
POST /v1.56/containers/<64位ID>/wait?condition=removed HTTP/1.1
POST /v1.56/containers/<64位ID>/attach?stderr=1&stdout=1&stream=1 HTTP/1.1
POST /v1.56/containers/<64位ID>/start HTTP/1.1docker run 在 API 层面就是 create + attach + start,加上 wait?condition=removed 等待 --rm 的容器被删除;它也先用 HEAD /_ping 探测。按我的理解,能访问这个 socket 就等于能控制整个 Docker,所以不要随便挂载到容器里(本文没有演示)。