2. CSI 规范深度解析:三大核心 gRPC 服务与版本协商

CSI(Container Storage Interface)规范的核心是一组定义清晰的 gRPC 接口,它将存储提供商(SP)的能力抽象为三个独立的服务:Identity(身份/元数据)、Controller(控制器/编排面)和 Node(节点/数据面)。理解这三个服务的 RPC 定义及其版本协商机制,是构建可靠、可移植存储插件的基石。

2.1 三大核心 gRPC 服务总览

CSI 规范通过三个 gRPC 服务来划分职责,每个服务包含一组特定的 RPC 调用。下表总结了每个服务的核心职责与关键 RPC 方法:

服务名称 核心职责 关键 RPC 方法 调用方
Identity 插件元数据、能力声明、版本协商 GetPluginInfo, GetPluginCapabilities, Probe 容器编排器(CO)
Controller 卷的生命周期管理(创建、删除、挂载/卸载控制) CreateVolume, DeleteVolume, ControllerPublishVolume, ControllerUnpublishVolume, ListVolumes, ValidateVolumeCapabilities 容器编排器(CO)
Node 卷在节点上的本地操作(挂载、格式化、卸载) NodePublishVolume, NodeUnpublishVolume, NodeGetCapabilities, NodeGetInfo, NodeStageVolume, NodeUnstageVolume 节点上的 kubelet
架构要点: Controller 服务通常运行在控制平面(如 Kubernetes 的 controller-manager 中),而 Node 服务运行在每个工作节点上。Identity 服务则必须由所有 CSI 插件实现,且通常与 Controller 或 Node 服务部署在同一进程中。

2.2 Identity 服务:插件的“身份证”与能力声明

Identity 服务是 CO 与 CSI 插件建立连接后调用的第一个服务。它用于发现插件的基本信息、支持的能力以及健康状态。该服务是强制实现的,且通常不依赖后端存储的实际连接。

  • GetPluginInfo:返回插件的名称、厂商信息和版本。CO 使用此信息进行插件识别和准入控制。
  • GetPluginCapabilities:返回插件支持的能力集,例如是否支持 Controller 服务、是否支持卷扩展、是否支持快照等。这是 CO 决定如何与插件交互的关键依据。
  • Probe:用于检查插件的健康状态。如果插件未就绪,CO 应避免向其发送其他 RPC 请求。

2.3 Controller 服务:卷编排的“大脑”

Controller 服务负责处理与存储后端交互的卷编排操作。它通常运行在集群的控制平面,并直接与存储阵列或云存储 API 通信。

  • CreateVolume / DeleteVolume:在存储后端上创建或删除持久卷。参数包括卷名称、容量、访问模式、参数等。
  • ControllerPublishVolume / ControllerUnpublishVolume:将卷附加(attach)到指定节点或从节点分离(detach)。对于块存储,这通常意味着将 LUN 映射到主机。
  • ListVolumes:列出存储后端上所有由该插件管理的卷,支持分页。
  • ValidateVolumeCapabilities:验证给定的卷是否支持特定的访问模式(如 ReadWriteOnce、ReadOnlyMany)。
关键约束: Controller 服务是可选的。如果插件只提供本地存储(如 HostPath 或 Local PV),可以不实现 Controller 服务,但必须在 Identity 服务的 GetPluginCapabilities 中声明不支持 Controller 能力。

2.4 Node 服务:节点上的“执行者”

Node 服务运行在每个工作节点上,负责执行卷在节点本地的挂载、格式化、卸载等操作。它直接与节点的操作系统和文件系统交互。

  • NodeStageVolume / NodeUnstageVolume:在节点上对卷进行全局准备(如格式化、挂载到全局路径),为后续的 Pod 挂载做准备。
  • NodePublishVolume / NodeUnpublishVolume:将卷挂载到 Pod 的指定挂载路径,或从 Pod 路径卸载。这是 Pod 生命周期中最直接的卷操作。
  • NodeGetCapabilities:返回节点服务支持的能力,例如是否支持卷扩展、是否支持文件系统挂载等。
  • NodeGetInfo:返回节点信息,包括节点 ID、拓扑域(如区域、可用区)以及最大支持的卷数量。

2.5 版本协商机制:确保兼容性的“握手协议”

CSI 规范使用 Major.Minor.Patch 的版本号格式。版本协商发生在 CO 与 CSI 插件之间,确保双方使用兼容的协议版本进行通信。

协商流程如下:

  1. CO 发起连接:CO 通过 Unix Domain Socket 或 TCP 连接到 CSI 插件。
  2. CO 调用 GetPluginInfo:获取插件的名称和版本信息。
  3. CO 调用 GetPluginCapabilities:获取插件支持的能力集。
  4. CO 选择版本:CO 根据自身支持的 CSI 版本列表和插件声明的版本,选择一个双方都支持的版本。通常,CO 会选择插件支持的最高版本,但不会超过 CO 自身支持的最高版本。
  5. CO 发送请求:CO 在后续的 gRPC 请求中,通过 gRPC 元数据(metadata)携带选定的版本号。
  6. 插件验证:插件收到请求后,验证版本号是否在其支持范围内。如果版本不兼容,插件应返回 Unimplemented 或 InvalidArgument 错误。
版本兼容性规则:
  • Major 版本不兼容:Major 版本号不同,表示协议不兼容,无法通信。
  • Minor 版本向后兼容:CO 可以使用比插件声明的 Minor 版本更低的版本进行通信(降级),但插件应支持所有低于其声明的 Minor 版本。
  • Patch 版本:Patch 版本仅用于 bug 修复,不影响协议兼容性。

2.6 三大服务交互流程与版本协商 SVG 图

下图展示了 Kubernetes(作为 CO)与 CSI 插件之间的典型交互流程,包括版本协商、Controller 服务调用和 Node 服务调用三个阶段。

CSI 三大服务交互与版本协商流程 CSI 三大服务交互与版本协商流程 Kubernetes (CO) Identity 服务 Controller 服务 Node 服务 存储后端 节点 OS / 文件系统 1. GetPluginInfo / GetPluginCapabilities 2. CreateVolume / ControllerPublishVolume 调用存储 API 3. NodeStageVolume / NodePublishVolume 挂载 / 格式化 说明: 1. 版本协商在 Identity 服务阶段完成,CO 通过 gRPC 元数据传递选定版本。 2. Controller 服务与 Node 服务是异步的,Node 服务依赖 Controller 服务完成卷的发布。

2.7 RPC 定义与版本协商的代码示例

以下是一个简化的 CSI Identity 服务 proto 定义,展示了版本协商相关的字段:

syntax = "proto3";
package csi.v1;

service Identity {
  rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse) {}
  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest) returns (GetPluginCapabilitiesResponse) {}
  rpc Probe(ProbeRequest) returns (ProbeResponse) {}
}

message GetPluginInfoResponse {
  string name = 1;
  string vendor_version = 2;
  // 插件声明的 CSI 规范版本
  repeated CSIComponentVersion manifest = 3;
}

message CSIComponentVersion {
  string name = 1; // 例如 "csi"
  string version = 2; // 例如 "1.5.0"
}

message GetPluginCapabilitiesResponse {
  repeated PluginCapability capabilities = 1;
}

message PluginCapability {
  message Service {
    enum Type {
      UNKNOWN = 0;
      CONTROLLER_SERVICE = 1;
      VOLUME_ACCESSIBILITY_CONSTRAINTS = 2;
      // ...
    }
    Type type = 1;
  }
  oneof type {
    Service service = 1;
    // ...
  }
}

在 Kubernetes CSI 侧车(sidecar)容器中,版本协商通常由 csi-provisioner 或 csi-attacher 负责。它们会读取插件返回的版本信息,并与自身支持的版本列表进行匹配,最终在 gRPC 调用中设置 csi.version 元数据。

最佳实践:
  • 始终实现 Identity 服务的 Probe 方法,并确保其在插件完全初始化前返回非就绪状态。
  • 在 GetPluginCapabilities 中准确声明能力,避免 CO 调用未实现的 RPC。
  • 版本协商时,优先使用插件支持的最高 Minor 版本,但不要超过 CO 自身支持的最高版本。