3. CRI(Container Runtime Interface)接口规范:设计目标、gRPC 接口定义与生命周期

CRI(Container Runtime Interface)是 Kubernetes 与容器运行时之间的核心抽象层。它定义了 kubelet 如何与容器运行时通信,以管理 Pod 和容器的完整生命周期。本章深入剖析 CRI 的设计目标、gRPC 接口定义以及 PodSandbox 与 Container 的生命周期管理。

3.1 CRI 设计目标

CRI 的设计初衷是为了解耦 Kubernetes 控制面与具体的容器运行时实现,使 kubelet 无需关心底层是 Docker、containerd、CRI-O 还是其他运行时。其核心设计目标包括:

设计目标 说明 关键收益
运行时无关性 kubelet 通过统一的 gRPC 协议调用运行时,不依赖特定运行时的内部 API 支持 containerd、CRI-O、Kata Containers 等多种运行时
Pod 语义原生支持 引入 PodSandbox 概念,将 Pod 作为资源调度的最小单元 支持 Pod 级别的网络命名空间、PID 命名空间共享
生命周期标准化 定义清晰的创建、启动、停止、删除等操作接口 运行时实现可预测,kubelet 状态机简化
可扩展性 通过 gRPC 的流式接口和可选的扩展点支持未来功能 支持容器日志流、执行命令、端口转发等
安全与隔离 接口设计支持安全上下文、Seccomp、AppArmor 等安全策略 运行时可以实施细粒度的安全隔离
设计哲学:CRI 的设计遵循“最小接口原则”——只暴露 kubelet 真正需要的操作,避免将运行时的内部实现细节泄露到控制面。这使得运行时可以自由优化内部实现,同时保证与 Kubernetes 的兼容性。

3.2 gRPC 接口定义

CRI 使用 Protocol Buffers(protobuf)定义接口,并通过 gRPC 进行通信。整个接口分为两大服务:RuntimeService 和 ImageService。以下是最核心的接口定义:

3.2.1 RuntimeService(运行时服务)

负责 PodSandbox 和容器的生命周期管理:

service RuntimeService {
    // PodSandbox 生命周期
    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}

    // 容器生命周期
    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}

    // 容器执行与日志
    rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
    rpc Exec(ExecRequest) returns (ExecResponse) {}
    rpc Attach(AttachRequest) returns (AttachResponse) {}
    rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
    rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {}
}

3.2.2 ImageService(镜像服务)

负责容器镜像的拉取、列表和删除:

service ImageService {
    rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
    rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
    rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
    rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
    rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}
关键消息结构:每个 RPC 请求和响应都使用 protobuf 定义。例如 CreateContainerRequest 包含 PodSandbox ID、容器配置(ContainerConfig)、PodSandbox 配置等字段。这些结构定义了 kubelet 与运行时之间的数据契约。

3.3 PodSandbox 与 Container 生命周期

CRI 将 Pod 抽象为 PodSandbox,这是一个隔离的环境(通常对应一个网络命名空间),容器在其中运行。PodSandbox 和 Container 的生命周期是 CRI 的核心流程。

3.3.1 PodSandbox 生命周期

PodSandbox 的生命周期包含以下阶段:

  1. 创建(RunPodSandbox):kubelet 调用 RunPodSandbox,运行时创建 Pod 级别的隔离环境(如网络命名空间、cgroup 父目录)。
  2. 运行中:PodSandbox 处于活跃状态,可以创建和运行容器。
  3. 停止(StopPodSandbox):停止 PodSandbox 内的所有容器,并清理网络资源。
  4. 删除(RemovePodSandbox):彻底删除 PodSandbox 及其所有资源。

3.3.2 Container 生命周期

容器在 PodSandbox 内创建,其生命周期如下:

  1. 创建(CreateContainer):在指定的 PodSandbox 内创建容器,但容器进程尚未启动。
  2. 启动(StartContainer):启动容器的主进程。
  3. 运行中:容器进程正常运行,kubelet 通过 ContainerStatus 监控健康状态。
  4. 停止(StopContainer):发送信号停止容器进程,支持优雅关闭。
  5. 删除(RemoveContainer):清理容器的所有资源。
注意:PodSandbox 必须先于容器创建,且容器必须在 PodSandbox 停止之前停止。如果直接删除 PodSandbox 而未先停止容器,运行时应当自动清理所有关联容器。

3.3.3 生命周期状态机

下图展示了 PodSandbox 与 Container 的完整生命周期状态转换:

PodSandbox 与 Container 生命周期状态机 PodSandbox 与 Container 生命周期状态机 PodSandbox SANDBOX_NOTREADY SANDBOX_READY SANDBOX_NOTREADY RunPodSandbox StopPodSandbox RemovePodSandbox Container CONTAINER_CREATED CONTAINER_RUNNING CONTAINER_EXITED StartContainer StopContainer RemoveContainer PodSandbox ID 状态说明: • SANDBOX_NOTREADY:PodSandbox 已创建但未就绪(网络未配置) • SANDBOX_READY:PodSandbox 就绪,可以创建容器 • CONTAINER_CREATED → CONTAINER_RUNNING:容器启动后进入运行状态

3.4 关键接口调用流程

以下是一个典型的 Pod 创建流程,展示了 kubelet 如何通过 CRI 接口与运行时交互:

  1. kubelet 调用 RunPodSandbox:创建 Pod 的网络隔离环境,返回 PodSandbox ID。
  2. kubelet 调用 PullImage(如果镜像不存在):拉取容器镜像。
  3. kubelet 调用 CreateContainer:传入 PodSandbox ID 和容器配置,创建容器但不启动。
  4. kubelet 调用 StartContainer:启动容器主进程。
  5. kubelet 定期调用 ContainerStatus:监控容器健康状态。
性能优化:在 containerd 和 CRI-O 的实现中,RunPodSandbox 和 CreateContainer 通常合并为一次操作,以减少 gRPC 调用次数。这种优化在 CRI 规范中是允许的,只要接口语义保持一致。

3.5 接口调用示例

以下是一个简化的 CreateContainerRequest 的 protobuf 定义示例:

message CreateContainerRequest {
    // PodSandbox ID,标识容器所属的 Pod
    string pod_sandbox_id = 1;

    // 容器配置
    ContainerConfig config = 2;

    // PodSandbox 配置(用于继承 Pod 级别的设置)
    PodSandboxConfig sandbox_config = 3;
}

message ContainerConfig {
    // 容器名称
    string name = 1;

    // 容器镜像
    ImageSpec image = 2;

    // 容器命令和参数
    repeated string command = 3;
    repeated string args = 4;

    // 工作目录
    string working_dir = 5;

    // 环境变量
    repeated KeyValue envs = 6;

    // 挂载卷
    repeated Mount mounts = 7;

    // 资源限制(CPU、内存)
    LinuxContainerResources linux = 8;

    // 安全上下文
    LinuxContainerSecurityContext security_context = 9;
}
兼容性注意:不同版本的 CRI 规范可能对字段的必填性有不同要求。例如,在 CRI v1 中 pod_sandbox_id 是必填字段,而 sandbox_config 在某些实现中可以为空。生产环境中建议始终传递完整的配置以避免兼容性问题。

3.6 总结

CRI 接口规范通过 gRPC 定义了 kubelet 与容器运行时之间的标准化通信协议。其核心设计目标包括运行时无关性、Pod 语义原生支持和生命周期标准化。PodSandbox 作为 Pod 的抽象,与 Container 共同构成了 CRI 的生命周期管理模型。理解这些接口和生命周期是构建容器运行时治理控制面的基础。