Moby Remote API v1.7 规格详解:容器/镜像端点全解与 Attach 多路复用流协议
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
本文以 Moby 仓库中恢复归档的历史 API 规范 api/docs/v1.7.md 为主体,完整梳理 Docker Remote API v1.7 的全部端点(容器、镜像、杂项三大类共 24+ 个接口)及其请求/响应示例、查询参数与状态码,并结合当前仓库源码佐证其中仍存活的底层机制:/var/run/docker.sock默认监听、attach 流的 8 字节帧头多路复用协议(对应 api/pkg/stdcopy)、HTTP 连接劫持(对应 client/hijack.go)与X-Registry-Auth头部编解码(对应 api/pkg/authconfig),帮助读者既读懂这份早期 REST API 规格,又看清哪些设计延续到了今天的 Moby。
需要说明的历史定位:该文档是 API v1.7(Docker 0.x 时代)的规范快照,通过提交 "api/docs: restore API versions v1.0 - v1.13" 恢复进仓库,作为历史版本存档。当前仓库的 Go 客户端最低只支持 API 1.40、最高 1.56(见 client/client.go 中的MinAPIVersion/MaxAPIVersion常量),因此 v1.7 接口不可被现代客户端直接调用,但其端点结构、状态码语义和流协议是理解整个 Docker API 演进的起点。
1. 总体设计:REST + 连接劫持
v1.7 文档开篇给出三条总体设计原则:
- Remote API 取代 rcli。rcli 是早期基于 RPC 的远程调用方案,Remote API 改用 HTTP 语义更通用的 REST 风格接口;
- 默认监听 Unix socket。守护进程默认监听
unix:///var/run/docker.sock,也可以绑定到其他 host/port 或另一个 Unix socket; - 倾向 REST,但允许劫持。对于
attach、pull这类需要双向传输stdin/stdout/stderr的复杂命令,HTTP 连接会被 hijack(劫持),后续流量不再受 HTTP 报文边界约束。
第一条原则在今天的仓库中依然成立:守护进程的默认 socket 路径定义于 daemon/pkg/opts/hosts.go 的DefaultUnixSocket = "/var/run/docker.sock",daemon/command/daemon.go 中的defaultAPISocketPath函数在 rootless(rootlessKit)场景下则改为监听$XDG_RUNTIME_DIR/docker.sock——这正是对文档"可以绑定到另一个 Unix socket"的落地。
第三条原则在 client/hijack.go 中能看到完整继承:postHijacked发起 POST 后调用setupHijackConn,后者设置Connection: Upgrade与Upgrade: <proto>请求头完成协议升级,等待服务端返回101 Switching Protocols,随后把裸连接连同响应头里的Content-Type一并交给上层——这个Content-Type决定流是原始流还是多路复用流,与 v1.7 attach 端点返回的application/vnd.docker.raw-stream语义一脉相承。
2. 容器端点(v1.7 共 15 个)
2.1 列出容器:GET /containers/json
示例请求:
GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1示例响应(Content-Type: application/json):
[ { "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 }, { "Id": "3176a2479c92", "Image": "base:latest", "Command": "echo 3333333333333333", "Created": 1367854154, "Status": "Exit 0", "Ports": [], "SizeRw": 12288, "SizeRootFs": 0 }, { "Id": "4cb07b47f9fb", "Image": "base:latest", "Command": "echo 444444444444444444444444444444444", "Created": 1367854152, "Status": "Exit 0", "Ports": [], "SizeRw": 12288, "SizeRootFs": 0 } ]查询参数:
| 参数 | 说明 |
|---|---|
all | 1/True/true或0/False/false,显示全部容器;默认只显示运行中的(默认 false) |
limit | 只显示最近创建的limit个容器,包括非运行中的 |
since | 只显示 Id 之后创建的容器,包括非运行中的 |
before | 只显示 Id 之前创建的容器,包括非运行中的 |
size | 1/True/true或0/False/false,显示容器大小(对应响应中的SizeRw/SizeRootFs字段) |
状态码:200成功;400参数错误;500服务端错误。
2.2 创建容器:POST /containers/create
示例请求:
POST /containers/create HTTP/1.1 Content-Type: application/json { "Hostname":"", "User":"", "Memory":0, "MemorySwap":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": {}} }示例响应:
HTTP/1.1 201 Created Content-Type: application/json { "Id":"e90e34656806" "Warnings":[] }请求体是"容器配置(config)"对象:Memory/MemorySwap是内存与内存+交换上限(0 表示不限制),AttachStdin/Stdout/Stderr决定 attach 时可用的流,Tty决定是否分配伪终端(该字段直接影响后文 attach 流是原始流还是多路复用流),ExposedPorts声明镜像级端口暴露。
状态码:201成功;404容器不存在;406无法 attach(容器未运行);500服务端错误。
对照现代 API:v1.7 把"创建配置"与"主机配置"拆在 create/start 两个端点里,这正是后来
ContainerCreateConfig+HostConfig分离结构的雏形。
2.3 检查容器:GET /containers/(id)/json
返回容器的低层信息。
示例请求:
GET /containers/4fa6e0f0c678/json HTTP/1.1示例响应(节选关键字段):
{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Created": "2013-05-07T14:51:42.041847+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 0, "MemorySwap": 0, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "PortSpecs": null, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": ["date"], "Dns": null, "Image": "base", "Volumes": {}, "VolumesFrom": "", "WorkingDir": "" }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:01360", "Ghost": false }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "SysInitPath": "/home/kitty/go/src/github.com/docker/docker/bin/docker", "ResolvConfPath": "/etc/resolv.conf", "Volumes": {} }响应结构是三层:Config(创建时的静态配置)、State(运行态:Running、Pid、ExitCode、StartedAt、Ghost)、NetworkSettings(早期单体网络模型下的 IP 前缀、网关、桥接与端口映射)。状态码:200成功;404容器不存在;500服务端错误。
2.4 列出容器内进程:GET /containers/(id)/top
示例请求:
GET /containers/4fa6e0f0c678/top HTTP/1.1示例响应:
{ "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"] ] }查询参数:ps_args—— 传给ps的参数(例如aux),Titles与Processes的每一行即按该格式对齐。状态码:200/404/500。
2.5 检查容器文件系统变更:GET /containers/(id)/changes
示例请求:
GET /containers/4fa6e0f0c678/changes HTTP/1.1示例响应:
[ {"Path": "/dev", "Kind": 0}, {"Path": "/dev/kmsg", "Kind": 1}, {"Path": "/test", "Kind": 1} ]Kind是变更类型编号(0/1/2 分别对应未变更/修改/删除的编码)。状态码:200/404/500。
2.6 导出容器:GET /containers/(id)/export
示例请求:
GET /containers/4fa6e0f0c678/export HTTP/1.1示例响应:HTTP/1.1 200 OK,Content-Type: application/octet-stream,响应体为{{ TAR STREAM }}(tar 数据流)。状态码:200/404/500。
2.7 生命周期控制:start / stop / restart / kill
启动POST /containers/(id)/start。与创建不同,start 可附带"主机配置(hostConfig)":
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" }] }, "Privileged":false, "PublishAllPorts":false }注意文档特别强调:Binds必须引用容器创建时已定义的 Volumes。响应为HTTP/1.1 204 No Content。
停止POST /containers/(id)/stop:
POST /containers/e90e34656806/stop?t=5 HTTP/1.1重启POST /containers/(id)/restart:
POST /containers/e90e34656806/restart?t=5 HTTP/1.1stop 与 restart 共享查询参数t—— 超时秒数,到时未退出则 kill。响应均为HTTP/1.1 204 No Content。
强制终止POST /containers/(id)/kill:
POST /containers/e90e34656806/kill HTTP/1.1响应HTTP/1.1 204 No Content。四个端点状态码一致:204成功;404容器不存在;500服务端错误。
2.8 附加到容器:POST /containers/(id)/attach(本版本最复杂的端点)
示例请求:
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 }}查询参数(均为1/True/true或0/False/false,默认 false):
| 参数 | 说明 |
|---|---|
logs | 返回日志 |
stream | 返回流 |
stdin | stream=true时附加到 stdin |
stdout | logs=true时返回 stdout 日志;stream=true时附加到 stdout |
stderr | logs=true时返回 stderr 日志;stream=true时附加到 stderr |
状态码:200成功;400参数错误;404容器不存在;500服务端错误。
流细节(本规范的核心算法,必须完整掌握):
- 创建容器时若启用了 TTY,流就是进程 PTY 与客户端 stdin 的原始数据(raw stream);
- 若未启用 TTY,流会做多路复用(multiplexed),把 stdout 与 stderr 交织在同一条字节流里,格式为帧头(Header)+ 载荷(Payload):
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}其中STREAM_TYPE:0= stdin(读取时写入 stdout)、1= stdout、2= stderr;SIZE1..SIZE4为 big-endian 编码的 uint32 帧长。文档给出的最简实现循环:
- 读 8 字节帧头;
- 根据第 1 个字节选择 stdout 或 stderr;
- 从后 4 字节解析帧大小;
- 读取该大小的载荷并输出到对应流;
- 回到第 1 步。
源码印证:这段 2013 年的协议描述与今天仓库中的 api/pkg/stdcopy/stdcopy.go 完全同构。该包定义Stdin=0、Stdout=1、Stderr=2三个流类型常量(并新增Systemerr=3用于守护进程侧错误),帧头长度stdWriterPrefixLen = 8、流类型位于偏移 0、帧长位于偏移 4 且按binary.BigEndian.Uint32解码;StdCopy函数实现的就是"读帧头 → 按第 1 字节分流 → 按后 4 字节读帧长 → 输出到 destOut/destErr"的循环。可以说 v1.7 文档里的手工解析步骤,就是现代 Go SDK 中stdcopy.StdCopy的算法原型,这套帧格式沿用至今未变。
2.9 通过 WebSocket 附加:GET /containers/(id)/attach/ws
GET /containers/e90e34656806/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1响应体直接是{{ STREAM }}。该端点按 RFC 6455 完成 WebSocket 握手,查询参数与状态码同上一节 attach。它是早期浏览器端交互(如当年的 Web UI)的通道,后续版本被POST .../attach+ 原生 WebSocket 升级取代。
2.10 等待容器退出:POST /containers/(id)/wait
阻塞直到容器id停止,然后返回退出码:
POST /containers/16253994b7c4/wait HTTP/1.1HTTP/1.1 200 OK Content-Type: application/json {"StatusCode": 0}状态码:200/404/500。
2.11 删除容器:DELETE /containers/(id)
DELETE /containers/16253994b7c4?v=1 HTTP/1.1查询参数:v——1/True/true或0/False/false,是否同时移除容器关联的卷,默认 false。响应HTTP/1.1 204 No Content。状态码:204成功;400参数错误;404容器不存在;500服务端错误。
2.12 从容器复制文件:POST /containers/(id)/copy
POST /containers/4fa6e0f0c678/copy HTTP/1.1 Content-Type: application/json { "Resource": "test.txt" }响应:HTTP/1.1 200 OK,Content-Type: application/octet-stream,响应体为{{ TAR STREAM }}(即后来docker cp与GET /containers/(id)/archive的前身)。状态码:200/404/500。
3. 镜像端点
3.1 列出镜像:GET /images/json
GET /images/json?all=0 HTTP/1.1[ { "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 } ]Size是镜像自身层大小,VirtualSize是含父层在内的总大小;RepoTags数组体现一个镜像 ID 可挂多个标签、无标签时RepoTags为空(即 dangling image)。
3.2 创建镜像(pull 或 import):POST /images/create
POST /images/create?fromImage=base HTTP/1.1响应是逐行的 JSON 进度流:
HTTP/1.1 200 OK Content-Type: application/json {"status":"Pulling..."} {"status":"Pulling", "progress":"1/? (n/a)"} {"error":"Invalid..."} ...从 registry 拉取时,可用X-Registry-Auth请求头携带 base64 编码的 AuthConfig 对象。
查询参数:
| 参数 | 说明 |
|---|---|
fromImage | 要拉取的镜像名 |
fromSrc | 要导入的来源,-表示 stdin |
repo | 目标仓库 |
tag | 目标标签 |
registry | 要拉取的 registry |
请求头:X-Registry-Auth—— base64 编码的 AuthConfig 对象。状态码:200成功;500服务端错误。
源码印证:这个请求头的编解码规则在今天的仓库中由 api/pkg/authconfig/authconfig.go 承担——Encode将registry.AuthConfig序列化为 base64url(RFC 4648 第 5 节)编码的 JSON 字符串用于X-Registry-Auth头;Decode反向解码且"即使出错也返回空 AuthConfig"的兼容性策略,注释明确写着是为了兼容旧客户端与旧 API 版本。v1.7 文档确立的"认证走请求头、正文走进度流"模式即为今日规范。
3.3 向镜像插入文件:POST /images/(name)/insert
从url取文件插入到镜像name的path路径:
POST /images/test/insert?path=/usr&url=myurl HTTP/1.1响应同样是逐行 JSON 进度流({"status":"Inserting..."}等)。查询参数:url(文件来源)、path(存储路径)。状态码:200/500。该端点是早期"不构建、直接改镜像"的能力,后由 Dockerfile + build 流程取代并从 API 中移除。
3.4 检查镜像:GET /images/(name)/json
GET /images/base/json HTTP/1.1{ "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "parent":"27cf784147099545", "created":"2013-03-23T22:24:18.818426-07:00", "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", "container_config": { "Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":false, "AttachStderr":false, "PortSpecs":null, "Tty":true, "OpenStdin":true, "StdinOnce":false, "Env":null, "Cmd": ["/bin/bash"], "Dns":null, "Image":"base", "Volumes":null, "VolumesFrom":"", "WorkingDir":"" }, "Size": 6824592 }字段要点:parent构成镜像层链;container记录该层由哪个容器 commit 而来;container_config快照了构建该层时容器的运行配置(注意此层Tty:true、OpenStdin:true,与 2.2 节 create 示例的差异)。状态码:200/404/500。
3.5 镜像历史:GET /images/(name)/history
GET /images/base/history HTTP/1.1[ {"Id": "b750fe79269d", "Created": 1364102658, "CreatedBy": "/bin/bash"}, {"Id": "27cf78414709", "Created": 1364068391, "CreatedBy": ""} ]状态码:200/404/500。
3.6 推送镜像:POST /images/(name)/push
POST /images/test/push HTTP/1.1响应为逐行 JSON 进度流({"status":"Pushing..."}等),请求头同样支持X-Registry-Auth携带 base64 编码 AuthConfig。状态码:200成功;404镜像不存在;500服务端错误。
3.7 打标签:POST /images/(name)/tag
POST /images/test/tag?repo=myrepo&force=0&tag=v42 HTTP/1.1响应HTTP/1.1 201 OK。查询参数:repo(目标仓库)、force(默认 false)、tag(新标签名)。状态码:201成功;400参数错误;404镜像不存在;409冲突;500服务端错误。
3.8 删除镜像:DELETE /images/(name)
DELETE /images/test HTTP/1.1HTTP/1.1 200 OK Content-type: application/json [ {"Untagged": "3e2f21a89f"}, {"Deleted": "3e2f21a89f"}, {"Deleted": "53b4f83ac9"} ]响应逐条报告操作结果:先摘除标签(Untagged),再逐层删除(Deleted)——这里能看到"删除一个带标签镜像实际会连带删掉其独有层"的语义。状态码:200成功;404镜像不存在;409冲突;500服务端错误。
3.9 搜索镜像:GET /images/search
在 Docker Hub 中搜索镜像。文档特别注明:从 API v1.6 起响应键名已变更,以对齐 registry 服务端返回给守护进程的 JSON。
GET /images/search?term=sshd HTTP/1.1[ { "description": "", "is_official": false, "is_trusted": false, "name": "wma55/u1210sshd", "star_count": 0 }, { "description": "", "is_official": false, "is_trusted": false, "name": "jdswinbank/sshd", "star_count": 0 }, { "description": "", "is_official": false, "is_trusted": false, "name": "vgauthier/sshd", "star_count": 0 } ]查询参数:term(搜索词)。状态码:200/500。
3.10 导出/加载镜像 tar 包:GET /images/(name)/get与POST /images/load
导出:GET /images/ubuntu/get返回包含该仓库全部镜像与元数据的 tar 包,Content-Type: application/x-tar,状态码200/500。
加载:POST /images/load,请求体即 tar 包,把一组镜像与标签载入本地仓库,响应HTTP/1.1 200 OK,状态码200/500。二者即今天docker save/docker load的 API 原型。
4. 杂项端点
4.1 通过 stdin 构建镜像:POST /build
POST /build HTTP/1.1 {{ TAR STREAM }}响应:HTTP/1.1 200 OK,Content-Type: application/json,响应体为{{ STREAM }}(构建进度流)。
约束:tar 流必须使用 identity(不压缩)、gzip、bzip2、xz 之一压缩(原文如此表述,实际指这几种编码之一),且归档根部必须包含名为Dockerfile的文件;归档中的其他文件都可作为构建上下文在ADD指令中使用。
查询参数:
| 参数 | 说明 |
|---|---|
t | 构建成功后应用到镜像的仓库名(可含标签) |
remote | 构建来源 URI(git 或 HTTPS/HTTP) |
q | 抑制详细构建输出 |
nocache | 构建时不使用缓存 |
请求头:Content-type应设为application/tar。状态码:200/500。
4.2 校验认证配置:POST /auth
POST /auth HTTP/1.1 Content-Type: application/json { "username":" hannibal", "password: "xxxx", "email": "hannibal@a-team.com", "serveraddress": "https://index.docker.io/v1/" }响应为HTTP/1.1 200 OK或204(均为成功,200 时返回校验后的 username/email 信息)。状态码:200/204成功;500服务端错误。该端点是docker login校验凭据的通道,请求体即后来registry.AuthConfig的前身(对照 api/types/registry 下今天的 AuthConfig 类型可看到字段演化)。
4.3 系统信息:GET /info
GET /info HTTP/1.1{ "Containers":11, "Images":16, "Debug":false, "NFd": 11, "NGoroutines":21, "MemoryLimit":true, "SwapLimit":false, "IPv4Forwarding":true }字段含容器/镜像计数、守护进程调试开关、打开的文件描述符数与 goroutine 数(典型的 Go 运行时自省指标)、以及内存/交换限制与 IPv4 转发能力。状态码:200/500。
4.4 版本信息:GET /version
GET /version HTTP/1.1{ "Version":"0.2.2", "GitCommit":"5a2a5cc+CHANGES", "GoVersion":"go1.0.3" }状态码:200/500。这个端点同时是 API 版本协商的载体:现代客户端正是通过/version(Ping)拿到守护进程的ApiVersion后再进行协商,见 client/client.go 的negotiateAPIVersion逻辑。
4.5 从容器变更创建镜像:POST /commit
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1HTTP/1.1 201 OK Content-Type: application/vnd.docker.raw-stream {"Id": "596069db4bf5"}查询参数:
| 参数 | 说明 |
|---|---|
container | 源容器 |
repo | 仓库 |
tag | 标签 |
m | 提交说明 |
author | 作者(例如 "John Hannibal Smith hannibal@a-team.com") |
run | 镜像运行时自动应用的配置,如{"Cmd": ["cat", "/world"], "PortSpecs":["22"]} |
状态码:201成功;404容器不存在;500服务端错误。响应中的新Id即 3.4 节 inspect 中container_config快照的来源机制(commit 把容器当前状态固化为新镜像层)。
4.6 监控事件:GET /events
以流式(实时)或轮询(带since)方式获取守护进程事件。容器报告的事件:create, destroy, die, export, kill, pause, restart, start, stop, unpause;镜像报告:untag, delete。
GET /events?since=1374067924HTTP/1.1 200 OK Content-Type: application/json {"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。
5. 深入理解:docker run背后的端点编排
v1.7 文档第 3.1 节给出了docker run客户端的完整编排步骤,这是理解"一条命令 = 一组 API 调用"的关键:
- 创建容器(
POST /containers/create); - 若返回404,说明镜像不存在:
- 尝试拉取(
POST /images/create); - 然后重试创建容器;
- 尝试拉取(
- 启动容器(
POST /containers/(id)/start); - 非分离(detached)模式下:
- 附加到容器,使用
logs=1(以获得容器启动以来的 stdout 与 stderr)和stream=1;
- 附加到容器,使用
- 分离模式或仅附加 stdin 时:
- 打印容器 ID。
这套"create → 404 则 pull → 重试 → start → attach"的幂等重试模式,至今仍是各类 Docker 客户端 SDK 实现run语义的标准流程。
5.1 Hijacking 机制
v1.7 文档第 3.2 节明确指出:本版本 API 中/attach使用 hijacking 在同一 socket 上同时传输 stdin、stdout 与 stderr,"未来可能改变"。从当前仓库源码看,这个机制不但没有消失,反而被标准化了:
- client/hijack.go 的
setupHijackConn通过Connection: Upgrade/Upgrade头完成协议切换,并针对长空闲连接(长时间无输出的命令)设置 TCP KeepAlive(30 秒周期),注释里说明这是为了规避某些网络环境下 ECONNTIMEOUT 导致的客户端状态不确定——这正是 v1.7 时代 hijack 流在弱网络上踩过的坑的工程化修复; - 响应头
Content-Type被HijackedResponse.MediaType()暴露给上层,用于判定拿到的是原始流还是多路复用流,进而决定是否走 api/pkg/stdcopy 拆帧。
5.2 跨域请求(CORS)
文档第 3.3 节:允许对远程 API 的跨域请求,需要在守护进程模式启动时加--api-enable-cors标志:
$ docker -d -H="192.168.1.9:2375" --api-enable-cors需要说明:--api-enable-cors是 v1.7 时代的守护进程标志,在当前仓库的 Go 源码中已检索不到该标志的实现(grepapi-enable-cors/APIEnableCORS无结果),它属于历史演进中被移除的选项;今天跨域支持由守护进程配置文件/--cors-header等形式承载。引用该段落时应以其历史语境为准。
6. 结语:从 v1.7 到现代 Moby API 的传承
通读 api/docs/v1.7.md 并结合仓库源码,可以清晰地看到三条延续至今的主线:
- 端点资源模型:
/containers/*、/images/*、/build、/events的资源划分与"逐行 JSON 进度流"响应风格,直接演化为今天 api/swagger.yaml 与 api/docs/v1.25.yaml 等现代版本的 OpenAPI 规范; - 流协议:attach 的 8 字节帧头多路复用格式(
[STREAM_TYPE,0,0,0,SIZE1..SIZE4],big-endian uint32)被 api/pkg/stdcopy 逐字节实现并沿用至今,是该文档最有长期价值的算法资产; - 认证与版本协商:
X-Registry-Authbase64 头由 api/pkg/authconfig 承接,/version端点则是客户端协商 API 版本(当前客户端支持 1.40~1.56)的入口,见 client/client.go。
同时应注意适用边界:v1.7 中的insert、WebSocket attach(attach/ws)、--api-enable-cors等均为已被取代的历史特性;若要在当前 Moby 上编写客户端,应以 api/docs 下最高版本规范(如 api/docs/v1.55.yaml)为准,本文档的价值在于理解 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),仅供参考