【Kubernetes从入门到精通】第66篇:CRI深度解析——容器运行时接口,K8s能“换引擎“全靠它
上一篇【第65篇】kubelet深度解析——节点上的“全能管家“起容器、查健康、报状态一把抓下一篇【第67篇】CNI深度解析——容器网络接口标准摘要上篇我们说kubelet通过CRI调containerd起容器。你可能会问为什么K8s能随便换容器引擎早年只用Docker后来把Docker干掉换成containerd/CRI-Okubelet的代码却没重写答案就是CRIContainer Runtime Interface——K8s在kubelet和容器运行时之间插了一层标准接口。就像USB接口让电脑能插任何牌子的鼠标CRI让K8s能用任何符合标准的运行时。这篇文章讲清CRI的设计两组gRPC API、Pod SandboxPod沙箱这个独特概念、containerd的CRI插件架构以及为什么CRI是K8s换引擎不换车的关键。一、CRI是什么1.1 解耦kubelet和运行时【没有CRI (早期)kubelet 和 Docker 死绑】 kubelet ──直接调用──► Docker API (代码里写死Docker的调用) 问题 • 想换运行时改kubelet源码 • Docker太重(K8s只用它一小部分能力) • 升级Docker牵一发动全身【有CRI (现在)中间一层标准接口】 kubelet ──CRI(gRPC)──► 任意运行时 ├── containerd ├── CRI-O └── (未来的新运行时) kubelet只依赖CRI接口不关心后面是谁 → 换运行时 换CRI实现kubelet零改动要点CRI的本质是**“依赖抽象不依赖实现”**——经典的依赖倒置原则。kubelet依赖CRI这个抽象接口任何运行时只要实现CRI接口就能被K8s用。这就是为什么第005篇说的K8s干掉Docker其实不疼——kubelet调的是CRIDocker只是通过dockershim间接实现了CRIdockershim移除后kubelet直接调containerd的CRI插件更纯净。二、CRI的两组API2.1 RuntimeService ImageService【CRI 的两大服务】 ┌─────────────────────────────────────────────┐ │ RuntimeService (运行时服务) │ │ 管容器的生老病死 │ │ │ │ • RunPodSandbox / StopPodSandbox │ │ • CreateContainer / StartContainer │ │ • StopContainer / RemoveContainer │ │ • ListContainers / ContainerStatus │ │ • Exec / Attach / PortForward │ └─────────────────────────────────────────────┘ ┌─────────────────────────────────────────────┐ │ ImageService (镜像服务) │ │ 管镜像 │ │ │ │ • PullImage / PushImage │ │ • ListImages / ImageStatus │ │ • RemoveImage / ImageFsInfo │ └─────────────────────────────────────────────┘ 两者通过 gRPC 调用(默认 unix socket: /run/containerd/containerd.sock)2.2 Pod Sandbox——CRI的独特概念【Pod Sandbox (Pod沙箱) —— 先盖好房子再入住】 关键洞察K8s的世界里最小调度单元是Pod(不是容器) 但Docker的世界里只有容器没有Pod! CRI的解决方案Pod Sandbox 1. kubelet先让运行时创建一个沙箱(Pause容器) → Pause容器创建Pod的网络命名空间(netns) → 分配Pod IP 2. 再把Pod里的业务容器塞进这个沙箱 → 业务容器共享Pause的网络命名空间 → 这就是Pod内共享网络的实现(见第008篇) Pause容器(第008篇讲过) • 几乎啥也不干就占着netns不退出 • 所有业务容器join它的netns → Pod网络统一# 看节点上的Pause容器(每个Pod一个)dockerps|greppause# 或 ctr (containerd命令行)ctr-nk8s.io containers list|greppause三、containerd的CRI插件架构3.1 调用链【containerd 内部结构(简化)】 kubelet │ CRI gRPC ▼ ┌─────────────────────────────────┐ │ containerd │ │ ┌───────────────────────────┐ │ │ │ CRI Plugin (内置) │ │ ← 实现CRI接口 │ │ • 处理RunPodSandbox等 │ │ │ │ • 转成containerd的调用 │ │ │ └───────────────────────────┘ │ │ ┌───────────────────────────┐ │ │ │ core (容器/镜像/快照管理) │ │ │ └───────────────────────────┘ │ │ ┌───────────────────────────┐ │ │ │ OCI runtime shim │ │ │ │ (调 runc 创建容器) │ │ │ └───────────────────────────┘ │ └──────────────────┬──────────────┘ │ OCI runtime spec ▼ ┌─────────────────────────────────┐ │ runc (真正创建Linux容器) │ └─────────────────────────────────┘3.2 containerd vs CRI-O运行时背景特点containerdDocker捐给CNCF生态最大Docker兼容好CRI-O红帽主导轻量专为K8s设计无Docker包袱# 看当前节点用哪个CRI运行时kubectl get nodes-owide# CONTAINER-RUNTIME: containerd://1.7.11# 直接用ctr操作containerdctr-nk8s.io images list# 看K8s命名空间下的镜像要点containerd本来是Docker的核心组件后来独立成CNCF项目并实现CRI插件成了K8s主流运行时。它在kubelet(CRI)和runc(OCI)之间做转换。CRI-O是红帽专为K8s打造的更轻量替代。两者都通过OCI标准调runc——所以CRI是K8s和运行时之间的标准OCI是运行时和容器之间的标准两层标准各管一段。四、为什么CRI这么重要4.1 它让K8s面向未来【CRI 开启的可能性】 1. 换运行时零成本 → containerd / CRI-O / 未来的WASM运行时 2. 安全运行时崛起 → Kata Containers (VM级隔离) 实现CRI → gVisor (用户态内核) 实现CRI → 直接用于K8s不用改kubelet 3. Windows容器 → Windows上的容器运行时也实现CRI 4. WASM/wasmedge → WebAssembly运行时也开始实现CRI → 轻量级替代容器(见第090篇)本篇小结CRI是kubelet和容器运行时之间的标准gRPC接口分RuntimeService容器生命周期和ImageService镜像两组。它让K8s换引擎不换车——kubelet只依赖CRI抽象具体运行时containerd/CRI-O/Kata/gVisor谁实现谁上岗。Pod Sandbox是CRI的独特设计kubelet先让运行时创建一个Pause沙箱容器建立Pod网络命名空间、分配IP再把业务容器塞进去共享网络——这就是Pod内共享网络的落地方式。containerd通过内置CRI插件在CRI(gRPC)和runc(OCI)之间做转换。CRI是K8s面向未来的关键——安全容器、WASM运行时都能即插即用。下篇讲CNI——网络接口标准。上一篇【第65篇】kubelet深度解析——节点上的“全能管家“起容器、查健康、报状态一把抓下一篇【第67篇】CNI深度解析——容器网络接口标准