运维知识
悠悠
2026年3月6日

别再傻傻分不清了!Containerd 和 Docker 到底有啥区别?命令、架构全拆解

前阵子有个刚入行的小伙伴跑来问我:“哥,我听说现在 Kubernetes 都不用 Docker 了,改用 Containerd,那是不是以后 Docker 就废了?我学 Docker 还有意义吗?”

我当时差点一口老血喷出来——这问题问得,就跟“微信被抖音干掉了,我还用不用装微信”一样离谱。其实啊,Docker 没死,只是它的一部分功能被“剥离”出去了,而那个被剥离出来的核心组件,就是 Containerd。

今天咱们就掰开揉碎了讲讲:Containerd 和 Docker 到底有啥区别?从命令行操作到底层架构,一条条给你捋清楚。这篇文章不灌鸡汤,全是我在生产环境里踩过的坑、调过的参数、看过的日志堆出来的干货。


先说个前提:Docker 其实是个“全家桶”。你平时用 docker run、docker build,背后其实是一整套工具链在干活。这套工具链里,真正负责和 Linux 内核打交道、创建容器、管理镜像的,并不是 Docker 本身,而是它内部的一个叫 containerd 的守护进程。

你可以把 Docker 想象成一个“外卖平台”——你点个“宫保鸡丁”,平台接单(Docker CLI),然后派单给后厨(containerd),后厨炒菜(拉镜像、启容器),最后骑手送餐(网络、存储等)。而 Containerd 呢,就是那个后厨,只管做菜,不管接单也不管送餐。

所以,Kubernetes 把 Docker “踢出局”,其实是说:“我不需要你这个外卖平台了,我自己会接单(Kubelet 直接调 containerd),你把后厨交出来就行。”于是 Docker 把 containerd 开源给了 CNCF(云原生基金会),现在它是个独立项目,谁都能用。


那从命令行角度看,差别大吗?

如果你习惯了 docker ps、docker images、docker exec,那你直接切到 containerd 上,会有点懵——因为 containerd 根本没有这些命令!

Containerd 提供的是 ctrcrictl 两个命令行工具。注意,这两个不是一回事:

  • ctr 是 containerd 自带的调试工具,功能齐全但不稳定,官方明确说“别在生产环境用”。
  • crictl 是 Kubernetes CRI(容器运行时接口)标准下的命令行工具,专为 K8s 设计,推荐在集群节点上使用。

举个例子:

你想看当前有哪些容器在跑:

# Docker 方式
docker ps

# ctr 方式(不推荐生产用)
ctr containers list

# crictl 方式(K8s 节点推荐)
crictl ps

再比如拉个镜像:

# Docker
docker pull nginx

# ctr
ctr image pull docker.io/library/nginx:latest

# crictl(注意:crictl 不能直接 pull,得通过 pod sandbox 或 runtime 接口间接拉)
# 所以通常 crictl 不用来拉镜像,而是配合 kubelet 自动拉

看到没?ctr 的命令更底层,参数也更啰嗦。而且 ctr 拉下来的镜像,默认命名空间是 default,而 K8s 用的是 k8s.io。如果你用 ctr 拉了个镜像,K8s 可能根本看不到——因为不在同一个命名空间里!

这就导致一个经典翻车现场:运维同学在 K8s 节点上用 ctr pull 了一个镜像,然后纳闷“为啥 Pod 还在 ImagePullBackOff”?结果一查,镜像在 default 命名空间,而 kubelet 在 k8s.io 里找,当然找不到。

正确的做法是:

ctr -n k8s.io image pull nginx:latest

加个 -n k8s.io 指定命名空间,这样 kubelet 才能认。


再说架构差异,这才是重点。

Docker 的架构是这样的(简化版):

用户 → Docker CLI → Docker Daemon (dockerd) → containerd → runc → Linux Kernel

其中:

  • Docker CLI:你敲的 docker 命令
  • Docker Daemon (dockerd):后台服务,负责解析命令、管理镜像、网络、卷等
  • containerd:负责容器生命周期管理(创建、启动、停止等)
  • runc:OCI 标准的容器运行时,真正调用 clone()、mount() 等系统调用创建容器

而纯 Containerd 的架构(比如在 K8s 中)是:

Kubelet → CRI Plugin → containerd → runc → Linux Kernel

这里少了 Docker Daemon 这一层。Kubelet 通过 CRI(Container Runtime Interface)直接和 containerd 对话。containerd 内置了 CRI 插件(以前是单独的 cri-containerd,现在合并了),所以不需要 dockerd。

这意味着什么?

  1. 更轻量:少了 dockerd,内存占用更低,启动更快。我们线上一个节点,切换到 containerd 后,系统负载降了 10% 左右。
  2. 更稳定:dockerd 曾经出过不少 bug,比如镜像清理异常、网络配置冲突。containerd 架构简单,故障面小。
  3. 更标准:CRI 是 Kubernetes 定义的标准接口,任何符合 CRI 的运行时(比如 containerd、CRI-O)都能无缝接入 K8s。而 Docker 并不原生支持 CRI,得靠 dockershim(一个适配层),这个 shim 在 K8s 1.20 之后就被废弃了。

不过,Docker 也不是一无是处。它的最大优势是开发体验好。本地开发时,docker build、docker-compose 一套下来丝滑流畅。而 containerd 没有 build 功能——它只能运行容器,不能构建镜像。

所以现实中的分工往往是:

  • 开发阶段:用 Docker 构建镜像、本地测试
  • 生产部署:用 containerd(或 CRI-O)跑在 K8s 上

两者根本不冲突,反而互补。


说到这儿,可能有人要问:“那我能不能在生产环境继续用 Docker?”

技术上当然可以。K8s 1.24 之前都支持 dockershim,就算现在,也有第三方维护的 shim 可以用。但我们团队早就切到 containerd 了,原因很简单:少一层抽象,少一个故障点

记得去年有一次,dockerd 卡死,导致整个节点上的容器无法更新,Pod 重启失败。排查半天才发现是 dockerd 的镜像元数据锁住了。换成 containerd 后,这种“中间商赚差价”导致的问题基本消失了。

另外,containerd 的配置也更透明。它的配置文件是 /etc/containerd/config.toml,所有参数一目了然。而 Docker 的配置散落在 daemon.json、systemd unit、甚至环境变量里,调起来头疼。

比如你想改镜像仓库地址(mirror),Docker 要改 /etc/docker/daemon.json:

{
  "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"]
}

而 containerd 要在 config.toml 里找到 [plugins."io.containerd.grpc.v1.cri".registry] 这一段,加上:

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
    endpoint = ["https://xxx.mirror.aliyuncs.com"]

虽然写法麻烦点,但结构清晰,不容易出错。


再聊聊镜像存储。

Docker 默认用 overlay2 存储驱动,镜像存在 /var/lib/docker 下,目录结构复杂,手动清理容易误删。

Containerd 用的是 content store + snapshotter 架构。镜像内容(layers)存在 content store(通常是 /var/lib/containerd/io.containerd.content.v1.content),快照(容器可写层)由 snapshotter 管理(比如 overlayfs)。

好处是:镜像和容器的存储完全解耦,支持多 snapshotter(overlayfs、btrfs、zfs 等),扩展性更强。

不过对运维来说,最直观的感受是:containerd 的磁盘占用更可控。我们曾经遇到 Docker 节点磁盘爆满,查了半天发现是 /var/lib/docker/containers 下的日志文件没轮转。而 containerd 默认不接管日志,日志由 kubelet 或 systemd 管理,更容易统一处理。


最后说点实用技巧。

如果你在 K8s 节点上想调试容器,推荐用 crictl:

# 查看所有 Pod
crictl pods

# 查看容器
crictl ps -a

# 进入容器(类似 docker exec)
crictl exec -it <container_id> sh

# 查看容器日志
crictl logs <container_id>

但要注意,crictl 的 container_id 是长 ID,不像 docker ps 显示短 ID。可以用 crictl ps | grep your-app 找。

另外,containerd 支持插件机制。比如你可以替换默认的 runc,换成 kata-runtime 实现安全容器;或者换 snapshotter 为 devmapper 做块设备隔离。这种灵活性是 Docker 很难做到的。


总结一下:

  • Docker 是“全家桶”,适合开发和单机部署;Containerd 是“核心引擎”,适合生产环境尤其是 K8s。
  • 命令上,Docker 有 docker CLI,Containerd 用 ctr/crictl,后者更底层但更贴近 K8s。
  • 架构上,Docker 多了一层 dockerd,Containerd 直接对接 CRI,更轻量、更标准。
  • 两者不是替代关系,而是分工协作:Docker 构建,Containerd 运行。

别再纠结“该学哪个”了——都得会。开发用 Docker,运维搞 K8s 就绕不开 Containerd。理解它们的关系,才能在容器世界里游刃有余。

如果你觉得这篇文章帮你理清了思路,不妨点个赞、转发给还在混淆这两者的同事。容器化这条路,踩坑不可怕,可怕的是不知道坑在哪。

关注我,带你少走弯路,多填干货。

公众号:运维躬行录
个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码