Moby(Docker) Remote API v1.8 全解析:容器生命周期端点、镜像管理与 hijack 流式协议
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
本篇以 api/docs/v1.8.md 记载的 Remote API v1.8 为蓝本,完整梳理其容器与镜像端点、查询/JSON 参数、状态码语义,以及 attach 场景中经典的 8 字节头多路复用流协议;并结合当前仓库中 api/pkg/stdcopy/stdcopy.go、client/hijack.go 与 容器路由 等源码,还原这套 API 在 Moby 守护进程中的真实实现,帮助读者既掌握 v1.8 时代的 API 全貌,也理解它延续至今的底层协议机制。
1. Remote API 是什么:取代 rcli 的 HTTP 接口
v1.8 文档开篇给出了三条核心定位,这也是理解整份规范的前提:
- Remote API 取代了 rcli:在此之前,客户端通过私有协议与守护进程通信,v1.8 开始统一为基于 HTTP 的 REST 风格接口;
- 默认监听 Unix Socket:守护进程默认监听
unix:///var/run/docker.sock,也可以绑定到任意 host/port 或另一个 Unix socket 上; - REST 为主,hijack 为辅:绝大多数端点是标准 REST 请求,但对
attach、pull这类需要传输stdin、stdout、stderr的复杂命令,HTTP 连接会被“劫持(hijack)”,在同一连接上直接透传原始字节流。
当前仓库中,api/目录仍然是这套 Engine API 的归属地。api/README.md 明确写道:“The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.” 从 v1.8 的 md 文档,到 api/docs/CHANGELOG.md 记载的版本演进,再到 v1.25 之后以 YAML 形式维护、由 api/swagger.yaml 生成文档的新规范(见 api/docs/v1.55.yaml),v1.8 正是这条演进线的早期里程碑。
2. 容器端点(/containers/...)
2.1 列出容器GET /containers/json
请求支持五个查询参数,示例请求为:
GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1| 参数 | 说明 |
|---|---|
| all | 1/True/true或0/False/false。显示全部容器;默认只显示运行中的容器(默认 false) |
| limit | 只显示最近创建的limit个容器,包含非运行状态的 |
| since | 只显示 Id 之后创建的容器,包含非运行状态的 |
| before | 只显示 Id 之前创建的容器,包含非运行状态的 |
| size | 1/True/true或0/False/false。显示容器占用大小 |
响应为 JSON 数组,每个元素包含Id、Image、Command、Created(Unix 时间戳)、Status、Ports(PrivatePort/PublicPort/Type)、以及SizeRw、SizeRootFs两个尺寸字段:
[ { "Id": "8dfafdbc3a40", "Image": "base:latest", "Command": "echo 1", "Created": 1367854155, "Status": "Exit 0", "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], "SizeRw": 12288, "SizeRootFs": 0 }, { "Id": "9cd87474be90", "Image": "base:latest", "Command": "echo 222222", "Created": 1367854155, "Status": "Exit 0", "Ports": [], "SizeRw": 12288, "SizeRootFs": 0 } ]状态码:200无错误;400参数错误;500服务端错误。
2.2 创建容器POST /containers/create
v1.8 的创建接口把所有配置(容器级配置)都放在请求体 JSON 中,示例:
POST /containers/create HTTP/1.1 Content-Type: application/json { "Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "CpuShares":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Dns":null, "Image":"base", "Volumes":{"/tmp": {}}, "VolumesFrom":"", "WorkingDir":"", "ExposedPorts":{"22/tcp": {}} }请求体 JSON 参数:
| 参数 | 说明 |
|---|---|
| Hostname | 容器主机名 |
| User | 用户名或 UID |
| Memory | 内存限制(字节) |
| CpuShares | CPU 权重(相对权重) |
| AttachStdin | 是否附加标准输入,默认 false |
| AttachStdout | 是否附加标准输出,默认 false |
| AttachStderr | 是否附加标准错误,默认 false |
| Tty | 是否分配伪终端(pseudo-tty),默认 false |
| OpenStdin | 即使未附加也保持 stdin 打开,默认 false |
查询参数:name—— 为容器指定名称,必须匹配/?[a-zA-Z0-9_-]+。
成功时返回201 Created并给出容器短 Id 与告警:
HTTP/1.1 201 Created Content-Type: application/json { "Id":"e90e34656806", "Warnings":[] }状态码:201无错误;404容器不存在;406无法附加(容器未运行);500服务端错误。
2.3 检查容器GET /containers/(id)/json
返回容器的底层详细信息,包括完整 Id、创建时间、入口Path/Args、创建时的Config快照、State(Running、Pid、ExitCode、StartedAt、Ghost)、镜像 Id、NetworkSettings(IpAddress、IpPrefixLen、Gateway、Bridge、PortMapping)以及宿主侧的HostConfig(Binds、PortBindings、Links、Privileged等)。文档示例响应节选:
{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Created": "2013-05-07T14:51:42.041847+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 0, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Tty": false, "Cmd": ["date"], "Image": "base", "Volumes": {}, "VolumesFrom": "", "WorkingDir": "" }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:00", "Ghost": false }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LxcConf": [], "Privileged": false, "PortBindings": { "80/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49153" } ] }, "Links": null, "PublishAllPorts": false } }状态码:200无错误;404无此容器;500服务端错误。
2.4 观察容器:top、changes、export
列出容器内进程GET /containers/(id)/top:查询参数ps_args指定 ps 参数(如aux)。响应含Titles(列头)与Processes(二维数组):
{ "Titles": ["USER","PID","%CPU","%MEM","VSZ","RSS","TTY","STAT","START","TIME","COMMAND"], "Processes": [ ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] ] }查看文件系统变更GET /containers/(id)/changes:返回Path与Kind组成的数组(Kind为变更类型编码,如示例中 0/1):
[ { "Path": "/dev", "Kind": 0 }, { "Path": "/dev/kmsg", "Kind": 1 }, { "Path": "/test", "Kind": 1 } ]导出容器GET /containers/(id)/export:返回application/octet-stream的 TAR 流。三个端点的状态码一致:200 / 404(无此容器)/ 500。
2.5 生命周期控制:start / stop / restart / kill
值得注意的是,v1.8 时代的start 接口还承载了宿主侧配置——POST /containers/(id)/start的请求体可以包含Binds、LxcConf、PortBindings、PublishAllPorts、Privileged等字段:
POST /containers/(id)/start HTTP/1.1 Content-Type: application/json { "Binds":["/tmp:/tmp"], "LxcConf":[{"Key":"lxc.utsname","Value":"docker"}], "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts":false, "Privileged":false }各字段语义:Binds创建绑定挂载host-path:container-path:rw|ro,容器路径不存在则新建卷;LxcConf自定义 lxc 选项键值对;PortBindings暴露端口并可指定宿主端口;PublishAllPorts发布所有暴露端口,默认 false;Privileged授予扩展权限,默认 false。成功返回204 No Content。
其余三个控制端点形态类似:
- Stop
POST /containers/(id)/stop:查询参数t为等待秒数,超时后强制 kill,例如POST /containers/e90e34656806/stop?t=5; - Restart
POST /containers/(id)/restart:同样支持t参数; - Kill
POST /containers/(id)/kill:直接终止容器。
三者均返回204(成功)/** 404**(无此容器)/** 500**(服务端错误)。
2.6 附加到容器POST /containers/(id)/attach与多路复用流协议
这是 v1.8 中最具协议深度的端点。示例请求与响应:
POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 HTTP/1.1 200 OK Content-Type: application/vnd.docker.raw-stream {{ STREAM }}查询参数:
| 参数 | 说明 |
|---|---|
| logs | 返回日志,默认 false |
| stream | 返回实时流,默认 false |
| stdin | stream=true时附加 stdin,默认 false |
| stdout | logs=true时返回 stdout 日志;stream=true时附加 stdout,默认 false |
| stderr | 同 stdout,但作用于 stderr,默认 false |
状态码:200无错误;400参数错误;404无此容器;500服务端错误。
流帧格式(Stream details):若创建容器时启用了 TTY,流就是进程 PTY 的原始数据;若未启用 TTY,流会把 stdout 与 stderr多路复用为带帧头的字节流。帧 =HEADER+PAYLOAD,头部固定 8 字节:
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}- 第 1 字节
STREAM_TYPE:0= stdin(读取时写到 stdout)、1= stdout、2= stderr; - 第 5~8 字节为 payload 大小,
uint32大端编码; - payload 为原始流数据。
文档给出的最简实现步骤:1. 读 8 字节头;2. 按第 1 字节判断 stdout/stderr;3. 从后 4 字节解出帧大小;4. 读取该大小的数据并写到对应输出;5. 回到第 1 步。
这套协议至今仍在仓库中生效。api/pkg/stdcopy/stdcopy.go 中的StdCopy就是该协议的去复用实现:stdWriterPrefixLen = 8、stdWriterFdIndex = 0、stdWriterSizeIndex = 4,用binary.BigEndian.Uint32读取帧大小,并把Stdin类型(0)与Stdout一样写到 stdout——与 v1.8 文档逐字对应。当前实现还在此基础上增加了Systemerr = 3类型用于传递守护进程侧错误,但 v1.8 文档描述的 0/1/2 三种类型完全保留。
服务端对应实现见 daemon/server/router/container/container_routes.go 的postContainersAttach:它断言http.ResponseWriter实现http.Hijacker,调用hijacker.Hijack()拿到裸连接后,自行写出HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream响应头——正是文档中application/vnd.docker.raw-stream这一内容类型的来源。
2.7 WebSocket 版本GET /containers/(id)/attach/ws
v1.8 同时提供了 WebSocket 变体,按 RFC 6455 握手,示例:
GET /containers/e90e34656806/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1查询参数与状态码与 2.6 的 attach 完全一致(200 / 400 / 404 / 500)。在当前仓库中,该路由仍注册于 daemon/server/router/container/container.go(/containers/{name:.*}/attach/ws),与 POST 版 attach 并列存在。
2.8 等待、删除与复制
- Wait
POST /containers/(id)/wait:阻塞直到容器停止,返回退出码{"StatusCode": 0}。状态码 200/404/500。 - Remove
DELETE /containers/(id):查询参数v(1/True/true或0/False/false,默认 false)为是否同时删除关联卷。状态码204成功;400参数错误;404无此容器;500服务端错误。 - Copy
POST /containers/(id)/copy:v1.8 采用 POST 请求体形式指定资源,返回 TAR 流:
POST /containers/4fa6e0f0c678/copy HTTP/1.1 Content-Type: application/json { "Resource": "test.txt" }状态码 200/404/500。
3. 镜像端点(/images/...)
3.1 列出镜像GET /images/json
示例GET /images/json?all=0,响应元素含RepoTags、Id、Created、Size、VirtualSize,带ParentId的条目表示上层镜像:
[ { "RepoTags": ["ubuntu:12.04","ubuntu:precise","ubuntu:latest"], "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", "Created": 1365714795, "Size": 131506275, "VirtualSize": 131506275 }, { "RepoTags": ["ubuntu:12.10","ubuntu:quantal"], "ParentId": "27cf784147099545", "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "Created": 1364102658, "Size": 24653, "VirtualSize": 180116135 } ]3.2 创建镜像POST /images/create(pull / import 二合一)
该端点既可从 registry 拉取,也可从源导入。查询参数:
| 参数 | 说明 |
|---|---|
| fromImage | 要拉取的镜像名 |
| fromSrc | 导入来源,-表示 stdin |
| repo | 仓库名 |
| tag | 标签 |
| registry | 要拉取的 registry |
请求头:X-Registry-Auth—— base64 编码的 AuthConfig 对象,用于私有仓库认证。
响应是一个进度消息流(JSON 行序列),例如:
{"status": "Pulling..."} {"status": "Pulling", "progress": "1 B/ 100 B", "progressDetail": {"current": 1, "total": 100}} {"error": "Invalid..."}状态码200成功、500服务端错误。这套status/progress/error的流式消息格式在后续版本中被stream/id/aux等字段取代,但其“逐行 JSON 汇报进度”的思想一以贯之。
3.3 向镜像插入文件POST /images/(name)/insert
把url处的文件插入镜像name的path位置,例如POST /images/test/insert?path=/usr&url=myurl,响应同样是带progress/progressDetail的进度流。状态码 200/500。(该端点在后续 API 版本中被移除,属于 v1.8 时代特有的端点。)
3.4 检查、历史、推送、打标签、删除、搜索
- Inspect
GET /images/(name)/json:返回id、parent、created、container、container_config(创建该层所用容器的完整配置快照,含Cmd、Tty、OpenStdin等)与Size。状态码 200/404/500。 - History
GET /images/(name)/history:返回各层Id、Created、CreatedBy(如/bin/bash)。状态码 200/404/500。 - Push
POST /images/(name)/push:请求头携带 base64 编码的X-Registry-Auth,响应为 Pushing 进度流。状态码 200/404/500。 - Tag
POST /images/(name)/tag:查询参数repo(目标仓库)、force(默认 false)、tag(新标签名),例如POST /images/test/tag?repo=myrepo&force=0&tag=v42。状态码201成功;400参数错误;404无此镜像;409冲突;500服务端错误。 - Remove
DELETE /images/(name):返回Untagged/Deleted事件数组:
[ {"Untagged": "3e2f21a89f"}, {"Deleted": "3e2f21a89f"}, {"Deleted": "53b4f83ac9"} ]状态码 200/404/409/500。
- Search
GET /images/search:查询参数term,在 Docker Hub 上搜索。文档特别注明:响应键名在 v1.6 之后已变更,改为直接透传 registry 服务器返回的 JSON(description、is_official、is_trusted、name、star_count),状态码 200/500。
4. 其他端点(Misc)
4.1 从 Dockerfile 构建POST /build
请求体是tar 压缩包流,支持 identity(不压缩)、gzip、bzip2、xz 四种算法;归档根目录必须包含名为Dockerfile的文件,可附带任意构建上下文文件(供ADD指令引用)。响应为构建进度流:
{"stream": "Step 1..."} {"stream": "..."} {"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}}查询参数:t(成功时应用到结果镜像的仓库名和可选标签)、remote(构建源 URI,git 或 HTTPS/HTTP)、q(静默输出)、nocache(不使用构建缓存)。请求头:Content-type应为"application/tar";X-Registry-Auth为 base64 编码的 AuthConfig。状态码 200/500。
4.2 认证校验POST /auth
提交username/password/email/serveraddress四字段校验凭据,成功返回 200 或 204(无响应体),500 为服务端错误。
4.3 系统信息GET /info与版本GET /version
/info返回守护进程全局状态,v1.8 示例字段:
{ "Containers":11, "Images":16, "Debug":false, "NFd": 11, "NGoroutines":21, "MemoryLimit":true, "SwapLimit":false, "IPv4Forwarding":true }/version返回Version、GitCommit、GoVersion。两者状态码均为 200/500。
4.4 提交镜像POST /commit
把容器变更固化为新镜像,查询参数:container(源容器)、repo、tag、m(提交说明)、author、run(运行期自动应用的配置,如{"Cmd": ["cat", "/world"], "PortSpecs":["22"]})。示例:
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 HTTP/1.1 201 OK Content-Type: application/vnd.docker.raw-stream {"Id": "596069db4bf5"}状态码 201/404/500。
4.5 事件流GET /events
以流式或轮询(since时间戳)方式获取事件。v1.8 中容器报告create, destroy, die, export, kill, pause, restart, start, stop, unpause,镜像报告untag, delete:
{"status": "create", "id": "dfdf82bd3881","from": "base:latest", "time":1374067924} {"status": "start", "id": "dfdf82bd3881","from": "base:latest", "time":1374067924} {"status": "stop", "id": "dfdf82bd3881","from": "base:latest", "time":1374067966} {"status": "destroy", "id": "dfdf82bd3881","from": "base:latest", "time":1374067970}查询参数since用于轮询时间戳。状态码 200/500。
4.6 镜像 tarball:GET /images/(name)/get、POST /images/load与格式定义
get导出仓库全部镜像与标签的 tarball(application/x-tar二进制流);load把 tarball 载入本地仓库,两者互为逆操作。文档明确了image tarball 格式:
- 每个镜像层一个以长 Id 命名的目录,内含三个文件:
VERSION:文件格式版本,当前为1.0;json:层的详细信息,类似docker inspect layer_id的输出;layer.tar:该层的文件系统变更 tar 包,其中用 aufs 风格的.wh..wh.aufs文件/目录记录属性变更与删除;
- 若 tarball 定义了仓库,根目录还会有
repositories文件,将仓库/标签名映射到层 Id:
{"hello-world": {"latest": "565a9d68a73f6706862bfe8409a7f659776d4d60a8d096eb4a3cbce6999cc2a1"} }状态码均为 200/500。
5.docker run的底层调用序列
文档 “Going further” 一节给出了 v1.8 时代客户端如何组合上述端点实现docker run,这也是理解 API 组装关系的最佳范例:
- 创建容器(
POST /containers/create); - 若返回404,说明镜像不存在——先执行pull(
POST /images/create),再重试创建容器; - 启动容器(
POST /containers/(id)/start); - 非分离模式下:以
logs=1&stream=1附加到容器(POST /containers/(id)/attach),从而拿到容器启动以来的 stdout/stderr; - 分离模式(或仅附加 stdin 时):直接打印容器 Id。
6. Hijack 机制与 CORS:v1.8 的两条特殊通道
Hijacking。v1.8 明确写道:/attach使用 hijacking 在同一 socket 上传输 stdin、stdout、stderr,且“未来可能改变”。客户端侧的实现可见 client/hijack.go:setupHijackConn在请求中设置Connection: Upgrade与Upgrade头、对 TCP 连接开启 30 秒 KeepAlive(避免长静默命令触发 ECONNTIMEOUT),随后等待服务端的101 Switching Protocols响应拿到裸连接;client/container_attach.go 等文件则基于postHijacked发起 attach/exec 请求。服务端侧则如 2.6 节所述,由路由层完成Hijacker.Hijack()并手写响应头。这一“客户端发起 Upgrade、服务端握手后接管连接”的对称设计,正是 v1.8 文档中 hijack 描述的具体落地。
CORS Requests。v1.8 通过守护进程参数开启跨域访问,示例:
$ docker -d -H="192.168.1.9:2375" --api-enable-cors即在 daemon 模式下附加--api-enable-cors标志,把远程 API 暴露到192.168.1.9:2375这类 TCP 端点。从当前仓库的守护进程参数定义(如 daemon/config 包)看,早期这类docker -d -H的顶层参数已被-H/--host等 dockerd 启动选项取代,但“API 默认走 Unix socket、TCP 暴露需显式绑定”这一安全基线始终未变。
7. 如何阅读这份历史文档与当前代码
api/docs/v1.8.md 是 api/docs/ 目录中按 API 版本归档的历史规范之一,与 api/docs/CHANGELOG.md 构成版本演进索引。阅读它时有三点实用结论:
- 端点路径基本稳定:
/containers/json、/containers/{id}/start|stop|attach、/images/json、/build、/events等路径从 v1.8 一直沿用至今,在 api/docs/v1.55.yaml 等新版规范与 api/swagger.yaml 中均可找到同名路径; - 请求体结构发生了大迁移:v1.8 中 start 携带的
Binds/PortBindings等宿主配置,在后续版本被拆分到独立的HostConfig,create 接口的请求体也随之重构——对照 api/types/container 包中的HostConfig类型即可看到演进结果; - 流式协议原封保留:8 字节帧头(
STREAM_TYPE+ 3 字节保留 + 大端 uint32 长度)与application/vnd.docker.raw-stream媒体类型,从 v1.8 文档、到 api/pkg/stdcopy/stdcopy.go 的StdCopy/NewStdWriter、再到 daemon/server/router/container/container_routes.go 的 attach 处理函数,是完全一致的三代证据链。
综上,掌握 v1.8 这份文档不仅是在读一段 API 历史:它把容器端点的参数语义、镜像 tarball 的二进制格式、以及贯穿至今的 hijack + stdcopy 流协议一次性讲透,是理解 Moby Engine API 设计基因的最短路径。
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考