容器技术发展到今天,运行时层面的选择已经不再是Docker一家独大。在Kubernetes集群里,containerd和CRI-O已经成为主流选项,而Docker更多出现在开发者的本地环境中。很多刚接触容器生态的同学容易把这三者混为一谈,分不清它们各自处在技术栈的哪一层、职责边界在哪里。这篇文章将从架构原理入手,把三者的关系和差异讲清楚,并给出实际的选型建议。

先理清容器技术栈的分层架构
要理解三者的区别,必须先明白容器生态的分层。最底层是Linux内核提供的namespace、cgroup等隔离能力,任何运行时都离不开它们。中间层是所谓的高层运行时,负责镜像管理、网络挂载、容器生命周期管理等;最贴近内核的是低层运行时,比如runc,它真正调用系统调用创建容器进程。
Docker是一个完整的产品级工具链,包含CLI、API、镜像构建、卷管理、网络管理等一整套东西;containerd则是从Docker中拆分出来的高层运行时,专注于容器的核心生命周期管理;CRI-O是CNCF社区专门为Kubernetes打造的轻量运行时,实现了Kubernetes的CRI接口,除此之外什么都不做。可以说三者的定位从重到轻,依次是Docker、containerd、CRI-O。
一个容易忽略的事实是:Docker内部本身就包含containerd。当你在本地跑Docker时,实际的容器管理工作是由内嵌的containerd完成的,Docker只是在它之上叠加了构建、推送、卷、网络等增值功能。这也是为什么Kubernetes在1.24版本移除dockershim之后,很多人发现把Docker镜像迁移到containerd几乎不需要改动,因为底层本来就是同一套东西。
Docker与containerd:父子关系的来龙去脉
Docker最初是一个单体架构,所有功能都揉在一个守护进程里。随着容器编排系统的兴起,Kubernetes等平台需要的是一个稳定、精简的容器管理接口,而不是Docker这个大而全的产品。于是Docker公司在2017年把核心的容器管理部分剥离出来捐给了CNCF,这就是containerd。所以containerd可以被理解为Docker的内核,Docker是containerd的超级封装。
从功能覆盖上看,Docker提供了docker build、docker compose、卷插件、Swarm编排、友好的CLI等完整开发体验;containerd只提供ctr、nerdctl这类偏底层的工具,镜像构建能力需要借助buildkit单独部署。下面的命令展示了两者在拉取镜像时的差异:
# Docker 一条命令完成拉取并运行 docker run -d --name web -p 8080:80 nginx:alpine # containerd 原生工具 ctr,参数风格完全不同 ctr images pull docker.io/library/nginx:alpine ctr run -d docker.io/library/nginx:alpine web # 更推荐使用 nerdctl,体验接近 docker nerdctl run -d --name web -p 8080:80 nginx:alpine
在资源占用方面,containerd的优势非常明显。它只有一个常驻进程,内存占用通常在几十MB级别,而Docker守护进程加上containerd、containerd-shim等多个进程,整体开销更大。对于大规模节点集群或者边缘设备来说,这种差距会直接体现在成本上。另外,由于链路更短,containerd创建容器的延迟也更低,在Pod启动速度敏感的场景下有实际收益。
CRI-O:为Kubernetes而生的极简运行时
CRI-O由Red Hat主导开发,设计目标可以用一句话概括:只做Kubernetes需要的事。它精确实现了CRI(Container Runtime Interface)规范,能直接拉取符合OCI标准的镜像,调用runc或crun启动容器,没有任何多余功能。没有docker build,没有独立CLI产品化的使用流程,它的使用者就是kubelet。
这种极简设计带来了几个好处。第一是攻击面小,代码量远小于Docker,安全审计更容易,出现的CVE也相对较少。第二是依赖少,安装部署简单,配合podman、buildah等工具可以组成完整的无守护进程容器工作流。第三是与OpenShift深度集成,Red Hat的产品线天然选择CRI-O作为默认运行时。
当然CRI-O的局限也很清楚:它不能脱离Kubernetes独立使用,本地开发时几乎没有可用性可言。如果你的场景是纯粹的K8s节点运行时,CRI-O和containerd的能力基本等价,两者在主流版本的性能测试中差距很小,选择往往取决于团队的技术栈偏好和发行版习惯。
核心维度对比与选型建议
下面这张表从多个维度汇总了三者的差异:
| 对比维度 | Docker | containerd | CRI-O |
|---|---|---|---|
| 定位 | 完整容器平台 | 通用高层运行时 | K8s专用运行时 |
| CRI支持 | 需借助cri-dockerd | 原生支持 | 原生支持,为此而生 |
| 镜像构建 | 内置 | 需buildkit | 需buildah等外部工具 |
| 资源占用 | 较高 | 低 | 低 |
| 主要工具 | docker CLI | ctr、nerdctl | crictl |
| 典型场景 | 本地开发、CI构建 | K8s节点、云原生基础设施 | OpenShift、红帽系集群 |
具体到选型,可以遵循这样的思路。本地开发环境继续用Docker是最省心的选择,生态成熟、文档丰富、Compose一键起本地依赖。生产Kubernetes集群优先考虑containerd,它是目前各大云厂商托管集群的默认配置,社区活跃度和问题排查资料都最丰富。如果你的团队重度使用RHEL、CentOS Stream或OpenShift,CRI-O是顺理成章的搭配,与podman工具链的组合体验更统一。
还有一个实践中常见的组合方案:开发用Docker构建镜像推送到镜像仓库,集群节点用containerd拉取运行。由于三者的镜像都遵循OCI标准,镜像格式完全互通,不存在迁移障碍。需要注意的是镜像仓库配置文件的位置不同,containerd需要修改/etc/containerd/config.toml来添加私有仓库认证,而Docker是/etc/docker/daemon.json。
总结
Docker、containerd、CRI-O并不是互相替代的竞争关系,而是容器生态分层演进的结果。Docker胜在开发体验和工具链完整性,containerd胜在轻量、通用和云原生默认地位,CRI-O胜在专一和安全面小。理解了它们在架构栈中的位置,选型就变成了一个简单的场景匹配问题:开发用Docker,生产节点在containerd和CRI-O之间按团队技术栈二选一,这几乎是当前业界的主流共识。
containerdDockerCRI-O容器运行时容器技术对比修改时间:2026-09-01 05:09:01