一、这一层文件是干什么的

打开 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 接口。
这样的好处是:

  1. router 可以独立单元测试(mock 一个 Backend 即可);
  1. daemon 重构时只要 Backend 接口不变,router 不需要改;
  1. 阅读代码时,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
}

它由几个子接口组合而成,按功能分组(这是阅读源码的"目录"):

子接口

负责的事情

代表方法

stateBackend

容器生命周期

ContainerCreateContainerStartContainerStopContainerKillContainerPauseContainerUnpauseContainerRestartContainerRmContainerRenameContainerUpdateContainerWaitContainerResize

monitorBackend

容器观测

ContainerInspectContainers(list)、ContainerLogsContainerStatsContainerTopContainerChanges

execBackend

exec 相关

ContainerExecCreateContainerExecStartContainerExecInspectContainerExecResizeExecExists

copyBackend

文件拷贝

ContainerArchivePathContainerExtractToDirContainerStatPathContainerExport

attachBackend

接管 stdio

ContainerAttach

systemBackend

系统级操作

ContainerPrune

commitBackend

提交镜像

CreateImageFromContainer

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 + containerStartprune 内部会调 containerRm)。

4.1 固定出现的"配方元素"

元素

作用

在哪里能看见

daemon.GetContainer(name)

按全 ID / 名字 / 短 ID 前缀找容器

几乎每个文件开头

ctr.Lock() / defer ctr.Unlock()

容器级互斥锁,避免并发改同一容器

pause.gokill.gorename.go

ctr.State.IsRunning() / IsPaused() / ...

状态机判断

stop.gokill.goexec.go

errdefs.InvalidParameter / Conflict / NotFound / System / NotModified

标准 typed error,router 会转成对应 HTTP 状态码

errors.go + 所有 <op>.go

ctr.CheckpointTo(ctx, daemon.containersReplica)

把容器内存状态写到 config.v2.json

所有改状态的操作

daemon.LogContainerEvent(ctr, events.ActionXxx)

上报事件,docker events 能看到

所有改状态的操作

metrics.ContainerActions.WithValues("xxx").UpdateSince(start)

Prometheus 指标

create.godelete.gostart.go

otel.Tracer("").Start(ctx, "daemon.xxx")

OpenTelemetry 分布式追踪 span

create.gostart.gologs.go

4.2 错误处理:errdefs 包

daemon/errors.go 里定义了一堆 typed error。它们都实现了 errdefs 里的接口(NotFound()Conflict()InvalidParameter() 等)。router 层的 httpstatus 中间件会根据这些接口自动转成 HTTP 状态码:

Go 错误类型

HTTP 状态码

例子

errdefs.InvalidParameter / invalidIdentifier

400 Bad Request

参数不合法

errdefs.NotFound / containerNotFound

404 Not Found

容器不存在

errdefs.Conflict / containerNotRunningError

409 Conflict

容器正在运行不能删

errdefs.NotModified

304 Not Modified

容器已经是目标状态

errdefs.System

500 Internal Server Error

daemon 内部错误

errdefs.Forbidden

403 Forbidden

操作不被允许

写 daemon 代码时,错误返回前都要用 errdefs.Xxx(err) 包一层,告诉 router 该回什么 HTTP 状态码。


五、按"功能分类"逐组讲解

5.1 容器生命周期(最重要的一组)

这组文件覆盖了容器从创建到销毁的完整状态机。也是 docker run 调用链的核心

文件

公开 API

干什么

create.go

ContainerCreate

创建容器对象、准备 rootfs、写 config.v2.json、注册到 daemon 内存表。不启动进程。

start.go

ContainerStart

启动容器进程。10 个阶段(mount、network、OCI spec、containerd task、runc 启动、状态更新)。最复杂的文件。

stop.go

ContainerStop

发停止信号 → 等超时 → SIGKILL 兜底

kill.go

ContainerKill

发指定信号;SIGKILL 时还会 wait 退出

pause.go

ContainerPause

冻结容器进程(cgroup freezer)

unpause.go

ContainerUnpause

解冻

restart.go

ContainerRestart

stop + start,中间避免重复 mount/unmount

delete.go

ContainerRm

删容器:停 → 释放 RWLayer → 删目录 → 释放名字和网络

wait.go

ContainerWait

阻塞等待容器达到某状态,返回 <-chan StateStatus

状态机简图

        create              start
   ┌──────────┐  ────►  ┌──────────┐
   │ created  │         │ running  │ ◄──── start (重启)
   └──────────┘         └──────────┘
        │                  │      │
        │ rm               │ stop │ pause
        ▼                  ▼      ▼
   ┌──────────┐     ┌──────────┐  ┌──────────┐
   │  dead    │     │ stopped  │  │  paused  │
   └──────────┘     └──────────┘  └──────────┘
                          │            │
                          │ start      │ unpause
                          └─────┬──────┘
                                ▼
                            running

5.2 容器观测(只读操作)

文件

公开 API

干什么

inspect.go

ContainerInspect

返回容器完整 JSON(状态、配置、网络、挂载、健康检查…)

list.go

Containers

docker ps 的实现,支持过滤、分页、按 ancestor/label/status 等过滤

logs.go

ContainerLogs

读容器日志(支持 follow、since、until、tail),返回 <-chan *LogMessage

stats.go

ContainerStats

容器 CPU/内存/网络 IO 指标,支持流式推送

top.go

ContainerTop

容器内进程列表(类似 docker top

changes.go

ContainerChanges

容器文件系统相对镜像的改动(A/D/C 三种)

5.3 容器交互

文件

公开 API

干什么

attach.go

ContainerAttach

接管容器 stdin/stdout/stderr,支持 detach key

exec.go

ContainerExecCreate / ContainerExecStart

在运行中的容器内执行额外命令

resize.go

ContainerResize / ContainerExecResize

调整 TTY 大小

5.4 容器文件系统

文件

公开 API

干什么

archive.go

ContainerArchivePath / ContainerExtractToDir / ContainerStatPath

docker cp 的实现

export.go

ContainerExport

把容器文件系统导出为 tar 流

commit.go

CreateImageFromContainer

把容器当前状态提交为新镜像

5.5 配置变更

文件

公开 API

干什么

rename.go

ContainerRename

改容器名(要联动改 DNS、link、网络 sandbox 名字)

update.go

ContainerUpdate

更新容器配置(资源限制、重启策略等,运行中也能改)

5.6 系统级清理

文件

公开 API

干什么

prune.go

ContainerPrune / NetworkPrune

清理已停止的容器 / 无用网络,带 pruneRunning 原子标记防并发

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...  上报指标                │
└─────────────────────────────────────────────────────────────┘

更多推荐