LabHub
学习 学习路径 课程

KCNA — Kubernetes 与云原生入门

CRI 为什么出现,shim 又抓着什么

在 LabHub 中继续学习

一句话总结

CRI 是“Kubernetes 与运行时对话的标准语法”,shim 则是“即使 containerd 死亡, 仍能维持容器存活的轻量进程”。两者都是为消除适配器而制定规范的产物。

概念图: 为消除适配器而制定规范 · 在自己的代码中携带了适配器 · 要移除适配器,必须先制定规范。 · Kubernetes v1.24 中移除

为什么需要它

没有 CRI 的时代,Kubernetes 能使用的运行时只有 Docker。为了与 Docker Engine 对话, Kubernetes 在自己的代码中携带了适配器,这就是 dockershim。

这段代码在当时是正确的,因为没有它,早期采用就不可能发生。问题在运行时数量增加后显现: 每增加一种运行时,都要向 Kubernetes 本体再加入一个适配器,维护负担就会无限堆积在 Kubernetes 一侧。

于是顺序被反转:要移除适配器,必须先制定规范。项目定义了名为 CRI(Container Runtime Interface)的 gRPC 接口,让运行时一侧实现该接口。dockershim 已在Kubernetes v1.24 中移除, 官方 FAQ 也说明这段代码最初就被设计为临时方案。它的位置由 containerd 和 CRI-O 接替。

这里必须消除一个考试中常见的误解:移除 dockershim 不代表“不能使用 Docker 镜像”。 镜像格式是 OCI Image Spec,所以用 docker build 制作的镜像仍能在所有 CRI 实现上运行。 消失的不是镜像,而是 kubelet 内部的适配器代码。

它如何运作

CRI 提供的两项服务

一个 Pod 启动时实际发生的顺序

  1. kubelet 调用 RunPodSandbox
  2. containerd 创建 pause 容器——它是 Pod 网络命名空间的所有者
  3. CNI 插件被调用,为该命名空间分配 IP
  4. kubelet 依次调用 PullImageCreateContainerStartContainer
  5. containerd 通过 shim 运行容器

为什么需要 pause 容器是 KCNA 的常考内容。Pod 内的容器要共享 IP 与端口空间, 就必须有人先创建网络命名空间,并一直持有到最后。即使应用容器因重启而全部消失, 命名空间也必须继续存在,IP 才不会改变。pause 只用 pause() 系统调用无限等待, 所以仅使用约 1MB 内存;它还作为 PID 命名空间的 init,负责回收僵尸进程。

shim——把 containerd 重启与容器寿命解耦的装置

containerd --> containerd-shim-runc-v2 --> runc --> 컨테이너 프로세스

shim 有四项职责。

容器进程的父进程不是 containerd,而是 shim。shim 会把自己守护进程化, 在与 containerd 不同的会话中运行。因此重启 containerd,容器进程的 PID 也不会变化。 因为“容器异常”而重启 containerd,大多数时候什么也修不好——重启的只是管理平面, 真正维持容器的是 shim。

shim 可以按容器类型替换:containerd-shim-runc-v2(runc)、containerd-shim-kata-v2(轻量 VM)、 containerd-shim-runsc-v1(gVisor)、containerd-shim-wasm(WebAssembly)。RuntimeClass 对象 把这种选择公开为 Pod 级别的配置。

在实际工作中会遇到的情况

作者重建家庭实验室集群时,把 CNI 从 Calico 换成了 Cilium 1.20.1,并且完全没有安装 kube-proxy (--skip-phases=addon/kube-proxy)。随后不是靠声称,而是通过测量确认它真的正常工作: **kube-proxy Pod 为 0,节点上的 iptables KUBE- 链为 0。**取而代之的是 eBPF map 中直接存在 10.96.0.1:443/TCP → 10.0.0.120:6443/TCP 这样的映射。

这里,层级讨论变成了现实:更换 CNI 与更换 CRI 是不同层级的工作。 CNI 只负责第 3 步(为命名空间分配 IP),创建容器的第 1、4、5 步保持不变。 因此即使完整替换 CNI,kubelet 与 containerd 之间的对话也一个字都不会改变。

还有一点。正因为层级分离,才会出现只有 exec 无法使用的故障。exec/attach/port-forward 并不直接通过 CRI gRPC 传输;containerd 返回 streaming URL 后,客户端会使用该 URL 重新连接节点。 因此可能出现 Pod 运行完全正常,只有 kubectl exec 超时的情况。

下一次测验要确认什么

本模块以测验结束。从下一模块开始,将连接真实集群,亲自观察目前讨论的各个组成部分 如何呈现为 API 对象。