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 与容器运行时交互的完整流程:

CRI 架构交互流程 CRI 架构交互流程 kubelet CRI 客户端 gRPC 通道 protobuf 序列化 容器运行时 CRI 服务端 Pod 管理模块 PodSandbox 生命周期 容器管理模块 Create/Start/Stop RuntimeService 沙箱 + 容器 RPC ImageService 镜像拉取/管理 RPC 协议特点 • 一元 RPC:同步操作 • 流式 RPC:日志/Attach • 超时控制:context • 版本协商:Version RPC

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) 中(需处理流式传输)