文章目录
- [containerd 与 docker-ce 的区别](#containerd 与 docker-ce 的区别)
-
- 核心结论
- 本文路线
- 一、先建立正确的整体认知
-
- [1. 两种接入链路](#1. 两种接入链路)
- [2. 各组件职责](#2. 各组件职责)
- [3. 三个容易搞错的地方](#3. 三个容易搞错的地方)
- [二、Kubernetes 1.24 之前:两条路线并行](#二、Kubernetes 1.24 之前:两条路线并行)
-
- [1. 路线 A:Docker Engine + 内置 dockershim](#1. 路线 A:Docker Engine + 内置 dockershim)
- [2. 路线 B:独立 containerd + CRI](#2. 路线 B:独立 containerd + CRI)
- [3. 两条路线对比](#3. 两条路线对比)
- [三、Kubernetes 1.24 --- 1.28:核心变化是 dockershim 被移除](#三、Kubernetes 1.24 — 1.28:核心变化是 dockershim 被移除)
-
- [1. 路线 A:containerd + kubeadm(推荐学习)](#1. 路线 A:containerd + kubeadm(推荐学习))
-
- [第一步:安装并检查 containerd](#第一步:安装并检查 containerd)
- [第二步:配置 containerd](#第二步:配置 containerd)
- [第三步:安装 Kubernetes 组件并初始化](#第三步:安装 Kubernetes 组件并初始化)
- [2. 路线 B:Docker Engine + cri-dockerd](#2. 路线 B:Docker Engine + cri-dockerd)
- [3. 1.24 到 1.28 之间还要注意什么](#3. 1.24 到 1.28 之间还要注意什么)
- [四、Kubernetes 1.28 之后:判断标准回到 CRI 兼容性](#四、Kubernetes 1.28 之后:判断标准回到 CRI 兼容性)
-
- [1. cgroup 驱动自动检测](#1. cgroup 驱动自动检测)
- [2. containerd 1.x 与 2.x 的配置差异](#2. containerd 1.x 与 2.x 的配置差异)
- [3. 别忽略 CRI 插件状态](#3. 别忽略 CRI 插件状态)
- 五、三个阶段放在一起比较
- [六、从安装到运行 Pod:两条路线的动作差异](#六、从安装到运行 Pod:两条路线的动作差异)
-
- [方案 A:独立 containerd](#方案 A:独立 containerd)
- [方案 B:Docker Engine + cri-dockerd](#方案 B:Docker Engine + cri-dockerd)
- 参考资料
containerd 与 docker-ce 的区别
本文回答 Kubernetes 运维面试里的经典问题:为什么早期 Kubernetes 可以直接把 Docker 当作容器运行时,而 1.24 之后很多教程却要求安装 containerd?
核心结论
- 不是二选一。 Docker Engine 内部本来就使用 containerd 管理容器生命周期,两者处于不同抽象层级。
- 1.24 是分界线。 早期 kubelet 通过内置的
dockershim兼容 Docker Engine;1.24 起dockershim被移除,kubelet 不能再把 Docker Engine 直接当作运行时。 - 想继续用 Docker 也可以。 需要额外安装适配器
cri-dockerd;更直接的方式是让 kubelet 通过 CRI 接口连接 containerd。 - 1.28 不是分界线。 官方在 1.20 宣布弃用
dockershim,1.24 正式移除;1.28 前后的变化更多属于运行时兼容机制、版本支持与部署细节的演进。
本文路线
| 阶段 | 关注点 |
|---|---|
| Kubernetes 1.24 之前 | Docker Engine 与 containerd 都可以接入,区别在控制链路 |
| Kubernetes 1.24 --- 1.28 | dockershim 移除后,两条主流路线分别怎么部署 |
| Kubernetes 1.28 之后 | cgroup 驱动自动检测、CRI 版本兼容、containerd 1.x/2.x 配置差异 |
最后附三阶段对照表、部署动作对照与面试问题清单。
一、先建立正确的整体认知
1. 两种接入链路
Docker Engine 方案
text
kubelet
│
│ CRI
▼
cri-dockerd(适配器)
│
▼
Docker Engine / dockerd
│
▼
containerd → runc → Linux 容器
containerd 方案
text
kubelet
│
│ CRI
▼
containerd(CRI 插件)
│
▼
runc → Linux 容器
2. 各组件职责
| 组件 | 职责 |
|---|---|
| Docker Engine | 面向用户的容器管理系统,提供 docker 命令、Docker API、镜像构建、网络、卷等功能 |
| containerd | 容器运行时组件,负责镜像传输与管理、容器生命周期 |
| runc | 更底层的 OCI 运行时,按 OCI 规范创建和运行容器进程 |
| CRI | Kubernetes 的容器运行时接口,规定 kubelet 如何与容器运行时通信 |
| dockershim / cri-dockerd | 适配层,让不直接实现 CRI 的 Docker Engine 能够接入 kubelet |
关键点:Docker Engine 内部本来就使用 containerd。所以从 Docker 方案迁移到 containerd 方案,不是把 Docker 容器换成另一种容器格式,而是改变 Kubernetes 管理容器时所走的控制链路。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
3. 三个容易搞错的地方
① 装了 Docker,不等于 Kubernetes 通过 Docker Engine 管理 Pod。
例如在 Ubuntu 上执行:
bash
docker build -t myapp:v1 .
docker push myregistry.example.com/myapp:v1
这只是用 Docker 构建、推送镜像。即使 Kubernetes 节点只装了 containerd,也完全能运行这个镜像------因为它们使用通用的 OCI 镜像格式。
② 装了 containerd,不等于可以直接用 docker ps。
containerd 常用 ctr、crictl 等工具,而不是 Docker CLI。
③ 表里的 socket 路径与 kubelet 参数都是历史实现。
不要把旧版本的参数原样复制到新版本集群。
二、Kubernetes 1.24 之前:两条路线并行
这个阶段要区分两条路线,而不是认为当时只有 Docker。
1. 路线 A:Docker Engine + 内置 dockershim
早期 Kubernetes 直接集成了一个名为 dockershim 的适配组件,kubelet 借助它调用 Docker Engine,Docker Engine 再通过自身内部的 containerd 等组件运行容器。
典型调用链
text
kubelet
│
▼
内置 dockershim
│
▼
Docker Engine(dockerd)
│
▼
containerd → runc
典型部署过程
- 在所有节点安装 Docker Engine。
- 在所有节点安装 kubelet、kubeadm、kubectl。
- 配置 Docker 与 kubelet 的 cgroup 驱动一致。
- 通过 kubeadm 初始化控制平面。
- 工作节点加入集群。
- 安装 CNI 网络插件。
- 验证 Pod 能否正常运行。
以上为历史版本示例,具体命令取决于系统和版本:
bash
docker --version
systemctl status docker
# 仅适用于相应版本、相应配置的历史部署流程
kubeadm init --pod-network-cidr=<你的Pod网段>
重要区别:不需要因为 Docker 内部已经包含 containerd,就再给 Kubernetes 配置一个独立的 containerd 服务。在这条旧路线里,kubelet 走的是 dockershim → Docker Engine,而不是直接连接独立 containerd 的 CRI 接口。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
2. 路线 B:独立 containerd + CRI
即使在 1.24 之前,也可以直接使用 containerd。
text
kubelet
│
│ CRI
▼
containerd 的 CRI 插件
│
▼
runc
│
▼
Linux 容器
部署步骤与 Docker 路线类似,但运行时安装和配置不同:
- 安装 containerd。
- 确认 CRI 插件可用。
- 配置 containerd 的 cgroup 驱动。
- 安装 kubeadm、kubelet、kubectl。
- 指定 containerd 的 CRI socket 初始化集群。
- 安装 CNI 插件并验证 Pod。
containerd 1.x 常见的 CRI socket:
text
/run/containerd/containerd.sock
使用 kubeadm 时明确指定:
bash
kubeadm init \
--cri-socket unix:///run/containerd/containerd.sock \
--pod-network-cidr=<你的Pod网段>
在旧版本 kubeadm 中,运行时检测行为与配置要求可能有差异,显式指定 socket 有助于避免检测到多个运行时后产生歧义。
3. 两条路线对比
| 对比项 | Docker Engine + dockershim | 独立 containerd |
|---|---|---|
| Kubernetes 接入方式 | 通过内置 dockershim | 通过 CRI 插件 |
| kubelet 连接对象 | Docker 适配链路 | containerd CRI socket |
| 是否需要安装 Docker Engine | 需要 | 不需要 |
| 是否需要独立安装 containerd | 通常不需要另外安装 | 需要 |
| 常用容器管理工具 | docker |
crictl、ctr |
| 典型 socket | 旧版 dockershim socket | /run/containerd/containerd.sock |
| 能否运行 OCI 镜像 | 能 | 能 |
三、Kubernetes 1.24 --- 1.28:核心变化是 dockershim 被移除
从 1.24 开始,变化不在于 Docker 不能运行容器 ,而在于 Kubernetes 不再自带连接 Docker Engine 的 dockershim。此后有两条主流路线。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
1. 路线 A:containerd + kubeadm(推荐学习)
节点上只需要两个关键组件:kubelet 与 containerd,kubelet 通过 CRI 连接 containerd 的 CRI 插件。以下以 Ubuntu 22.04 为例。
第一步:安装并检查 containerd
bash
sudo apt update
sudo apt install -y containerd
# 启动并查看服务状态
sudo systemctl enable --now containerd
sudo systemctl status containerd
检查 CRI 是否正常(crictl 需另外安装):
bash
sudo crictl \
--runtime-endpoint unix:///run/containerd/containerd.sock \
info
crictl info 能成功返回运行时信息,比只看到 containerd 服务处于 active 更能证明 CRI 接口可用。
第二步:配置 containerd
先备份配置,再生成默认配置:
bash
sudo mkdir -p /etc/containerd
sudo cp /etc/containerd/config.toml \
/etc/containerd/config.toml.bak 2>/dev/null || true
sudo containerd config default |
sudo tee /etc/containerd/config.toml >/dev/null
对于 containerd 1.x,确认以下配置为 true:
toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
改完重启服务:
bash
sudo systemctl restart containerd
两个坑
containerd config default会覆盖原有配置文件内容,生产环境不能无条件直接执行。- containerd 2.x 的配置段名称不同,不能照抄 1.x 的配置路径(见第四章第 2 节)。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
第三步:安装 Kubernetes 组件并初始化
在所有节点安装匹配版本的 kubeadm、kubelet、kubectl,然后在控制平面节点执行:
bash
sudo kubeadm init \
--cri-socket unix:///run/containerd/containerd.sock \
--pod-network-cidr=<你的Pod网段>
上面是命令模板,Pod 网段必须与所选 CNI 插件的配置一致。工作节点使用初始化输出中的
kubeadm join命令加入集群。
2. 路线 B:Docker Engine + cri-dockerd
如果希望继续用 Docker Engine 管理 Kubernetes Pod,也可以做到,但要额外安装适配器 cri-dockerd。
调用链变为:
text
kubelet
│
│ CRI
▼
cri-dockerd
│
▼
Docker Engine
│
▼
Docker 内部的 containerd → runc
额外部署步骤
- 安装 Docker Engine。
- 安装并启动
cri-dockerd。 - 确认
cri-dockerd的 socket。 - 安装 kubeadm、kubelet、kubectl。
- 初始化集群时显式指定
cri-dockerdsocket。 - 检查 Docker、cri-dockerd、kubelet 的状态和日志。
常见的 Linux socket:
text
unix:///run/cri-dockerd.sock
初始化时指定:
bash
sudo kubeadm init \
--cri-socket unix:///run/cri-dockerd.sock \
--pod-network-cidr=<你的Pod网段>
具体 socket 路径取决于
cri-dockerd的安装方式和服务配置,部署前应检查实际值。
为什么不直接用 Docker? 因为 Docker Engine 并不原生实现 Kubernetes 所需的 CRI 接口,cri-dockerd 负责把 kubelet 的 CRI 请求转换为 Docker Engine 能处理的操作。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
因此,Kubernetes 1.24 以后:
| 操作 | 是否仍然可行 |
|---|---|
docker build 构建镜像 |
✅ 可以 |
docker push 推送镜像 |
✅ 可以 |
| Docker Engine 运行普通容器 | ✅ 可以 |
| kubelet 依靠内置 dockershim 直接连接 Docker Engine | ❌ 不可以 |
| 由 Docker Engine 管理 Kubernetes Pod | ⚠️ 需要额外安装 cri-dockerd |
3. 1.24 到 1.28 之间还要注意什么
| Kubernetes 版本 | 关键变化或注意事项 |
|---|---|
| 1.24 | 移除内置 dockershim;Docker Engine 路线需要 cri-dockerd,或改用其他 CRI 运行时 |
| 1.25 | 延续上述规则;仍要注意旧运行时的 CRI API 兼容性 |
| 1.26 | kubelet 要求运行时支持 CRI v1 API;不支持该 API 的运行时无法正常完成注册 |
| 1.27 | Docker 与 containerd 的接入路线没有因该版本发生根本变化 |
| 1.28 | 引入 cgroup 驱动自动检测的 Alpha 特性,但不代表默认自动检测,也不代表 Docker 从此不能使用 |
1.26 是额外的兼容性节点 :官方文档明确指出,1.26 及之后的 kubelet 要求容器运行时支持 CRI v1 API。
对 containerd 而言,不能只看大版本号。例如 1.5、1.6、1.7 的 CRI 支持情况存在差异。搭建 1.26---1.28 集群时,应选择明确支持 CRI v1 的兼容版本,而不是随意安装旧教程里的包。
四、Kubernetes 1.28 之后:判断标准回到 CRI 兼容性
先纠正一个常见误解:1.28 之后并没有把 containerd 变成唯一可用运行时,也没有彻底禁止 Docker Engine。
真正的判断标准仍然是:运行时能否通过 Kubernetes 要求的 CRI 接口工作,以及它与当前 Kubernetes 版本是否兼容。
1. cgroup 驱动自动检测
早期部署教程通常要求手动把 kubelet 和 containerd 的 cgroup 驱动设为一致的值(例如 systemd),原因是 Linux 用 cgroup 管理容器的 CPU、内存等资源,而常见的两种驱动语义不同:
| 驱动 | 含义 |
|---|---|
systemd |
由 systemd 管理 cgroup 层级 |
cgroupfs |
运行时直接通过 cgroup 文件系统管理层级 |
如果 kubelet 用 systemd 而运行时用 cgroupfs,就可能产生资源管理方面的问题,因此在 kubeadm 集群中通常建议两边都使用 systemd。
1.28 引入的自动检测属于 Alpha 阶段能力,需要相应的特性门控配置。不能因为版本号到了 1.28,就认为已经自动完成检测。
对学习用集群,最稳妥的做法仍是显式配置:
toml
# containerd 1.x
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
并确保 kubelet 也使用 systemd 驱动。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
2. containerd 1.x 与 2.x 的配置差异
如果看到两份教程,一份配置正常、另一份却提示找不到配置项,很可能是因为它们用了不同的大版本。
| 配置项 | containerd 1.x | containerd 2.x |
|---|---|---|
| CRI 插件配置段 | io.containerd.grpc.v1.cri |
运行时与镜像配置拆分为新的插件命名空间 |
| 常见运行时配置段 | plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options |
plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options |
| cgroup 配置 | SystemdCgroup = true |
同样需要 SystemdCgroup = true |
| 能否照搬旧配置 | 仅适用于对应的 1.x 版本 | 不建议照搬 1.x 的完整配置 |
2.x 的写法:
toml
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
这并不是说 1.28 强制要求 containerd 2.x,而是提醒:Kubernetes 版本与 containerd 版本是两个独立的版本维度,配置必须匹配实际安装的 containerd。
!\[03_云原生/云原生技术栈/Kubernetes/image/dff28e2de498ef6a0822deea958e115e_MD5.png]
3. 别忽略 CRI 插件状态
有时 containerd 服务正常,但 kubeadm init 仍然失败,这时需要检查 CRI 插件是否启用:
bash
sudo ctr plugins ls | grep -i cri
如果插件状态异常,继续查看配置:
bash
grep -n -A5 -B2 'disabled_plugins' /etc/containerd/config.toml
若 cri 被写进 disabled_plugins 列表,containerd 就无法提供 Kubernetes 所需的 CRI 服务。修正后重启并复查:
bash
sudo systemctl restart containerd
sudo crictl \
--runtime-endpoint unix:///run/containerd/containerd.sock \
info
ctr plugins ls 看插件状态,crictl info 检查 CRI 接口,两者作用不同------不能只看 containerd 进程是否启动。
五、三个阶段放在一起比较
面试被问到"用 Docker 部署和用 containerd 部署有什么区别",可以从这几个维度展开。
| 比较维度 | K8s 1.24 之前 | K8s 1.24 --- 1.28 | K8s 1.28 之后 |
|---|---|---|---|
| Docker Engine 接入 | 内置 dockershim | 需要 cri-dockerd | 仍可使用 cri-dockerd |
| containerd 接入 | 通过 CRI 插件 | 通过 CRI 插件 | 通过 CRI 插件 |
| 是否必须安装 Docker | 否 | 否 | 否 |
| 是否必须安装 containerd | 取决于路线,Docker 内部本来就使用它 | 取决于路线,独立 containerd 是常见选择 | 取决于路线 |
| Docker CLI 是否必须 | Docker 路线需要 | 仅 Docker 路线需要 | 仅 Docker 路线需要 |
| kubelet 如何连接运行时 | 旧版 Docker 路线经过 dockershim | Docker 路线经过 cri-dockerd;containerd 路线直连 CRI | 基本同上 |
CRI v1 兼容性 |
视 Kubernetes 与运行时版本而定 | 1.26 起是关键要求 | 必须满足当前 Kubernetes 的要求 |
| cgroup 驱动 | 需要关注一致性 | 需要关注一致性 | 仍需关注,新版本有进一步的自动检测能力 |
| 配置文件 | 取决于 Docker / containerd 版本 | 取决于 cri-dockerd 或 containerd 版本 | 还需区分 containerd 1.x / 2.x |
| 新建学习集群的建议 | 适合研究历史架构 | 建议直接使用 containerd | 建议使用兼容的 containerd 版本 |
官方文档明确说明:Docker Engine 不实现 Kubernetes 所需的 CRI,因此需要额外的 cri-dockerd;而 containerd 可以通过其 CRI 插件直接提供接口。
六、从安装到运行 Pod:两条路线的动作差异
方案 A:独立 containerd
- 安装 containerd 与 crictl。
- 配置 CRI 插件、
SystemdCgroup和镜像仓库。 - 确认
containerd.service正常、crictl info成功。 - 安装匹配版本的 kubeadm、kubelet、kubectl。
- 通过
--cri-socket unix:///run/containerd/containerd.sock初始化集群。 - 安装 CNI,加入工作节点,验证 Pod。
方案 B:Docker Engine + cri-dockerd
- 安装 Docker Engine。
- 安装并配置
cri-dockerd服务。 - 确认 Docker 与
cri-dockerd都正常运行。 - 安装匹配版本的 kubeadm、kubelet、kubectl。
- 通过
--cri-socket unix:///run/cri-dockerd.sock初始化集群。 - 安装 CNI,加入工作节点,验证 Pod。
三点提醒
- 两种方案都要求每个节点安装并配置对应的运行时,而不是只装在 Master 上;Master 上的控制平面组件同样运行在容器里。
- 容器运行时解决容器创建与生命周期管理,CNI 负责 Pod 网络,kubeadm 负责集群初始化与节点加入------三者不是同一个东西。
- 排查顺序建议:
systemctl status containerd→ctr plugins ls→crictl info→journalctl -u kubelet。