3. CRI 标准诞生:Kubernetes CRI 的设计动机、接口定义与 gRPC 协议规范
在 Kubernetes 早期版本(v1.5 之前),kubelet 直接通过 硬编码接口 调用 Docker API 来管理容器生命周期。这种紧耦合架构导致社区出现严重的“容器运行时绑定”——任何新的运行时(如 rkt、Hyper、Kata Containers)都必须修改 kubelet 核心代码。为了打破这一僵局,Kubernetes 社区在 v1.5 中正式引入了 Container Runtime Interface(CRI),将容器运行时管理抽象为标准的 gRPC 接口。
3.1 设计动机:为什么需要 CRI?
CRI 的诞生源于三个核心痛点:
- 运行时锁定:kubelet 与 Docker 深度耦合,无法灵活切换或组合运行时。
- 接口碎片化:每个运行时提供不同的 API 语义,kubelet 需要维护大量适配代码。
- 安全隔离需求:传统容器共享内核,无法满足多租户强隔离场景(如 Kata、gVisor)。
关键设计目标: 将“容器运行时”定义为可插拔组件,kubelet 通过统一协议与运行时通信,运行时只需实现 CRI 接口即可接入 Kubernetes 生态。
3.2 CRI 接口定义:RuntimeService 与 ImageService
CRI 将运行时能力拆分为两个独立的 gRPC 服务:
| 服务名称 | 职责范围 | 核心 RPC 方法 |
|---|---|---|
| RuntimeService | 容器生命周期管理、Pod 沙箱管理、执行命令/日志 | RunPodSandbox, CreateContainer, StartContainer, StopContainer, RemoveContainer, ExecSync, Status |
| ImageService | 镜像拉取、列表、状态查询、清理 | PullImage, ListImages, ImageStatus, RemoveImage, ImageFsInfo |
设计哲学: RuntimeService 管理“运行态”资源(Pod 沙箱、容器),ImageService 管理“静态”资源(镜像层数据)。这种分离允许运行时独立优化镜像缓存策略,而不影响容器调度性能。
3.3 gRPC 协议规范与消息结构
CRI 基于 Protocol Buffers(proto3) 定义接口,所有通信通过 gRPC 双向流或一元 RPC 完成。核心消息结构如下:
// RuntimeService 核心 RPC 定义(简化)
service RuntimeService {
// Pod 沙箱管理
rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse);
rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse);
// 容器管理
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
// 执行与调试
rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse);
rpc Attach(AttachRequest) returns (stream AttachResponse);
}
// ImageService 核心 RPC 定义(简化)
service ImageService {
rpc PullImage(PullImageRequest) returns (PullImageResponse);
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse);
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
}
每个 RPC 请求/响应消息都包含 元数据(Metadata) 和 运行时配置(RuntimeConfig)。以 CreateContainerRequest 为例:
message CreateContainerRequest {
string pod_sandbox_id = 1; // 所属 Pod 沙箱 ID
ContainerConfig config = 2; // 容器配置(镜像、命令、资源限制)
PodSandboxConfig sandbox_config = 3;// Pod 沙箱配置(DNS、端口映射)
}
message ContainerConfig {
Image image = 1; // 镜像引用
repeated string command = 2; // 入口命令
repeated string args = 3; // 命令参数
map<string, string> envs = 4; // 环境变量
LinuxContainerResources resources = 5; // CPU/内存限制
}
3.4 CRI 架构交互流程
下图展示了 kubelet 通过 CRI 与容器运行时交互的完整流程:
3.5 CRI 版本协商与兼容性
CRI 通过 Version RPC 实现运行时版本协商:
// 版本协商 RPC
service RuntimeService {
rpc Version(VersionRequest) returns (VersionResponse);
}
message VersionResponse {
string version = 1; // 运行时版本(如 "0.1.0")
string runtime_name = 2; // 运行时名称(如 "containerd")
string runtime_version = 3; // 运行时内部版本
string runtime_api_version = 4; // CRI API 版本
}
兼容性陷阱: kubelet 与运行时必须协商一致的
runtime_api_version。如果运行时返回的版本低于 kubelet 期望的最低版本,kubelet 将拒绝启动该节点。生产环境中,建议保持 containerd/CRI-O 与 Kubernetes 版本对齐。
3.6 CRI 接口适配清单
实现一个完整的 CRI 运行时需要覆盖以下接口组:
| 接口组 | 必须实现的方法数 | 典型实现难度 |
|---|---|---|
| Pod 沙箱管理 | 4(Run/Stop/Remove/Status) | 高(需隔离网络/命名空间) |
| 容器生命周期 | 6(Create/Start/Stop/Remove/Status/Stats) | 中 |
| 镜像管理 | 5(Pull/List/Status/Remove/FsInfo) | 低(可复用现有镜像仓库) |
| 调试与日志 | 3(ExecSync/Attach/PortForward) | 中(需处理流式传输) |