很多接触过 Docker 的人听说 containerd 时,第一反应是:"又多了一个容器技术?它和 Docker 到底什么关系?"
这篇文章从容器技术的分层讲起,把 Docker 和 containerd 的关系彻底说清楚。
一、一句话先总结
Docker 是一整套容器管理工具,containerd 是它底层的容器运行时引擎。
用汽车来比喻:
Docker = 一辆完整的汽车(方向盘、油门、仪表盘、用户手册……)
Containerd = 汽车发动机(只管"转",不管"怎么开")Docker 让用户通过 CLI/API 操控容器;containerd 在幕后真正执行容器的创建、启动、停止等底层操作。
二、看一条命令的执行链路
当你运行:
docker run -d --name web -p 8080:80 nginx背后发生了这些:
docker CLI
│
▼
dockerd(Docker 守护进程)
│
├── 解析镜像名,拉取 nginx 镜像
├── 创建容器配置(端口映射、网络、挂载……)
│
▼
containerd ← 这是 containerd!
│
▼
containerd-shim ← 每个容器一个 shim 进程
│
▼
runc ← OCI 运行时,真正创建容器的进程
│
▼
nginx 容器(在独立的命名空间中运行)Docker 负责"上层管理",containerd 负责"底层执行"。
三、从历史看关系
理解 Docker 和 containerd 的关键是知道它们是怎么来的。
阶段一:Docker 一家独大(2013-2016)
Docker 早期是一个巨大的单体守护进程(dockerd),什么都干:
- 镜像构建
- 镜像存储
- 容器运行时
- 网络管理
- 卷管理
- REST API
- CLI
阶段二:OCI 标准分拆(2015-2017)
业界不想被 Docker 一家绑定,于是成立了 OCI(Open Container Initiative),制定了两套标准:
| 标准 | 规范内容 |
|---|---|
| OCI Image Spec | 镜像长什么样( layers、config、manifest) |
| OCI Runtime Spec | 容器运行时怎么工作(namespace、cgroup、rootfs) |
Docker 把底层的运行时组件捐给了 OCI,这就是 runc——一个轻量级容器运行时。
阶段三:containerd 独立(2017)
Docker 把 containerd 也捐给了 CNCF(云原生计算基金会),containerd 成为 CNCF 毕业项目。Docker 自己也开始使用 containerd 作为默认的底层运行时。
2013 2015 2017 现在
│ │ │ │
│ Docker 单体架构 │ │
│ ┌──────────────┐ │ │
│ │ dockerd │ │ │
│ │ (一体化) │ │ │
│ └──────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ OCI 标准诞生 │ │
│ runc 独立 │ │
│ │ │ │
│ ▼ │ │
│ containerd 独立 │
│ Docker 改用 │
│ containerd │
│ │ │ │
│ ▼ │ │
│ Docker = dockerd │
│ + containerd + runc │四、Docker 和 containerd 的功能对比
| 维度 | Docker | Containerd |
|---|---|---|
| 定位 | 完整的容器管理平台 | 轻量容器运行时 |
| CLI | docker 命令全家桶 |
ctr(调试用)、nerdctl(类 Docker CLI) |
| REST API | Docker API(v1.47+) | 自带 gRPC API(不对外暴露 REST) |
| 镜像构建 | ✅ Dockerfile + docker build |
❌ 需配合 buildkit 或 Docker |
| 镜像推送 | ✅ docker push |
✅ 通过 ctr/nerdctl |
| 网络管理 | ✅ 内置 bridge/host/overlay | ❌ 需配合 CNI 插件 |
| 卷管理 | ✅ 完整卷驱动 | ❌ 需外部工具 |
| 容器编排 | ✅ Docker Compose / Swarm | ❌ 需配合 Kubernetes |
| Kubernetes 集成 | ❌ 已弃用(通过 cri-dockerd 桥接) | ✅ 原生 CRI 实现 |
| 资源占用 | 较大(含完整的上层逻辑) | 极小(仅运行时的核心功能) |
从资源占用角度看
# 看看进程数
ps aux | grep -E 'docker|containerd' | grep -v grep
# Docker 环境下的进程
root dockerd ← Docker 守护进程
root containerd ← containerd 守护进程
root containerd-shim ← 每个容器一个 shim
root nginx ← 容器进程本身
# 纯 containerd 环境(无 Docker)
root containerd ← containerd 守护进程
root containerd-shim ← 每个容器一个 shim
root nginx ← 容器进程本身纯 containerd 省去了 dockerd 这个进程,以及它附带的所有上层逻辑。
五、Kubernetes 与两者的关系
这是 containerd 和 Docker 在生态位上的最大分水岭。
Kubernetes 的 CRI 抽象
K8s 不想绑定任何特定的容器运行时,因此定义了 CRI(Container Runtime Interface)——一个标准 gRPC 接口,只要实现了 CRI 的运行时都能被 K8s 使用。
Kubernetes
│
├── CRI(标准接口)
│ │
│ ├── containerd ← 原生 CRI 实现(推荐)
│ │
│ ├── CRI-O ← 另一个轻量 CRI 实现
│ │
│ └── cri-dockerd ← Docker 的 CRI 桥接(已弃用)
│
▼
runc / crun / kata(OCI 运行时)Docker 在 K8s 中的退场
Kubernetes v1.24+ 正式移除了 dockershim(内置的 Docker CRI 桥接)。这意味着:
- K8s 不再原生支持 Docker 作为容器运行时
- 如果你想用 Docker,需要额外安装 cri-dockerd(社区维护的桥接器)
- containerd 是 K8s 官方推荐的默认运行时
为什么 K8s 要弃用 Docker?
不是因为 Docker 不好,而是因为:
- 多余的抽象层:K8s 只需要运行容器,Docker 提供了太多 K8s 用不到的功能(如 Docker Compose、Swarm、镜像构建)
- 性能损耗:每个操作都要经过 dockerd → containerd → runc 三层,而 containerd 可以直接到 runc
- 稳定性:containerd 直接实现 CRI,少了一层桥接就少了一个故障点
六、Docker 依然有用的场景
虽然 containerd 在 K8s 中取代了 Docker,但这不意味着 Docker 过时了。
Docker 不可替代的场景
| 场景 | 原因 |
|---|---|
| 本地开发 | docker build / docker compose 是最顺手的工具链 |
| CI/CD 流水线 | 构建镜像、推送镜像离不开 Docker |
| 单机部署 | 不跑 K8s 的环境,Docker Compose 比 containerd + nerdctl 方便得多 |
| 镜像构建 | containerd 没有内置镜像构建能力(需 buildkit) |
| 生态完善度 | Docker Hub、Docker Extension、大量第三方工具集成 |
哪些场景更适合 containerd?
| 场景 | 原因 |
|---|---|
| 生产环境 K8s | containerd 是 K8s 原生 CRI 实现,更轻量更稳定 |
| 边缘计算 | 资源受限的设备上,去掉 dockerd 能省不少内存 |
| IoT 设备 | 只需跑几个固定容器,不需要完整的 Docker 工具链 |
七、实际体验对比
Docker 环境
# 安装
apt install docker.io
# 启动容器
docker run -d --name nginx nginx
# 构建镜像
docker build -t myapp .
# 查看容器
docker ps
# 查看日志
docker logs nginx纯 containerd 环境
# 安装
apt install containerd
# 启动容器(需 nerdctl)
nerdctl run -d --name nginx nginx
# 查看容器
nerdctl ps
# 查看日志
nerdctl logs nginx
# 或者用 ctr(containerd 自带 CLI,更底层的调试工具)
ctr images pull docker.io/library/nginx:alpine
ctr run docker.io/library/nginx:alpine nginx
nerdctl是一个兼容 Docker CLI 语法的 containerd 客户端,由 containerd 社区维护。如果你从 Docker 迁移到 containerd,nerdctl几乎可以无缝替换docker。
八、总结
Docker 和 containerd 不是"二选一"的关系,而是"层次分工"的关系。 Docker 用户视角
┌─────────────────────────────────────────┐
│ docker build / docker run / docker ps │ ← 用户接触的 CLI
├─────────────────────────────────────────┤
│ dockerd │ ← Docker 守护进程(管理镜像、网络、卷……)
├─────────────────────────────────────────┤
│ containerd │ ← 容器运行时核心(真正的"干活"的)
├─────────────────────────────────────────┤
│ containerd-shim + runc │ ← OCI 运行时(创建容器进程)
├─────────────────────────────────────────┤
│ nginx 容器 │ ← 最终运行的容器
└─────────────────────────────────────────┘ K8s 用户视角
┌─────────────────────────────────────────┐
│ kubectl run / apply │ ← K8s CLI
├─────────────────────────────────────────┤
│ kubelet + CRI │ ← K8s 节点代理
├─────────────────────────────────────────┤
│ containerd │ ← CRI 实现(原生,无桥接)
├─────────────────────────────────────────┤
│ containerd-shim + runc │ ← OCI 运行时
├─────────────────────────────────────────┤
│ nginx 容器 │ ← 最终运行的容器
└─────────────────────────────────────────┘一句话最终版:
Docker 是"开车的人"——方向盘、油门、仪表盘一应俱全,适合开发者和单机场景。containerd 是"发动机"——只管高效稳定地运转,适合 K8s 和生产环境。它们本是同一件事的不同抽象层次,不是竞争对手。