- 云原生
- 容器编排
【免费下载链接】k3d
Little helper to run CNCF's k3s in Docker
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.yaml | API 的 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 定义文件。官方文档明确指出,这份定义有三种用途:
- 自动生成文档:从同一份定义渲染出人类可读的 API 参考文档;
- 自动生成 Go 服务器端与客户端代码(官方标注为 work-in-progress,即进行中);
- 提供机器可读的 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"场景下行为的关键:
- docker-machine:如果配置了 docker-machine,使用其 IP(用于通过 docker-machine 创建远程节点时访问集群 API);
DOCKER_HOST环境变量:显式指定的 daemon 地址;- Docker for Desktop(Win/Mac):本地连接时回退到
host.docker.internal——若检测到 WSL2(存在WSL_DISTRO_NAME)则放弃,因为该域名在 WSL2 内不可达; - 最终通过
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
相关推荐
k3d 背后的 Docker 引擎 API Go 客户端:`docker/docker/client` 包解析与实战指南
k3d 背后的 Docker 引擎 API Go 客户端: docker/docker/client 包解析与实战指南 导读 k3d 是一个在 Docker 中
云原生容器编排深入解析 Docker Engine API:从 Swagger 定义到 k3d 运行时集成的完整指南
深入解析 Docker Engine API:从 Swagger 定义到 k3d 运行时集成的完整指南 导读 Docker Engine API 是 Docke
云原生容器编排Docker Engine API 与 Swagger 定义:从 api/swagger.yaml 到 Go 客户端与文档生成
Docker Engine API 与 Swagger 定义:从 api/swagger.yaml 到 Go 客户端与文档生成 Docker Engine AP
操作系统云原生容器运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考