广告位 · 页头(后台-模块管理-新建模块后替换此区域)

容器运行时 containerd 与 Docker 到底有什么区别?

如果直接问 containerd 和 Docker 哪个更好,这个问题本身需要先拆解。Docker 并非单一的容器运行时,而是一套完整的开发与运维平台,它把镜像构建、容器运行、网络配置、存储管理以及命令行交互全部封装在一起。containerd 则是 Docker 底层负责容器生命周期管理的核心组件,最初由 Docker 公司开发并捐赠给 CNCF,后来独立演进。因此,把两者放在同一维度比较并不准确,更合适的问法是:在特定场景下,应该使用完整的 Docker 平台,还是直接使用更轻量的 containerd 运行时。

容器运行时 containerd 与 Docker 到底有什么区别?

从调用链来看,用户执行 docker run 后,Docker 客户端会先连接 dockerd,dockerd 再通过 gRPC 调用 containerd,containerd 最终调用 runc 创建容器。对日常开发来说,这些细节被完全隐藏;但对 Kubernetes 这类编排系统来说,多余的适配层只会增加延迟和故障风险。理解了这层架构关系,后面的差异就很好解释。

一、从 Docker 架构看 containerd 的位置

Docker 在 1.11 版本之前几乎是一个单体应用,守护进程直接负责镜像、网络、存储和容器执行。后来 Docker 把容器执行层拆分为 containerd 和 runc,将这两个组件捐赠给 CNCF,自己则继续维护更上层的开发体验。containerd 负责镜像传输与存储、容器生命周期管理、挂载卷、网络命名空间等任务,runc 则严格遵循 OCI 规范完成最终的进程创建与隔离。Docker 平台仍然保留 Dockerfile 解析、镜像构建、Compose 编排、Swarm 集群和桌面端工具,这些功能并不属于 containerd。

正因为 containerd 只做核心运行时工作,它的侵入面更小,也更适合被 Kubernetes、Kata Containers 甚至边缘设备直接嵌入。很多云厂商和 Linux 发行版默认安装 containerd 而不安装 Docker,原因就在这里。Docker 的价值更多体现在开发者交互体验,而不是底层运行效率。如果把容器平台比作操作系统,containerd 就像内核中的进程管理模块,Docker 则是带图形界面和丰富工具链的完整发行版。

这种分层也解释了为什么社区会同时存在 nerdctl 和 ctr 两个命令行工具。ctr 是 containerd 自带的调试工具,命令参数与 Docker 差异很大;nerdctl 则是第三方提供的 Docker 兼容 CLI,让习惯 Docker 命令的用户可以平滑操作 containerd。接下来通过具体差异进一步说明。

二、containerd 与 Docker 的核心差异

最直观的差异体现在命令行和镜像管理方式上。Docker 使用 docker run、docker images 等命令,而 containerd 原生工具 ctr 需要显式指定完整镜像引用,并且参数风格更底层。实际使用中更多人选择 nerdctl,它几乎兼容 Docker 的命令习惯,也支持 Compose 的部分功能。下面是一组运行 hello 容器的命令对比:

# 使用 Docker
docker run --rm -it alpine echo hello
# 使用 nerdctl(兼容 Docker 命令)
nerdctl run --rm -it alpine echo hello
# 使用 containerd 自带工具 ctr
ctr images pull docker.io/library/alpine:latest
ctr run --rm docker.io/library/alpine:latest demo echo hello

从输出和交互体验看,Docker 提供了更友好的错误提示、进度条和日志格式化,而 containerd 及其工具更偏向脚本化和自动化场景。Docker 的镜像构建依赖 docker build 和 BuildKit,containerd 本身并不内置构建镜像的能力,需要配合 BuildKit 或 nerdctl build 使用。存储驱动方面,两者都支持 overlayfs,但目录结构和管理方式不同,Docker 镜像存储在 /var/lib/docker,containerd 默认存储在 /var/lib/containerd,不能直接互相读取。

性能层面,containerd 作为独立的 CRI 运行时,资源占用通常比完整 Docker 引擎更低。Docker 多了一层 dockerd 进程和 HTTP API 转发,在大量短生命周期容器场景下,额外的调度开销会被放大。不过对于普通开发环境,这种差异很难被感知,真正影响选型的是架构简化和运维一致性。生产集群如果只运行 Kubernetes,没有必要安装完整的 Docker。

三、为什么 Kubernetes 选择 containerd

Kubernetes 在早期版本通过 dockershim 适配 Docker,因为当时 Docker 是事实标准。但 Docker 本身并不遵循 Kubernetes 的 CRI 接口,kubelet 必须先把 CRI 请求转换成 Docker API 调用,Docker 再调用 containerd,形成了一条曲折的链路。随着 containerd 原生实现 CRI 插件,社区开始推动移除 dockershim。Kubernetes 1.24 版本正式移除了该组件,直接对接 containerd 或 CRI-O 成为主流做法。

移除 dockershim 之后,kubelet 通过本地 gRPC 接口直接与 containerd 通信,调用路径从 kubelet -> dockershim -> dockerd -> containerd -> runc 缩短为 kubelet -> containerd -> runc。这样做不仅减少了故障点,也降低了每个节点的内存和 CPU 开销,同时避免了 Docker 版本升级与 Kubernetes 兼容性之间的耦合。对集群管理员来说,节点初始化时只需要安装 kubelet 和 containerd,不再需要处理 Docker 的安装与配置。

可以通过以下命令查看当前节点的容器运行时信息,方便确认是否已经切换成功:

# 查看节点容器运行时
kubectl get nodes -o wide
# 登录节点后查看 containerd 版本
ctr version
# 查看 CRI 运行时类型
crictl info | grep runtimeType

四、实际选型与迁移建议

选型时通常遵循一个简单原则:开发环境用 Docker,生产集群用 containerd。Docker 的镜像构建、Compose 编排、桌面端可视化以及丰富的插件生态,能显著提升开发效率;而 containerd 更轻量、与 Kubernetes 集成更紧密,适合作为节点上的常驻运行时。如果团队同时维护多个集群,统一使用 containerd 可以减少节点组件差异,让故障排查更简单。

如果已有 Docker 节点需要迁移到 containerd,最简单的方式是重新拉取镜像,而不是手动导出导入。因为 Docker 和 containerd 的镜像存储目录不兼容,直接复制文件没有意义。离线环境可以使用以下命令导出和导入镜像:

# 在 Docker 节点导出镜像
docker save alpine:3.19 -o alpine.tar
# 在 containerd 节点导入镜像
ctr images import alpine.tar
# 或者使用 nerdctl 导入
nerdctl load -i alpine.tar

迁移过程中要注意,Docker 的一些高级功能在 containerd 中并不直接等价。例如 Docker 的 docker compose 需要替换为 nerdctl compose 或 Kubernetes 的清单文件;Docker Swarm 通常建议迁移到 Kubernetes。镜像构建可以使用 BuildKit 独立部署,或直接用 nerdctl build 完成。总的来说,containerd 和 Docker 不是替代关系,而是不同抽象层级的工具,理解它们各自的位置后,选型和迁移都会清晰很多。

广告位 · 文章底部(后台-模块管理替换此区域)