☰
解读 Docker Engine API:Swagger 定义、Go 客户端与 k3d 的 Docker 运行时实践
2026/10/9 1:53:46 网站建设 项目流程
  • 云原生
  • 容器编排

【免费下载链接】k3d

Little helper to run CNCF's k3s in Docker

项目地址:https://gitcode.com/gh_mirrors/k3/k3d
点击查看免费下载

Docker Engine API 是 Docker 命令行客户端与守护进程(daemon)之间通信的 HTTP 接口,也是第三方软件控制 Docker 引擎的统一入口。本文以 k3d 仓库中 vendored 的 Docker 官方 API 文档(vendor/github.com/docker/docker/api/README.md)为骨架,结合仓库内的swagger.yaml、Go 客户端源码以及 k3d 自身对这套 API 的消费方式,完整讲解 Engine API 的结构、Swagger 定义维护流程、版本协商机制,以及如何从源码层面理解 k3d 通过 Go 客户端操作 Docker 容器、网络与镜像的完整链路。读完本文,你将能够独立定位 Engine API 的端点与类型定义、理解客户端版本协商的底层逻辑,并看懂 k3d 的 Docker 运行时实现。

一、Engine API 是什么

按照 Docker 官方 API 文档的定义,Engine API 是一个 HTTP API,被命令行客户端用于与守护进程通信,也可以被第三方软件用来控制守护进程。k3d 正是这类"第三方软件"的典型代表:它通过该 API 在 Docker 中创建、启动、删除 k3s 集群节点容器。

在 Docker 官方仓库中,这套 API 由以下组件构成(在 k3d 仓库中对应 vendored 目录):

组件作用在 k3d 仓库中的位置
api/swagger.yamlAPI 的 Swagger 定义,是 API 文档与部分类型的唯一事实来源vendor/github.com/docker/docker/api/swagger.yaml
api/types/客户端与服务器共享的类型,表示各种对象、选项、响应等;多数手写,部分由 Swagger 定义自动生成vendor/github.com/docker/docker/api/types
cli/命令行客户端(docker命令本体)未在 k3d 中 vendored
client/命令行客户端使用的 Go 客户端,也可供第三方 Go 程序使用vendor/github.com/docker/docker/client
daemon/守护进程,负责提供 API 服务未在 k3d 中 vendored

在 k3d 的 vendor 目录下,api包还包含 vendor/github.com/docker/docker/api/common.go(daemon 与 client 共用的常量)和 vendor/github.com/docker/docker/api/swagger-gen.yaml(与 Swagger 定义配套的生成配置文件)。

二、Swagger 定义:API 的唯一事实来源

api/swagger.yaml是整套 API 的 Swagger 定义文件。官方文档明确指出,这份定义有三种用途:

  1. 自动生成文档:从同一份定义渲染出人类可读的 API 参考文档;
  2. 自动生成 Go 服务器端与客户端代码(官方标注为 work-in-progress,即进行中);
  3. 提供机器可读的 API 描述,用于内省 API 能力、为其他语言自动生成客户端等。

从 k3d 仓库中的实际文件看,这份定义采用 Swagger 2.0(即 OpenAPI)格式。文件头部声明了协议与数据格式:

swagger: "2.0" schemes: - "http" - "https" produces: - "application/json" - "text/plain" consumes: - "application/json" - "text/plain" basePath: "/v1.51" info: title: "Docker Engine API" version: "1.51"

注意basePath: "/v1.51",即当前 API 的默认版本为 v1.51。这一点与 vendor/github.com/docker/docker/api/common.go 中定义的常量完全一致:

const ( // DefaultVersion of the current REST API. DefaultVersion = "1.51" // MinSupportedAPIVersion is the minimum API version that can be supported // by the API server, specified as "major.minor". MinSupportedAPIVersion = "1.24" // NoBaseImageSpecifier is the symbol used by the FROM // command to specify that no base image is to be used. NoBaseImageSpecifier = "scratch" )

MinSupportedAPIVersion = "1.24"意味着低于 1.24 的 API 请求会被 daemon 拒绝(产生错误);NoBaseImageSpecifier = "scratch"则是 DockerfileFROM指令中"无基础镜像"的专用符号。

三、API 版本化与开放 schema 模型

swagger.yaml的描述部分还明确了几个对客户端开发者至关重要的契约:

  • 版本前缀:API 每个版本都可能变化,调用时通过 URL 前缀锁定版本,例如/v1.30/info使用 v1.30 版本的/info端点;如果指定的版本不被 daemon 支持,返回 HTTP400 Bad Request。
  • 省略版本前缀:不带版本前缀时使用当前版本(v1.51),例如/info等价于/v1.51/info。官方文档明确提示这种方式已弃用,并将在未来版本移除。
  • 开放 schema 模型:服务器可以在响应中附加额外属性,也会忽略请求中额外的查询参数和请求体属性。因此客户端必须忽略响应中的额外属性,才能在面对更新的 daemon 时保持不中断。
  • 错误格式:API 使用标准 HTTP 状态码表示成败,错误响应体为 JSON:
{ "message": "page not found" }
  • 注册表认证:涉及注册表的端点(如POST /images/(name)/push)由客户端侧处理认证,通过X-Registry-Auth请求头发送一个 base64url 编码的 JSON 字符串。k3d 的镜像导入功能(pkg/client/image 相关实现)在底层就会涉及这类与镜像仓库交互的端点。

四、更新 API 文档的标准工作流

官方文档强调:API 文档完全由api/swagger.yaml生成。如果对 API 做了任何修改,都必须编辑该文件,让变更反映到文档中。

swagger.yaml被分为两个主要部分(在 vendored 文件中分别位于 第 174 行definitions:和 第 7650 行paths:):

  • definitions:定义请求和响应中可复用的对象;
  • paths:定义 API 端点(以及少量无需复用的内联对象)。

编辑流程为:先在paths下找到要修改的端点,进行所需修改;端点可以通过$ref引用definitions中的可复用对象。文件中已有大量示例可供模仿(例如新增字段或端点时,可以从文件其他位置复制相似模式),完整的参考则见 Swagger 规范本身。

以容器列表端点为例,paths下的GET /containers/json(vendor/github.com/docker/docker/api/swagger.yaml#L7651)声明了查询参数all(默认仅返回运行中的容器)、limit、size以及filters,其中filters是 JSON 编码的map[string][]string,支持ancestor、before、expose、health、label、name、network、status、volume等十余种过滤条件;200 响应引用#/definitions/ContainerSummary,400/500 响应引用#/definitions/ErrorResponse。k3d 在 pkg/runtimes/docker/container.go 中正是通过这个端点配合label与name过滤来定位集群节点的容器。

swagger.yaml会由 Docker 仓库的hack/validate/swagger校验脚本验证,确保其始终是合法的 Swagger 定义——这是官方文档给出的质量保障手段。

五、查看 API 文档渲染效果

官方文档给出了本地预览与生产发布的流程:

  • 修改swagger.yaml后,运行make swagger-docs,即可在http://localhost:9000打开预览页面,确认文档是否正确渲染(部分样式可能不准确,但足以验证内容生成是否正确);
  • 生产环境文档则由 Docker 官方将swagger.yamlvendored 到 docker.github.io 文档站点后生成。

六、Go 客户端:从 Swagger 定义到可调用代码

官方文档中client/组件是命令行客户端使用的 Go 客户端,也可供第三方 Go 程序使用。k3d 正是这样一个第三方使用者。理解客户端的关键入口是 vendor/github.com/docker/docker/client/client.go 中的NewClientWithOpts,它采用函数式选项(functional options)模式:

cli, err := client.NewClientWithOpts( client.FromEnv, client.WithAPIVersionNegotiation(), )

6.1 函数式选项清单

vendor/github.com/docker/docker/client/options.go 定义了完整选项,按功能可分为几类:

  • 环境变量类:FromEnv一次性读取DOCKER_HOST(daemon 地址)、DOCKER_API_VERSION(API 版本,留空则用最新)、DOCKER_CERT_PATH(TLS 证书目录,包含ca.pem、cert.pem、key.pem)、DOCKER_TLS_VERIFY(是否启用 TLS 校验,默认关闭);另有独立的WithHostFromEnv、WithVersionFromEnv可单独启用;
  • 连接类:WithHost指定 daemon 地址(解析为协议/地址/basePath 并配置底层 transport)、WithDialContext自定义拨号器(可设置超时与 KeepAlive)、WithHTTPClient替换底层 HTTP 客户端、WithTimeout设置请求超时;
  • 版本类:WithVersion固定 API 版本(设置后开启manualOverride,跳过协商)、WithAPIVersionNegotiation开启自动版本协商(在第一次请求时执行,后续请求不再协商);
  • HTTP 细节类:WithUserAgent、WithHTTPHeaders;
  • 可观测性类:WithTraceProvider、WithTraceOptions,用于接入 OpenTelemetry 链路追踪。

6.2 默认客户端的行为细节

从NewClientWithOpts的实现(vendor/github.com/docker/docker/client/client.go#L250-L268)可以看到默认http.Client的关键配置:

  • MaxIdleConns = 6、IdleConnTimeout = 30 * time.Second,避免长期运行的程序因空闲连接未释放而泄漏连接;
  • 自定义CheckRedirect重定向策略:GET 请求遇重定向时直接返回ErrUseLastResponse,避免 Go 1.8 起 301 被自动转成 GET 再得到 404 的经典坑;
  • 最终 transport 会用otelhttp.NewTransport包装,为每个请求生成带方法名与路径的追踪 span。

6.3 API 版本协商的底层机制

版本协商是客户端最核心的机制之一,相关实现集中在 vendor/github.com/docker/docker/client/client.go:

  • checkVersion:在构建请求路径前被调用(getAPIPath内部调用),若启用了协商且尚未协商过,则先发一次GET /_ping,根据响应头中的 API 版本确定最终版本;
  • getAPIPath:若客户端有版本号,则拼接/v<version>/<path>形式的路径,否则直接使用无版本前缀的路径;
  • NegotiateAPIVersion/NegotiateAPIVersionPing:若 ping 返回的版本低于客户端默认版本则降级;若服务器版本高于客户端支持的最大版本则用客户端最大版本;若 ping 响应中没有版本信息,则降级到引入版本协商之前的最高版本1.24(与common.go的MinSupportedAPIVersion呼应);
  • 只要存在手动覆盖(DOCKER_API_VERSION环境变量或WithVersion固定版本),就完全不进行协商。

另外 vendor/github.com/docker/docker/client/client_deprecated.go 中的NewClient与NewEnvClient已被标记为 Deprecated,官方推荐统一使用NewClientWithOpts。

七、k3d 如何消费这套 API:运行时源码拆解

k3d 的核心运行时就建立在 Docker Engine API 之上(pkg/runtimes/docker)。以下是几个关键消费点,可以与上文介绍的 API 机制一一对上。

7.1 客户端创建:复用 docker CLI 的初始化管线

k3d 没有直接用NewClientWithOpts,而是通过 pkg/runtimes/docker/util.go#L183-L203 的GetDockerClient(),复用 docker CLI 的完整初始化管线:

func GetDockerClient() (client.APIClient, error) { dockerCli, err := command.NewDockerCli(command.WithStandardStreams()) ... newClientOpts := flags.NewClientOptions() newClientOpts.LogLevel = l.Log().GetLevel().String() flagset := pflag.NewFlagSet("docker", pflag.ContinueOnError) newClientOpts.InstallFlags(flagset) newClientOpts.SetDefaultOptions(flagset) err = dockerCli.Initialize(newClientOpts) ... return dockerCli.Client(), nil }

这样 k3d 可以继承 docker CLI 的DOCKER_HOST、TLS、API 版本等全部环境变量约定,并与用户本机的 docker 配置保持一致。

7.2 确定 daemon 地址:GetHost 的优先级链

pkg/runtimes/docker/docker.go#L45-L92 的GetHost()展示了连接 Docker daemon 时主机地址的判定优先级,这也是理解 k3d 在"远程 Docker"场景下行为的关键:

  1. docker-machine:如果配置了 docker-machine,使用其 IP(用于通过 docker-machine 创建远程节点时访问集群 API);
  2. DOCKER_HOST环境变量:显式指定的 daemon 地址;
  3. Docker for Desktop(Win/Mac):本地连接时回退到host.docker.internal——若检测到 WSL2(存在WSL_DISTRO_NAME)则放弃,因为该域名在 WSL2 内不可达;
  4. 最终通过url.Parse提取url.Host作为返回地址。

同时 pkg/runtimes/docker/docker.go#L94-L99 的GetRuntimePath()定义了默认 socket 路径DefaultDockerSock = "/var/run/docker.sock"(可用DOCKER_SOCK覆盖)——k3d 正是靠挂载这个 socket 让容器内的 k3s 与 Docker daemon 通信。

7.3 容器生命周期:Create/Start/Remove 与镜像拉取

pkg/runtimes/docker/container.go 展示了 Engine API 在容器生命周期管理中的典型用法:

  • createContainer(第 41-69 行):调用ContainerCreate,若返回IsErrNotFound(镜像不存在),先pullImage再重试创建——对应POST /containers/create与POST /images/create两个端点;
  • startContainer(第 71-80 行):调用ContainerStart(对应POST /containers/{id}/start);
  • removeContainer(第 83-100 行):以RemoveVolumes: true, Force: true调用ContainerRemove,等价于docker rm -f(对应DELETE /containers/{id})。

7.4 节点查找:借助 filters 的精确定位

GetContainer(pkg/runtimes/docker/container.go#L140-L167)把上文GET /containers/json的filters参数用到了极致:

  • 用label过滤 k3d 写入容器的运行时标签(k3d.cluster、k3d.role等);
  • 用name过滤实现精确匹配:容器名以/开头(Docker 命名惯例),用户输入可能带或不带k3d-前缀,因此构造正则^/?(k3d-)?<name>$;
  • 要求过滤结果恰好为一个容器,多于一个或为零都视为错误。

7.5 容器内探测与文件复制

  • executeCheckInContainer(第 171-219 行):创建一个临时容器执行任意命令,通过ContainerWait(等待条件WaitConditionNotRunning)获取退出码后删除容器——k3d 用它探测 Docker 环境;
  • CheckIfDirectoryExists(第 222-229 行):用sh -c '[ -d "<dir>" ] && exit 0 || exit 1'检查容器环境内目录是否存在;
  • pkg/runtimes/docker/util.go#L170-L181:CopyFromContainer从节点容器内复制文件(如 k3s 的 kubeconfig 生成依赖),对应GET /containers/{id}/archive端点。

八、在 k3d 仓库中继续深入

  • 阅读 API 全貌:从 vendor/github.com/docker/docker/api/swagger.yaml 的definitions(第 174 行起)与paths(第 7650 行起)两大节开始,对照 vendor/github.com/docker/docker/api/common.go 确认当前版本常量;
  • 研究客户端:以 vendor/github.com/docker/docker/client/options.go 为索引,逐个跟踪With*选项的实现,再结合 vendor/github.com/docker/docker/client/client.go 理解版本协商与路径拼接;
  • 理解 k3d 的消费模式:从 pkg/runtimes/docker/docker.go(daemon 地址与 socket)→ pkg/runtimes/docker/util.go(客户端创建与文件复制)→ pkg/runtimes/docker/container.go(容器全生命周期)这条链路读下去,即可完整还原 k3d 与 Docker daemon 的每一次 HTTP 交互。

需要说明的是,k3d 仓库中的vendor/github.com/docker/docker属于只读的 vendored 依赖,官方文档中提到的hack/validate/swagger、make swagger-docs等工具链属于 Docker 上游仓库的开发流程;在 k3d 中阅读与使用这些文件时,应以了解 API 契约与客户端行为为主要目的,如需修改 API 定义则应回到 Docker 上游进行。

  • 云原生
  • 容器编排

【免费下载链接】k3d

Little helper to run CNCF's k3s in Docker

项目地址:https://gitcode.com/gh_mirrors/k3/k3d
点击查看免费下载
上一篇:终极指南:为什么SVGPath是设计师必备的在线绘图神器?
下一篇:Taxonomy搜索功能:Next.js 13全文搜索终极实现指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询