Docker、containerd、runc 的关系

一句话讲清三者的关系

如果用一个表格概括:

组件角色类比
Docker面向开发者的全栈工具,提供 CLI、镜像构建、容器管理4S 店:你只需要说”我要买车”,剩下的它帮你搞定
containerd容器生命周期的管理者,管理镜像、存储、网络、进程汽车总装厂:负责调度生产线、管理零部件库存
runc真正创建并启动容器进程的底层工具,启动完就退出流水线上拧最后一颗螺丝的机器人,拧完就走

三者的调用关系是:

docker CLI  →  dockerd  →  containerd  →  containerd-shim  →  runc
(你的命令)    (API网关)    (容器管家)       (进程保姆)         (容器启动器)

下面逐层展开。


一、Docker:开发者的一站式工具箱

Docker 是一套完整的平台工具。你日常用的 docker build、docker run、docker push 全部来自它。

当你执行 docker run -d nginx 时:

  1. docker CLI 把请求发给 dockerd(Docker 守护进程)
  2. dockerd 负责编排接下来的所有事情:拉镜像、配网络、启容器

Docker 的职责是将镜像构建(BuildKit)、镜像分发、网络配置、卷存储封装成一条条简单命令。开发者的日常工作只需要和 Docker 交互。

值得注意的是,dockerd 并不直接操作容器进程——它把容器启动和生命周期管理委托给 containerd,自己专注于 API 网关、镜像构建调度、网络与存储编排等高阶工作。


二、containerd:容器的”管家”

containerd 是 CNCF 的毕业项目,也是容器生态的基础设施。它是一个独立的守护进程,专门管理容器的完整生命周期。Docker、Kubernetes 等上层平台都通过它来操控容器。

containerd 的核心职责:

  • 镜像管理:pull、push、存储、分层管理
  • 容器生命周期:create、start、stop、delete、pause、resume
  • 快照存储:通过 snapshotter 插件管理容器文件系统
  • 任务执行:调用 runc 创建实际进程,并通过 shim 进程持续监控

在 Docker 体系内,分工是这样的:

  • dockerd 负责镜像构建(BuildKit)、网络编排、卷管理、用户 API
  • containerd 负责拉镜像、存镜像、管理镜像层、启动/停止容器

containerd 不关心上层是谁(Docker 还是 Kubernetes),它只管把容器管好。这种解耦意味着:你可以用 Docker 构建镜像,用 Kubernetes 直接对接 containerd 运行,同一套镜像体系通吃。


三、runc:容器的”启动器”

如果说 containerd 是管家,那 runc 就是管家手下的执行者。

3.1 runc 只做一件事

runc 的职责极其单一:读取一份符合 OCI Runtime Spec 的 JSON 配置文件,根据配置调用 clone() 创建 Namespace,设置 Cgroups,然后启动容器进程。做完这些,runc 自己就退出了。

它不是守护进程,不常驻后台。每次启动一个容器,containerd 就新开一个 runc 进程;容器启动完毕,runc 退出。容器进程由另一个角色接管——shim。

3.2 OCI 标准与 runc 的关系

OCI(Open Container Initiative,开放容器标准)定义了容器格式和运行时的行业规范,包含两个核心标准:

标准内容对应实现
OCI Runtime Spec定义”如何运行一个容器”:Namespace 如何配置、Cgroups 如何设置、根文件系统如何挂载runc 是参考实现
OCI Image Spec定义”镜像的格式”:文件系统层如何组织、元数据如何描述所有 Registry 和构建工具遵循此标准

这意味着:只要一个程序能生成符合 OCI Runtime Spec 的配置,就能用 runc(或任何兼容的 OCI 运行时)启动容器。Docker、containerd、Kubernetes 都通过这个标准与底层运行时解耦。


四、一次 docker run 的完整旅程

理解了每个组件的职责后,来看一个容器从创建到运行的完整过程:

1. docker run nginx
      │
      ▼
2. dockerd 收到请求,解析参数
      │
      ▼
3. dockerd 调用 containerd:帮我拉 nginx 镜像,然后启动它
      │
      ▼
4. containerd 连接 Registry 下载镜像的各层,存到本地 snapshotter
      │
      ▼
5. containerd 启动一个新进程——containerd-shim(进程保姆)
      │
      ▼
6. shim 调用 runc:给,这是 OCI 配置,帮我创建容器进程
      │
      ▼
7. runc 执行 clone() 创建 Namespace,设置 Cgroups,
   加载镜像文件系统,启动 nginx 主进程
      │
      ▼
8. runc 退出。nginx 进程交给 shim 接管
      │
      ▼
9. shim 持续监控 nginx 进程:
   - 收集 stdout/stderr
   - 向 containerd 报告退出状态
   - 容器退出后负责清理

shim 为什么重要

你可能会问:runc 启动容器后退出,那谁来看着容器进程?答案是 shim。

shim 的设计要解决两个问题:

  1. containerd 重启不影响容器:如果 containerd 升级或崩溃重启,shim 进程不受影响,容器继续运行。
  2. 收集退出状态:runc 退出后,如果它还是容器的父进程,容器退出会变成僵尸进程。shim 作为父进程,可以正确回收并报告退出信息。

五、Kubernetes 视角:Docker 是可选的

在 Kubernetes 集群中,容器运行时的链路是:

Kubernetes → containerd → runc

注意这里没有 dockerd。Kubernetes 直接对接 containerd 管理容器,因为:

  • Kubernetes 自己就通过 CNI 插件管理网络、CSI 插件管理存储,不需要 dockerd 再做一层
  • containerd 更轻量、更专注,资源开销更小
  • Kubernetes 只需要”管好容器进程”,不需要”帮开发者构建镜像”

但这不意味着 Docker 被淘汰了——镜像格式是 OCI 标准,去哪儿都能跑。开发者依然用 Docker 构建和测试镜像,把镜像推到 Registry,Kubernetes 拉下来通过 containerd 直接运行。Docker 和 Kubernetes 的分工是:Docker 管开发环节,containerd 管生产运行。


六、总结

Dockercontainerdrunc
给谁用开发者平台 / 编排系统更底层的运行时
做什么构建镜像、CLI 交互、完整工具链管理镜像传输和容器生命周期创建一个符合 OCI 标准的容器进程
运行方式守护进程(dockerd)守护进程一次性进程(启动完退出)
依赖关系依赖 containerd依赖 runc只依赖 Linux 内核
是否常驻是是否

理解这三者的分工,是理解现代容器技术栈的第一步。