目录

K8s 中的开放接口

CRI、CNI、CSI 三大可插拔接口与扩展机制

Kubernetes 作为云原生应用的基础调度平台,常被类比为"云原生的操作系统"。和传统操作系统通过驱动屏蔽硬件差异类似,Kubernetes 通过一组开放接口(Open Interfaces) 把计算、网络、存储这三类最基础的资源抽象出来,让上游可以对接不同的后端实现,而无需修改 Kubernetes 核心代码。这种可插拔(pluggable)的设计,是 Kubernetes 能够支撑极其多样化的基础设施的关键。

Kubernetes 的三大核心开放接口分别对应一种基础资源类型:

接口全称提供的资源协议规范仓库
CRIContainer Runtime Interface计算资源(容器)gRPCkubernetes/cri-api
CNIContainer Network Interface网络资源JSON 配置 + 可执行插件containernetworking/cni
CSIContainer Storage Interface存储资源gRPCcontainer-storage-interface/spec

此外还有面向异构硬件的 Device Plugin 机制,可以让 GPU、FPGA、NIC 等设备以扩展资源的方式接入集群,常被并称为"第四类接口"。

下面分别展开。

CRI(Container Runtime Interface)是 kubelet 与容器运行时之间的主要 gRPC 协议。Kubernetes 官方文档对其的定义是:

The CRI is a plugin interface which enables the kubelet to use a wide variety of container runtimes, without having a need to recompile the cluster components.

CRI 把运行时能力拆成两个 gRPC 服务:

  • RuntimeService:负责 Pod 沙箱(PodSandbox)和容器的生命周期管理,例如 RunPodSandboxCreateContainerStartContainerContainerStatus 等。
  • ImageService:负责镜像的拉取、列表、查询和删除,例如 PullImageListImages

PodSandbox 是 CRI 里的关键抽象,通常对应一组 Linux 命名空间(network、IPC 等),同一 Pod 内的所有容器共享这同一个沙箱。

当 kubelet 从 API Server 收到一个 Pod 后,典型交互如下:

kubelet
  ├── gRPC ──► ImageService.PullImage()          # 1. 拉镜像
  ├── gRPC ──► RuntimeService.RunPodSandbox()    # 2. 创建沙箱,拿到 PodSandboxID
  ├── gRPC ──► RuntimeService.CreateContainer()  # 3. 在沙箱里创建容器
  ├── gRPC ──► RuntimeService.StartContainer()   # 4. 启动容器
  └── 轮询  ──► ContainerStatus() / ListContainers()  # 5. 持续对账

kubelet 通过 --container-runtime-endpoint 参数指定运行时监听的 Unix socket,例如:

kubelet --container-runtime-endpoint=unix:///run/containerd/containerd.sock
  • CRI API 的 v1 版本自 Kubernetes v1.23 起达到 Stable;从 v1.26 起,kubelet 强制要求运行时支持 v1 CRI API,否则节点不会注册。
  • 自 Kubernetes v1.24 起,内置的 dockershim 被移除,Docker Engine 不再直接对接 kubelet,需要通过 cri-dockerd 适配。
  • 目前主流的 CRI 实现:
    • containerd:CNCF 毕业项目,业界事实标准。
    • CRI-O:由 Kubernetes 社区发起、CNCF 毕业,专为 Kubernetes 设计的轻量运行时,底层调用 OCI 兼容的 runckata-containers
    • cri-dockerd:Mirantis 维护的 Docker Engine 适配层。

小贴士:Kubernetes v1.36 引入了 CRIListStreaming(Alpha),用流式 RPC 替代 ListContainers 等,解决单节点万级容器时 gRPC 默认 16 MiB 消息上限问题。

CNI(Container Network Interface)本身是一个独立于 Kubernetes 的 CNCF 项目,包含规范、库与一组参考插件,目标是"为 Linux 容器配置网络接口"。Kubernetes 借用它来实现 Pod 网络。

  • 规范仓库:containernetworking/cni,协议为 Apache-2.0,主要用 Go 实现,参考插件在 containernetworking/plugins
  • Kubernetes 要求 CNI 规范最低 v0.4.0,推荐 v1.0.0 及以上。
  • 配置目录默认为 /etc/cni/net.d/,二进制目录默认为 /opt/cni/bin/

一个常见的误解是 “kubelet 加载 CNI 插件”。实际上,自 Kubernetes v1.24 起,kubelet 不再通过 cni-bin-dirnetwork-plugin 参数管理 CNI,改由容器运行时(containerd、CRI-O 等)负责加载和管理。kubelet 仅仅通过 CRI 询问沙箱的网络命名空间,实际的网络配置走 CNI 协议。

CNI 的设计非常 Unix 风格:它把网络能力切分成一个个独立的二进制(如 bridgeptphost-localportmapbandwidth),通过 JSON 配置串联起来,典型结构如下:

{
  "name": "k8s-pod-network",
  "cniVersion": "0.4.0",
  "plugins": [
    { "type": "calico", "ipam": { "type": "host-local", "subnet": "usePodCidr" } },
    { "type": "portmap", "capabilities": { "portMappings": true } }
  ]
}

社区常见的 CNI 实现包括 Calico、Cilium、Flannel、Antrea、Weave Net、Kube-OVN、AWS VPC CNI 等,具体对比与选型可参考本博客的 K8s CNI 网络插件 一文。

CSI(Container Storage Interface)是一个跨容器编排系统的标准,用于把任意的块存储、文件存储系统暴露给容器化工作负载。它由 Kubernetes、Mesos、Cloud Foundry 等社区共同推动,规范定义在 container-storage-interface/spec(对应 csi.proto)。

在 CSI 之前,Kubernetes 把各类云盘驱动(gcePersistentDiskawsElasticBlockStore 等)直接编译进二进制,称为 in-tree 插件。这种模式的缺点显而易见:

  • 存储厂商每改一行代码,都要等下一个 Kubernetes 版本。
  • 核心仓库被各家驱动塞得越来越臃肿。
  • 安全/合规问题难以独立修复。

CSI 把这些能力搬到集群外部,以 DaemonSet + Deployment 的方式独立部署,既加快了迭代,也降低了核心复杂度。社区正在逐步把老的 in-tree 插件迁移到 CSI 并弃用之。

CSI 驱动需要实现三类服务:

  • Identity Service:声明驱动身份与能力。
  • Controller Service:集群级操作,如 CreateVolume、DeleteVolume、Attach/Detach、Snapshot。
  • Node Service:节点级操作,如 NodeStageVolume、NodePublishVolume(挂载到 Pod)。

为了避免每个驱动都重复实现一遍 Kubernetes API 交互逻辑,社区提供了一组 external sidecar 容器,负责监听 PVC、PV、VolumeAttachment 等对象并触发 CSI 调用,常见的有:

Sidecar作用
external-provisioner监听 PVC,触发 CreateVolume/DeleteVolume
external-attacher处理 VolumeAttachment,触发 ControllerPublishVolume
external-resizer处理 PVC 扩容
external-snapshotter管理 VolumeSnapshot
node-driver-registrar把驱动注册到 kubelet
livenessprobe健康检查

CSI 自 Kubernetes v1.13 起 GA,生产环境基本以 v1.0.0 规范为基线。

虽然不像前三个有醒目的三字母缩写,Device Plugin 同样是 Kubernetes 暴露的一类重要扩展接口,专门用于让厂商把 GPU、FPGA、SR-IOV NIC、InfiniBand 等硬件以扩展资源(Extended Resource) 的方式接入集群。

工作流程大致是:厂商以 DaemonSet 形式在每个节点运行一个 gRPC 服务,通过 /var/lib/kubelet/device-plugins/kubelet.sock 向 kubelet 注册自己,例如声明资源名 nvidia.com/gpu,随后通过 ListAndWatch 上报设备列表与健康状态。Pod 通过 resources.limits 申请:

spec:
  containers:
    - name: cuda-container
      image: nvidia/cuda:12.0.0-base-ubuntu22.04
      resources:
        limits:
          nvidia.com/gpu: 2

容器创建时,kubelet 会调用驱动的 Allocate,把对应的设备节点(/dev/nvidia0 等)、环境变量、挂载、CDI 设备名注入到容器里。NVIDIA、Intel、AMD、华为等都基于这套机制提供了自己的 Device Plugin 实现。

把 CRI、CNI、CSI、Device Plugin 放在一起看,会发现 Kubernetes 的扩展模型有几个共同特点:

  • 协议化、解耦:核心只定义 gRPC/JSON 协议,后端实现可以独立演进。
  • out-of-tree:厂商无需侵入 Kubernetes 主仓库,版本节奏各自掌控。
  • 最小内核 + 丰富生态:核心保持精简,生态通过接口外延。

正因如此,计算可以用 containerd 或 Kata Containers,网络可以选 Calico 或 Cilium,存储可以挂本地盘、Ceph 或云厂商的块存储,GPU 可以来自任意厂商——而 Kubernetes 本身一行代码都不用改。可以说,这套"开放接口"才是 Kubernetes 真正的护城河。

相关内容