从 Docker 到 containerd:Kubernetes 生产环境为什么必须理解 CRI?

很多人第一次排查 Kubernetes 节点问题时,第一反应还是:

docker ps
docker logs
docker exec

但到了新的 Kubernetes 集群里,你很可能会发现:节点上根本没有 Docker daemon,或者 Docker 命令已经不是你真正应该看的排障入口。

这不是 Kubernetes “变复杂了”,而是我们需要把几个概念拆开:Docker、containerd、CRI、kubelet。只要这层关系理顺,后面排查 ContainerCreating、镜像拉取失败、kubelet 连不上运行时、升级 containerd 等问题,都会清楚很多。

这篇文章先做铺垫。明天再进入更重的实战:Kubernetes 1.36 + containerd 2.3 生产升级检查清单。

1. 先说结论:Kubernetes 真正关心的是 CRI

Docker 对开发者很友好,它提供了镜像构建、容器运行、日志查看、网络、命令行等一整套体验。

但 Kubernetes 在节点上真正需要的,不是完整 Docker 工具链,而是一个能被 kubelet 调用的容器运行时接口。这个接口就是 CRI,Container Runtime Interface。

简单说:

  • kubelet 负责管理节点上的 Pod 生命周期;
  • CRI 是 kubelet 和容器运行时之间的标准接口;
  • containerd 是 Kubernetes 常用的容器运行时;
  • runc 是更底层真正创建 Linux 容器的组件;
  • crictl 是面向 CRI 的节点排障工具。

所以,生产环境里如果 Pod 启动失败,不要只想着“Docker 怎么了”。更准确的问题应该是:

kubelet 通过 CRI 调用 containerd 的过程中,哪一步失败了?

2. Docker、containerd、CRI、kubelet 分别是什么?

Docker:开发者熟悉的一整套容器工具链

Docker 最早让容器真正流行起来,核心原因是体验好。开发者可以用一组统一命令完成镜像构建、容器启动、日志查看和调试。

但在 Kubernetes 节点上,Docker 的完整能力并不是都必须存在。Kubernetes 不一定需要 Docker CLI,也不一定需要 Docker daemon 的所有功能。

containerd:更贴近运行时本身

containerd 更靠近容器运行时的核心职责。它主要负责:

  • 拉取镜像;
  • 管理镜像;
  • 创建容器;
  • 管理容器生命周期;
  • 管理 Pod sandbox;
  • 与底层 runtime 交互。

如果把 Docker 理解成“完整工具箱”,containerd 更像是 Kubernetes 节点上真正干活的“运行时底座”。

CRI:kubelet 和运行时之间的接口

CRI 是 Kubernetes 定义的一套容器运行时接口。kubelet 不需要理解每个运行时的内部实现,只要运行时实现 CRI,kubelet 就可以通过统一接口调用它。

这也是为什么 Kubernetes 可以对接 containerd、CRI-O 等不同运行时。

kubelet:节点上的 Pod 管家

kubelet 运行在每个 Kubernetes 节点上。它会接收控制面的调度结果,然后在本机创建、检查、重启和汇报 Pod 状态。

但 kubelet 并不自己拉镜像、创建容器。它通过 CRI 请求 containerd 这类运行时完成底层动作。

3. 为什么 Kubernetes 逐步淡化 Docker?

很多人听过一句话:“Kubernetes 弃用了 Docker。”

这句话容易误解。更准确地说,是 Kubernetes 移除了 kubelet 里内置的 dockershim。Kubernetes 官方在 dockershim FAQ 里说明,dockershim 在 v1.24 从 kubelet 中移除。它移除的是 kubelet 直接适配 Docker Engine 的那层兼容逻辑,不是说 Docker 镜像不能用了。

今天你用 Dockerfile 构建的镜像,仍然可以在 Kubernetes 里运行。因为镜像格式遵循 OCI 规范,containerd 同样可以拉取和运行这些镜像。

真正发生变化的是节点排障方式:

  • 以前你可能习惯看 Docker daemon;
  • 现在你更应该看 kubelet、CRI、containerd;
  • 以前你习惯 docker ps;
  • 现在节点侧更常用 crictl ps -a;
  • 以前你查 Docker 日志;
  • 现在你要查 kubelet 和 containerd 日志。

到这里可以先建立一个判断原则:不要先问“Docker 在不在”,而要先问“kubelet 能不能通过 CRI 正常调用运行时”。

如果这个调用链是通的,再继续看镜像、网络、资源和业务容器;如果这个调用链本身不通,后面看多少业务日志都容易跑偏。

4. containerd 在 Kubernetes 节点里到底做什么?

一个 Pod 被调度到节点上,大致会经历这样的过程:

  1. 控制面决定把 Pod 调度到某个节点;
  2. 该节点上的 kubelet 看到这个 Pod;
  3. kubelet 通过 CRI 调用 containerd;
  4. containerd 准备 Pod sandbox;
  5. containerd 拉取业务镜像;
  6. containerd 调用底层 runtime 创建容器;
  7. kubelet 持续检查 Pod 状态并向 API Server 汇报。

这里面有几个特别容易出问题的点。

第一,Pod sandbox 创建失败

Pod 不是一个单独容器。Kubernetes 会先创建 Pod sandbox,用来承载 Pod 的网络命名空间等基础环境。这个过程通常依赖 pause 镜像。

如果 pause 镜像拉不下来,Pod 就可能一直卡在 ContainerCreating。

第二,业务镜像拉取失败

企业环境里经常会用 Harbor 或其他私有镜像仓库。如果 containerd 的 registry、mirror、TLS 或认证配置有问题,kubelet 看到的结果就是 Pod 拉镜像失败。

第三,kubelet 连不上 containerd

如果 CRI socket 配错、containerd 没启动、CRI 插件异常,kubelet 就无法通过 CRI 管理容器。

这类问题很容易被误判成“Pod 有问题”,但真正的问题在节点运行时。

5. 生产环境为什么必须理解 CRI?

因为生产环境的很多故障,表面看是 Pod 异常,实际入口在 CRI。

场景一:Pod 一直 ContainerCreating

你看到的可能只是:

kubectl get pod

状态一直停在 ContainerCreating。

但原因可能是:

  • pause 镜像拉不下来;
  • 业务镜像拉不下来;
  • containerd 无法创建 sandbox;
  • CNI 网络初始化失败;
  • kubelet 调用 CRI 失败。

这时候要从 kubectl describe pod 往下追,而不是立刻去猜业务代码问题。

场景二:节点 NotReady

节点 NotReady 不一定是机器宕机,也可能是 kubelet 无法正常汇报状态,或者 kubelet 与 containerd 通信异常。

你需要检查:

systemctl status kubelet
systemctl status containerd
journalctl -u kubelet -f
journalctl -u containerd -f

场景三:升级 Kubernetes 或 containerd

升级前必须确认:

  • kubelet 版本:kubelet --version,并确认与目标 Kubernetes 小版本兼容;
  • containerd 版本:containerd --version,重点确认是否启用了 CRI 插件;
  • CRI endpoint:检查 kubelet 启动参数里的 --container-runtime-endpoint,以及 crictl info 能否连通;
  • cgroup driver:检查 kubelet 配置里的 cgroupDriver,以及 containerd 配置里的 SystemdCgroup;
  • pause 镜像:检查 containerd 的 sandbox_image,确认镜像仓库在当前网络环境可以拉取;
  • 私有仓库配置:检查 /etc/containerd/certs.d/ 或 config.toml 里的 registry mirror、TLS、认证配置;
  • 节点系统和内核配置:至少确认内核版本、cgroup v1/v2、swap、系统服务状态和磁盘压力。

否则升级后最常见的问题就是:节点 Ready,但 Pod 起不来;或者部分 Pod 起得来,部分 Pod 卡住。

6. 常用排查命令

下面这组命令建议直接收藏。它们不是所有问题的最终答案,但能帮你快速判断问题在 Kubernetes 层、kubelet 层,还是 containerd/CRI 层。

查看节点运行时

kubectl get nodes -o wide

关注 CONTAINER-RUNTIME 字段,确认当前节点使用的是 containerd、CRI-O 还是其他运行时。

查看节点详情

kubectl describe node <node-name>

重点看:

  • Conditions;
  • Allocated resources;
  • Events;
  • Container Runtime Version。

查看 Pod 事件

kubectl describe pod <pod-name> -n <namespace>

重点看 Events。镜像拉取失败、sandbox 创建失败、调度失败,通常都会在这里留下第一手线索。

查看 CRI 信息

crictl info

如果这里都连不上,说明你要先检查 crictl 的 endpoint 配置,以及 containerd 的 CRI 插件状态。

重点看三件事:

  • runtimeType 或运行时名称是否符合预期;
  • config.containerd.runtimes 里是否有默认 runtime;
  • 输出里是否出现连接 endpoint 失败、CRI service 不可用等错误。

如果 crictl info 报错,可以继续检查:

cat /etc/crictl.yaml
ls -l /run/containerd/containerd.sock

常见目标是确认 runtime-endpoint 是否指向 unix:///run/containerd/containerd.sock。

查看节点上的容器

crictl ps -a

这相当于以前很多人习惯用的 docker ps -a,但它面向的是 CRI。

查看 containerd 状态

systemctl status containerd

如果 containerd 没正常运行,Pod 生命周期管理就会受影响。

如果服务是运行中的,还要继续看配置是否生效:

containerd config dump

重点查:

  • CRI 插件是否存在;
  • sandbox_image 是否符合当前环境;
  • registry mirror 是否指向企业内可访问地址;
  • default runtime 是否是预期的 runc 或其他运行时。

查看 kubelet 日志

journalctl -u kubelet -f

kubelet 日志通常能看到它调用 CRI 时的错误,比如 runtime endpoint 不可用、sandbox 创建失败等。

7. 一个简单的排障顺序

遇到 Pod 启动异常时,我建议先按这个顺序来:

步骤命令目的
1kubectl get pod -o wide看 Pod 在哪个节点、当前状态是什么
2kubectl describe pod看 Events,判断是调度、镜像、sandbox 还是运行时问题
3kubectl describe node看节点状态、资源压力和运行时版本
4systemctl status containerd看 containerd 是否正常
5crictl info看 CRI 是否可用,endpoint 是否正确
6journalctl -u kubelet -f看 kubelet 调用运行时的错误
7journalctl -u containerd -f看 containerd 侧错误

这套顺序的好处是:先看 Kubernetes 暴露出来的信息,再逐层下钻到节点运行时。

如果只能记一条路径,就记这个:

Pod 状态 -> Pod Events -> Node 状态 -> kubelet 日志 -> CRI 连通性 -> containerd 配置与日志

8. 常见误区

误区一:Docker 不能用了,所以 Docker 镜像也不能用了

不对。Docker 构建出来的 OCI 镜像仍然可以被 containerd 拉取和运行。

真正变化的是节点运行时和排障入口。

误区二:containerd 就是 Docker 的完整替代品

不完全是。containerd 是运行时底座,不是 Docker Desktop 或 Docker CLI 的完整体验替代。

如果你想在本地获得接近 Docker 的命令体验,可以了解 nerdctl。但在 Kubernetes 节点排障时,更应该熟悉 crictl。

误区三:Pod 起不来就是业务镜像有问题

不一定。Pod 起不来可能卡在:

  • 调度;
  • 镜像拉取;
  • Pod sandbox;
  • CNI;
  • CRI;
  • containerd;
  • kubelet。

所以排障时不要从业务代码开始猜,要先看 Events 和节点运行时。

9. 小结:先理解 CRI,再谈生产升级

如果你只记住一句话,那就是:

Kubernetes 节点上的容器问题,不要只用 Docker 思路排查;先看 kubelet 通过 CRI 调用 containerd 的链路。

理解这条链路后,很多问题会变得清楚:

  • 为什么没有 Docker 命令也能跑容器;
  • 为什么要用 crictl;
  • 为什么 Pod sandbox 会影响 Pod 启动;
  • 为什么 containerd 配置会影响镜像拉取;
  • 为什么升级 Kubernetes 前必须检查运行时版本和 CRI socket。

下一篇我会进入真正的生产实战:

Kubernetes 1.36 + containerd 2.3 生产升级实战:CRI、版本兼容与避坑清单。

那篇会重点给出升级前检查项、命令清单、常见故障和回滚方案。

参考资料

更多推荐