Moby/Docker daemon API 操作实现入门指南
一、这一层文件是干什么的
打开 daemon/ 根目录,你会看到一堆"动词"文件:
daemon/create.go 创建容器
daemon/start.go 启动容器
daemon/stop.go 停止容器
daemon/kill.go 发信号/强杀容器
daemon/pause.go 暂停容器
daemon/unpause.go 恢复容器
daemon/restart.go 重启容器
daemon/delete.go 删除容器
daemon/wait.go 等待容器退出
daemon/inspect.go 查看容器详情
daemon/list.go 列出容器
daemon/logs.go 拉取容器日志
daemon/attach.go 接管容器 stdio
daemon/exec.go 在容器内执行命令
daemon/rename.go 改名
daemon/update.go 更新配置
daemon/prune.go 清理无用容器/网络
daemon/export.go 导出容器文件系统
daemon/changes.go 查看容器文件系统改动
daemon/archive.go 容器与宿主机之间拷文件
daemon/stats.go 拉取容器资源使用
daemon/commit.go 把容器提交为镜像
daemon/events.go 事件上报(被各操作调用)
daemon/network.go 网络相关操作
daemon/build.go 镜像构建
daemon/content.go 镜像内容存储
daemon/info.go daemon 信息
daemon/disk_usage.go 磁盘占用
daemon/resize.go 调整容器 TTY 大小
daemon/top.go 容器内进程列表
daemon/checkpoint.go CRIU 检查点
daemon/configs.go 配置
daemon/secrets.go 密钥
daemon/cluster.go Swarm 集群
daemon/migration.go 数据迁移
daemon/reload.go 配置热加载
daemon/keys.go 签名密钥
daemon/hosts.go daemon 监听地址
daemon/volumes.go 卷管理
些文件统称为 daemon 的 API 操作实现层,是 dockerd 真正"干活"的地方:
- 上面接收 HTTP 路由层(
daemon/server/router/...)转发过来的请求;
- 下面调用 containerd、libnetwork、镜像服务、卷服务等底层组件;
- 中间完成"参数校验 + 状态机维护 + 资源分配 + 持久化 + 事件上报"这一整套业务流程。
一句话总结:HTTP 层只做协议转换,业务逻辑全在 daemon/<op>.go。
二、整体调用链(一张图看懂)
docker CLI 命令(如 docker run)
│
▼ HTTP 请求
daemon/server/router/container/container.go ← 路由表(注册 URL → handler)
│
▼
daemon/server/router/container/container_routes.go ← handler(解析请求参数)
│
▼ 通过 Backend 接口转发
daemon/server/router/container/backend.go ← Backend 接口定义
│
▼
daemon/<op>.go ← ★ 你正在学的这一层 ★
│ ContainerXxx(公开 API)
│ └─ containerXxx(内部实现)
│
▼ 调用底层服务
┌───────────────────────────────────────────────┐
│ daemon/container/ 容器对象、状态机 │
│ daemon/images/ 镜像服务 │
│ daemon/libnetwork/ 网络(libnetwork) │
│ daemon/volume/ 卷服务 │
│ daemon/containerd/ containerd 客户端 │
│ daemon/internal/... 内部工具包 │
└───────────────────────────────────────────────┘
│
▼
containerd / runc / kernel(真正运行容器进程)
关键设计:router 不直接依赖 *daemon.Daemon,只依赖 Backend 接口。
这样的好处是:
- router 可以独立单元测试(mock 一个 Backend 即可);
- daemon 重构时只要 Backend 接口不变,router 不需要改;
- 阅读代码时,
backend.go就是 router 和 daemon 之间的"合同"。
三、Backend 接口:router 和 daemon 之间的"合同"
打开 daemon/server/router/container/backend.go:
// Backend is all the methods that need to be implemented to provide container specific functionality.
type Backend interface {
commitBackend
execBackend
copyBackend
stateBackend
monitorBackend
attachBackend
systemBackend
sysInfoProvider
}
它由几个子接口组合而成,按功能分组(这是阅读源码的"目录"):
|
子接口 |
负责的事情 |
代表方法 |
|
|
容器生命周期 |
|
|
|
容器观测 |
|
|
|
exec 相关 |
|
|
|
文件拷贝 |
|
|
|
接管 stdio |
|
|
|
系统级操作 |
|
|
|
提交镜像 |
|
daemon.Daemon 实现了 Backend 接口的所有方法,实现就散落在 daemon/<op>.go 这些文件里。
四、每个 <op>.go 的通用代码模式
新手最容易卡住的地方是"这个文件 800 行,从哪儿看起"。其实它们有非常固定的套路。90% 的 <op>.go 都遵循下面的双层结构:
// ① 公开 API(大写开头):实现 Backend 接口,被 router 调用
// 职责:参数校验 + 查容器 + 状态判断 + 调内部实现
func (daemon *Daemon) ContainerXxx(ctx context.Context, name string, ...) error {
ctr, err := daemon.GetContainer(name) // 1. 按名字/ID 找容器
if err != nil {
return err
}
if !ctr.State.IsRunning() { // 2. 状态机校验
return errdefs.Conflict(...) // 用 errdefs 返回标准错误
}
return daemon.containerXxx(ctx, ctr, ...)// 3. 调内部小写实现
}
// ② 内部实现(小写开头):真正干活的地方
// 职责:加锁 → 调底层 → 改状态 → 持久化 → 发事件
func (daemon *Daemon) containerXxx(ctx context.Context, ctr *container.Container, ...) error {
ctr.Lock()
defer ctr.Unlock()
// ... 业务逻辑 ...
ctr.CheckpointTo(ctx, daemon.containersReplica) // 持久化到磁盘
daemon.LogContainerEvent(ctr, events.ActionXxx) // 上报事件
return nil
}
为什么要拆成两层?
- 大写
ContainerXxx是"门面":负责"参数→容器对象"的转换,做状态机检查;
- 小写
containerXxx是"内核":拿到容器对象后真正干活,可以被 daemon 内部其他流程复用(比如restart内部会调containerStop+containerStart,prune内部会调containerRm)。
4.1 固定出现的"配方元素"
|
元素 |
作用 |
在哪里能看见 |
|
|
按全 ID / 名字 / 短 ID 前缀找容器 |
几乎每个文件开头 |
|
|
容器级互斥锁,避免并发改同一容器 |
|
|
|
状态机判断 |
|
|
|
标准 typed error,router 会转成对应 HTTP 状态码 |
|
|
|
把容器内存状态写到 |
所有改状态的操作 |
|
|
上报事件, |
所有改状态的操作 |
|
|
Prometheus 指标 |
|
|
|
OpenTelemetry 分布式追踪 span |
|
4.2 错误处理:errdefs 包
daemon/errors.go 里定义了一堆 typed error。它们都实现了 errdefs 里的接口(NotFound()、Conflict()、InvalidParameter() 等)。router 层的 httpstatus 中间件会根据这些接口自动转成 HTTP 状态码:
|
Go 错误类型 |
HTTP 状态码 |
例子 |
|
|
400 Bad Request |
参数不合法 |
|
|
404 Not Found |
容器不存在 |
|
|
409 Conflict |
容器正在运行不能删 |
|
|
304 Not Modified |
容器已经是目标状态 |
|
|
500 Internal Server Error |
daemon 内部错误 |
|
|
403 Forbidden |
操作不被允许 |
写 daemon 代码时,错误返回前都要用 errdefs.Xxx(err) 包一层,告诉 router 该回什么 HTTP 状态码。
五、按"功能分类"逐组讲解
5.1 容器生命周期(最重要的一组)
这组文件覆盖了容器从创建到销毁的完整状态机。也是 docker run 调用链的核心。
|
文件 |
公开 API |
干什么 |
|
|
|
创建容器对象、准备 rootfs、写 |
|
|
|
启动容器进程。10 个阶段(mount、network、OCI spec、containerd task、runc 启动、状态更新)。最复杂的文件。 |
|
|
|
发停止信号 → 等超时 → SIGKILL 兜底 |
|
|
|
发指定信号;SIGKILL 时还会 wait 退出 |
|
|
|
冻结容器进程(cgroup freezer) |
|
|
|
解冻 |
|
|
|
stop + start,中间避免重复 mount/unmount |
|
|
|
删容器:停 → 释放 RWLayer → 删目录 → 释放名字和网络 |
|
|
|
阻塞等待容器达到某状态,返回 |
状态机简图:
create start
┌──────────┐ ────► ┌──────────┐
│ created │ │ running │ ◄──── start (重启)
└──────────┘ └──────────┘
│ │ │
│ rm │ stop │ pause
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ dead │ │ stopped │ │ paused │
└──────────┘ └──────────┘ └──────────┘
│ │
│ start │ unpause
└─────┬──────┘
▼
running
5.2 容器观测(只读操作)
|
文件 |
公开 API |
干什么 |
|
|
|
返回容器完整 JSON(状态、配置、网络、挂载、健康检查…) |
|
|
|
|
|
|
|
读容器日志(支持 follow、since、until、tail),返回 |
|
|
|
容器 CPU/内存/网络 IO 指标,支持流式推送 |
|
|
|
容器内进程列表(类似 |
|
|
|
容器文件系统相对镜像的改动(A/D/C 三种) |
5.3 容器交互
|
文件 |
公开 API |
干什么 |
|
|
|
接管容器 stdin/stdout/stderr,支持 detach key |
|
|
|
在运行中的容器内执行额外命令 |
|
|
|
调整 TTY 大小 |
5.4 容器文件系统
|
文件 |
公开 API |
干什么 |
|
|
|
|
|
|
|
把容器文件系统导出为 tar 流 |
|
|
|
把容器当前状态提交为新镜像 |
5.5 配置变更
|
文件 |
公开 API |
干什么 |
|
|
|
改容器名(要联动改 DNS、link、网络 sandbox 名字) |
|
|
|
更新容器配置(资源限制、重启策略等,运行中也能改) |
5.6 系统级清理
|
文件 |
公开 API |
干什么 |
|
|
|
清理已停止的容器 / 无用网络,带 |
5.7 事件
events.go 不是被 router 直接调的 API,而是被其他 <op>.go 调用的工具函数:
// 任何改变容器状态的操作,最后都会调一句这个:
daemon.LogContainerEvent(ctr, events.ActionStart)
daemon.LogContainerEvent(ctr, events.ActionStop)
daemon.LogContainerEvent(ctr, events.ActionDestroy)
// ...
事件最终通过 daemon.EventsService.Log(...) 推到 docker events 订阅者。这是编排系统(Kubernetes、Swarm)感知容器变化的依据。
5.8 平台特定文件(带 _linux.go / _windows.go 后缀)
很多操作在 Linux 和 Windows 上实现差别很大,所以会拆出平台文件:
daemon/create_unix.go daemon/create_windows.go
daemon/start_linux.go daemon/start_windows.go
daemon/exec_linux.go daemon/exec_windows.go
daemon/info_unix.go daemon/info_windows.go
daemon/volumes_linux.go daemon/volumes_windows.go
六、两个文件深度剖析(新手必读)
6.1 create.go:容器是怎么"造"出来的
文件位置:daemon/create.go
三层入口(从外到内):
ContainerCreate(公开 API,实现 Backend 接口)
│ 加 OTel span、校验平台、填默认 RestartPolicy
▼
containerCreate(内部实现)
│ ① verifyContainerSettings 校验配置
│ ② imageService.GetImage 找镜像、检测平台不匹配时给 warning
│ ③ validateNetworkingConfig 校验网络配置
│ ④ adaptContainerSettings 填充 HostConfig 默认值
▼
create(具体执行,原文是 daemon.create)
① daemon.newContainer 新建 *Container 实例
② daemon.setSecurityOptions 设置 SELinux/AppArmor
③ daemon.createContainerOSSpecificSettings 平台特定初始化
④ daemon.updateContainerNetworkSettings 配置网络
⑤ daemon.registerMountPoints 注册挂载点
⑥ daemon.imageService.CreateLayer 创建可写层(rootfs)
⑦ user.MkdirAndChown 创建容器目录
⑧ daemon.register 注册到 daemon.containers map
⑨ daemon.LogContainerEvent(ctr, events.ActionCreate)
关键设计:
- 创建是同步原子的:失败时通过
defer调cleanupContainer完整回滚(不留垃圾);
- 创建不启动进程,不占运行时资源;
docker run=docker create+docker start,CLI 会顺序发两个 HTTP 请求。
6.2 start.go:容器是怎么"跑"起来的
文件位置:daemon/start.go,整个 daemon 里最复杂的文件之一。
containerStart 函数有 10 个阶段(注释里标得很清楚):
阶段 1 OTel span + 状态守卫 已经 Running/Dead 直接返回
阶段 2 失败兜底 defer AutoRemove 容器失败时直接 rm
阶段 3 准备运行环境 mount + initializeNetworking
+ setupContainerDirs + setupMounts
阶段 4 构造 OCI spec createSpec(cgroup/namespace/mounts 合并)
阶段 5 restart-manager / AppArmor / ckpt 路径
阶段 6 libcontainerd.ReplaceContainer 在 containerd 里创建容器对象
阶段 7 ctr.NewTask 创建 task(=容器进程实体,但还没运行)
阶段 8 initializeCreatedTask 注入 cgroup 资源限制、namespace
阶段 9 tsk.Start ★ 真正 fork 出容器进程 ★
阶段 10 状态更新 + 健康监控 + 事件上报 SetRunning
+ initHealthMonitor + LogContainerEvent
为什么要这么多阶段?
容器启动要兼顾网络、存储、安全、命名空间、日志、attach 等多方面,每一步都可能失败,失败要回滚。所以函数末尾用了一串洋葱式 defer:
defer ① 失败时回滚(SetError + Cleanup + 可选 containerRm)
defer ② 失败时删 sandbox
defer cleanup(成功路径也会跑,unmount 挂载点)
defer ③ 失败时删 containerd 容器
defer ④ 失败时 ForceDelete containerd task
七、一张速查表(贴墙边用)
┌─────────────────────────────────────────────────────────────┐
│ daemon/<op>.go 标准结构 │
├─────────────────────────────────────────────────────────────┤
│ 1. package daemon │
│ 2. import (...) │
│ 3. 公开 API: func (daemon *Daemon) ContainerXxx(...) │
│ ├─ daemon.GetContainer(name) 找容器 │
│ ├─ 状态机校验 (IsRunning/IsPaused/...) │
│ ├─ verifyContainerSettings 校验配置 │
│ └─ return daemon.containerXxx(...) 调内部实现 │
│ 4. 内部实现: func (daemon *Daemon) containerXxx(...) │
│ ├─ ctr.Lock() / defer ctr.Unlock() │
│ ├─ OTel span │
│ ├─ 业务逻辑(调底层服务) │
│ ├─ ctr.CheckpointTo(...) 持久化 │
│ ├─ daemon.LogContainerEvent(...) 发事件 │
│ └─ metrics.ContainerActions... 上报指标 │
└─────────────────────────────────────────────────────────────┘
更多推荐

所有评论(0)