Kubernetes CRI(容器运行时接口)完全指南:从设计原理到实现与验证
【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community
导读:CRI(Container Runtime Interface,容器运行时接口)是 Kubernetes 在节点侧定义的一组规范、protobuf API 与配套库,它划清了 kubelet 与容器运行时(如 containerd、cri-o)之间的边界。本文以 contributors/devel/sig-node/container-runtime-interface.md 为主体,结合本仓库中 kubelet 组件说明、CRI 网络规范、容器指标与测试策略等姊妹文档,系统讲解 CRI 的产生动机、API 组成、网络与指标扩展、历史版本演进、已知问题与验证方法。读完本文,你将理解 kubelet 如何通过 CRI 管理 Pod 与容器的完整生命周期,并掌握运行时接入 Kubernetes 所需的测试与发布流程。
CRI 是什么:kubelet 与运行时之间的标准接口
CRI 由三部分构成:
- 规格与需求(specifications/requirements):定义运行时必须满足的行为契约(原文档标注为"to-be-added",即持续演进中);
- protobuf API:定义 kubelet 与运行时之间的 gRPC 消息与 RPC 服务,即 Kubernetes 中
staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto对应的那一组定义; - 配套库(libraries):例如 Kubernetes 中
pkg/kubelet/cri/streaming,为 exec、attach、port-forward 等流式请求提供实现支撑。
从项目定位看,CRI 是"容器运行时在节点上与 kubelet 集成"的唯一通道。文档撰写时 CRI API 处于 Alpha 阶段,且自 Kubernetes 1.7 起,CRI-Docker 集成成为默认方案(历史版本信息,详见下文"版本演进"一节)。
在理解 CRI 前,需要先建立一条关键链路:kubelet 通过 CRI 把声明式的 Pod Spec 翻译成命令式的运行时操作,运行时再进一步交给 OCI 运行时(如 runc)完成底层操作系统级容器设置。这条链路在本仓库 kubelet.md 中有完整的代码级描述,下文会展开。
为什么需要 CRI:从内部接口到插件化生态
在 CRI 出现之前,容器运行时(如docker、rkt)是通过在 kubelet 内部实现一个高层的内部接口来完成集成的。这种做法存在两个根本性问题:
- 入门门槛极高:运行时集成方必须理解 kubelet 内部实现,并且要把代码贡献到 Kubernetes 主仓库,任何改动都要经过 Kubernetes 核心评审;
- 不可扩展:每新增一个运行时,都会在 Kubernetes 主仓库中产生一笔持续性的维护开销,随着运行时数量增长,维护成本会线性膨胀。
Kubernetes 的定位是"可扩展平台"。CRI 正是为了实现可插拔的容器运行时、构建更健康的生态而迈出的小而关键的一步:运行时作者只需面向稳定的 CRI API 编程,无需触碰 kubelet 内部逻辑。
从本仓库 kubelet.md 的实现描述可以印证这一分层:kubelet 的kuberuntime模块承担"声明式 API 到命令式 CRI API"的翻译,例如在startContainer流程中,它会先生成容器配置(将 Kubernetes API 对象翻译为 CRI spec),再调用 CRI 的CreateContainer与StartContainer。运行时是否真正以 OCI 方式创建容器,kubelet 完全不关心。
如何使用 CRI:启用方式与版本约束
CRI 的使用方式随 Kubernetes 版本不同而有差异。文档以 1.5 时代为例,给出了显式开启所需的额外参数:
- API Server 侧:设置
--feature-gates=StreamingProxyRedirects=true(开启流式请求代理重定向特性门控); - kubelet 侧:设置
--experimental-cri=true(显式开启实验性的 CRI 支持)。
文档同时提醒:CRI 当时仍处于早期阶段,社区正在积极吸收开发者反馈以改进 API;虽然尽力保持向后兼容,但开发者仍应预期偶尔会出现破坏性 API 变更。安装与配置的权威指引以官方 CRI 安装文档为准。
Kubelet 是否使用 CRI:答案是"总是使用"
文档给出了明确结论:除 rktnetes 集成之外,kubelet 总是使用 CRI。旧的非 CRI Docker 集成已在 Kubernetes 1.7 中被彻底移除。
结合本仓库 kubelet.md,可以看清 CRI 在 kubelet 实际运行中的位置:
- Pod 创建:kubelet 观察到绑定到本节点的 Pod 后,进入
SyncPod主流程,最终交由kuberuntime_manager.SyncPod处理容器操作; - Sandbox 生命周期:kubelet 通过
createPodSandbox创建 Pod 沙箱(sandbox),沙箱是 CRI 中承载 Pod 网络与命名空间的抽象; - 容器生命周期:kubelet 通过 CRI 依次调用
CreateContainer、StartContainer、StopContainer等 RPC; - 稳态维护:PLEG(Pod Lifecycle Event Generator)以约 2 秒为周期轮询运行时检测状态变化,触发同步循环;
- 终止与回收:
KillPod经由 CRI 停止容器与沙箱,容器 GC 与镜像 GC 周期性清理已退出容器和未使用镜像。
也就是说,从 Pod 的诞生到终结,kubelet 对容器的每一次操作都经由 CRI 这一层统一出口完成。
深入 CRI 网络规范:沙箱操作与网络生命周期
CRI 对网络的要求由 kubelet-cri-networking.md 专门规定,它是对 Kubernetes Pod 网络需求的扩展,但不涉及 Service 等上层网络抽象。核心要求可归纳为三条:
1. 网络生命周期必须随沙箱操作管理
kubelet 期望运行时 shim 在沙箱操作中同步管理 Pod 网络:
| RPC | 网络行为要求 |
|---|---|
RunPodSandbox | 必须完成网络设置,包括分配 Pod IP、配置网络接口与默认路由。成功后 Pod 沙箱必须拥有集群内可路由的 IP;网络设置失败必须返回错误;若网络已设置则跳过设置继续执行 |
StopPodSandbox | 必须拆除 Pod 网络;拆除失败必须返回错误;若网络已拆除则跳过继续执行 |
RemovePodSandbox | 若网络尚未拆除,可以顺带拆除;拆除失败必须返回错误 |
PodSandboxStatus | 响应中必须包含沙箱网络状态;无法构造网络状态时返回空网络状态 |
2. 用户级网络配置由运行时 shim 直接处理
凡是不直接暴露在 Kubernetes API 中的网络配置(如hairpin-mode、cni-bin-dir、cni-conf-dir、network-plugin、network-plugin-mtu、non-masquerade-cidr等),应由运行时 shim 自行处理。CRI 迁移完成后,kubelet 将不再触碰这些配置。
3. API 暴露的配置通过UpdateRuntimeConfig下发
通过 Kubernetes API 暴露的网络配置(例如podCIDR)经由UpdateRuntimeConfig接口传递给运行时 shim。不同运行时/网络实现可自行决定处理或忽略这些更新。
可扩展性设计
- kubelet 对 shim 如何管理网络一无所知:shim 可以自由使用 CNI、CNM 或任何其他实现,只要满足 CRI 网络需求与 Kubernetes 网络需求即可;
- 运行时 shim 对 Pod 网络配置拥有完整可见性;
- 随着更多网络特性的出现,CRI 本身将持续演进。
深入 CRI 容器指标:CPU、内存与文件系统统计
在 CRI 之前,kubelet 依赖独立的 cAdvisor 库获取容器 CPU、内存等指标,每个接入 Kubernetes 的运行时都需要在 cAdvisor 中增加对应包来支持指标跟踪,这构成了又一个独立集成点。CRI 成为新抽象后,自然演进为由 CRI 直接承载容器指标,消除这一额外的集成点(详见 cri-container-stats.md)。
指标 API 设计
CRI 暴露两个指标相关 RPC:
// ContainerStats returns stats of the container. If the container does not // exist, the call returns an error. rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {} // ListContainerStats returns stats of all running containers. rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {}返回的ContainerStats消息覆盖三类资源:
// ContainerStats provides the resource usage statistics for a container. message ContainerStats { // Information of the container. ContainerAttributes attributes = 1; // CPU usage gathered from the container. CpuUsage cpu = 2; // Memory usage gathered from the container. MemoryUsage memory = 3; // Usage of the writable layer. FilesystemUsage writable_layer = 4; } // CpuUsage provides the CPU usage information. message CpuUsage { // Timestamp in nanoseconds at which the information were collected. Must be > 0. int64 timestamp = 1; // Cumulative CPU usage (sum across all cores) since object creation. UInt64Value usage_core_nano_seconds = 2; } // MemoryUsage provides the memory usage information. message MemoryUsage { // Timestamp in nanoseconds at which the information were collected. Must be > 0. int64 timestamp = 1; // The amount of working set memory in bytes. UInt64Value working_set_bytes = 2; } // FilesystemUsage provides the filesystem usage information. message FilesystemUsage { // Timestamp in nanoseconds at which the information were collected. Must be > 0. int64 timestamp = 1; // The underlying storage of the filesystem. StorageIdentifier storage_id = 2; // UsedBytes represents the bytes used for images on the filesystem. // This may differ from the total bytes used on the filesystem and may not // equal CapacityBytes - AvailableBytes. UInt64Value used_bytes = 3; // InodesUsed represents the inodes used by the images. // This may not equal InodesCapacity - InodesAvailable because the underlying // filesystem may also be used for purposes other than storing images. UInt64Value inodes_used = 4; }设计要点:为何使用带时间戳的缓存统计
每个资源用量消息都包含一个timestamp,标明统计数据的采集时刻。原因在于:不同资源(如文件系统)的采集成本差异很大,更新频率可能低于其他资源。有了时间戳,消费方(kubelet)可以判断数据的新鲜程度,同时给运行时调整采集节奏的灵活性。
需要特别指出:CRI 不规定统计的更新频率,但 kubelet 对某些资源有最低新鲜度保证的需求,以便在资源压力下及时回收;这类需求将被逐步纳入 CRI 规范。此外,Kubelet 负责按 QoS 类别创建 Pod 级 cgroup 并作为父 cgroup 传给运行时,确保 Pod 沙箱、容器等所有资源都被计入对应 cgroup,因此 kubelet 自己(借助内建 cAdvisor)就能跟踪 Pod 级资源用量,CRI 指标增强聚焦于容器级。
演进状态
容器指标 RPC 在 Kubernetes 1.7 中加入 CRI,但当时 kubelet 尚未消费;1.8 起 kubelet 才获得通过 CRI stats 消费容器指标的选项,并依据相应开关函数决定指标来源(使用 CRI stats 还是旧版 cAdvisor stats)。
规范、设计文档与提案
原文档给出了一组 CRI 相关的规范/设计文档与提案清单(外部链接此处不再重复输出,仓库内可深入阅读的部分如下):
- 原始提案:Kubernetes 1.5 发布的 CRI 总体方案;
- 网络规范:kubelet-cri-networking.md(上文已详解);
- 容器指标:cri-container-stats.md(上文已详解);
- Exec/attach/port-forward 流式请求:定义了 kubelet 与运行时之间流式通道的转发/重定向机制;
- 容器 stdout/stderr 日志:定义了容器日志路径与格式规范。
CRI 运行时实现盘点
文档列出了当时活跃的 CRI 运行时实现:
- cri-o:为 Kubernetes 量身打造的轻量运行时,直接面向 OCI 容器;
- rktlet:让 rkt 通过 CRI 接入 kubelet 的 shim;
- frakti:基于 hypervisor 的运行时 shim,提供更强的隔离性;
- cri-containerd:containerd 的 CRI 插件实现,如今已是 Kubernetes 节点侧事实标准(Node E2E 测试文档即默认要求 containerd 开启 CRI 插件,见 e2e-node-tests.md);
- singularity-cri:面向 HPC 场景的 Singularity 容器运行时 shim。
这些实现从侧面印证了 CRI 的插件化价值:无论底层是 OCI 兼容运行时、rkt、hypervisor 还是 HPC 专用运行时,都能通过实现同一套 CRI API 接入 Kubernetes。
版本演进与状态更新
原文档以版本为线索记录了 CRI 的成熟过程,是理解其演进脉络的重要史料:
Kubernetes v1.5:CRI v1alpha1
- 发布 CRI v1alpha1 版本;
- 此版本需通过
--feature-gates=StreamingProxyRedirects=true(apiserver)与--experimental-cri=true(kubelet)显式开启。
Kubernetes v1.6:Docker-CRI 集成进入 Beta 并默认启用
- 升级建议:升级 kubelet 前先 drain 节点;若选择原地升级,kubelet 会重启节点上所有 Kubernetes 托管的容器;
- 资源与性能:实测无性能回退,kubelet 内存占用因 CRI 的 gRPC 序列化而略有增加(约每个 Pod +0.27MB);
- 禁用方式:设置
--enable-cri=false可回退到旧实现,但旧实现已被弃用,计划在下个版本移除,鼓励尽早迁移到 CRI; - 其他变化:Docker 容器命名/标签方案在 1.6 中有显著变化,这被视为实现细节,外部工具或脚本不应依赖。
Kubernetes v1.7:Docker-CRI 集成 GA,容器指标 API 落地
- Docker CRI 集成提升为 GA(一般可用);
- 旧的非 CRI Docker 集成从 kubelet 中完全移除,弃用的
--enable-cri参数一并删除; - CRI 扩展支持从运行时收集容器指标(对应上文容器指标 API)。
已知问题与边界(以 1.5 版本为基准)
原文档记录了 1.5 版本时的已知问题,供集成方与使用者参考(此后可能已修复):
CRI 层面
- 容器指标尚未定义于 CRI(对应 issue 27097)——该问题在 1.7 由容器指标 API 解决;
- 新的容器日志路径/格式尚未被日志管道(如 fluentd、GCL)支持(对应 issue 36401);
- CRI 可能与其他实验性特性(如 Seccomp)不兼容;
- 流式服务器需要加固:
- 认证问题(对应 issue 36666);
- 避免在重定向 URL 中包含用户数据(对应 issue 36187)。
Docker CRI 集成层面
- Docker 兼容性:仅支持 Docker v1.11 与 v1.12;
- 网络:不支持主机端口(host ports,对应 issue 35457);不支持带宽整形(bandwidth shaping,对应 issue 37315);
- 流式请求:不支持以
nsenter作为 exec 处理器(--exec-handler=nsenter,对应 issue 35747)。
如何验证你的运行时:CRI 测试策略
CRI 的价值建立在"可验证"之上。本仓库 cri-testing-policy.md(SIG-Node 所有)规定了运行时实现者必须执行的测试与结果发布流程:
必测与推荐测试
- 必测(required):
- Node conformance test suite(节点一致性套件,平台无关,验证 OS 镜像的一致性);
- Node feature test suite(节点特性套件,展示运行时在特定 OS 发行版上支持哪些特性);
- 强推荐(strongly recommended):Kubernetes conformance test suite(集群级 E2E,覆盖 CRI 与节点级测试无法覆盖的网络等领域;因网络涉及运行时、云厂商与集群组件的深度集成,建议向相关 SIG 寻求指导或赞助)。
Node E2E 测试框架与兼容 OS 镜像验证方法见 e2e-node-tests.md:本地运行make test-e2e-node(Linux only,需 etcd、开启 CRI 插件的 containerd、CNI 配置),远程运行make test-e2e-node REMOTE=true;kubelet 在启用 swap 的主机上默认拒绝启动,需通过TEST_ARGS='--kubelet-flags="--fail-swap-on=false"'放行。此外,运行时开发者被强烈鼓励在开发过程中运行低层的 CRI 验证测试套件(cri-tools 项目中的 validation 套件)。
测试结果发布流程
- 在 Kubernetes community 仓库提交提案,简要说明运行时、提供至少两位维护者,并将提案指派给 SIG-Node leads;
- 测试结果发布在
sig-node标签页下,组织方式为:sig-node -> sig-node-cri-{Kubernetes-version} -> [包含必测任务的页面]; - 任何时候只保留最近三个 Kubernetes 版本与 master 分支的结果,与 Kubernetes 发布节奏一致。
测试任务维护与 pre-submit
- 测试至少每晚运行一次;若测试被认为未积极维护,SIG-Node 可酌情将其移出测试网格;
- 测试连续通过超过 2 周后,维护者可申请将其纳入 PR pre-submit 测试(pre-submit 对测试容量与稳定性要求显著更高);
- 若测试 flaky 或失败且维护者未及时修复,SIG leads 可将运行时移出 pre-submit;
- 当前 SIG-Node 只接受将 Node conformance 测试提升为 pre-submit(集群级 conformance 涉及更广范围,可能需要其他 SIG 联合赞助);
- Windows 容器仍处早期阶段,建议按白名单特性集运行部分测试。
小结
CRI 是 Kubernetes 节点侧最关键的抽象之一:它以一组 protobuf API 与配套库,把 kubelet 与容器运行时的集成从"内部接口 + 主仓库贡献"转变为"稳定 API + 独立实现",并配套了网络生命周期、容器指标、流式请求等细化规范与一整套验证测试策略。对于想要为 Kubernetes 实现或选用运行时的工程师,本文连同仓库内的 kubelet-cri-networking.md、cri-container-stats.md、cri-testing-policy.md、kubelet.md 与 e2e-node-tests.md 构成了完整的从原理到验证的参考闭环。
联系方式:SIG-Node 邮件列表 sig-node@kubernetes.io,Slack 频道 #sig-node。
【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考