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 |
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)。
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 插件之间,确保双方使用兼容的协议版本进行通信。
协商流程如下:
- CO 发起连接:CO 通过 Unix Domain Socket 或 TCP 连接到 CSI 插件。
- CO 调用 GetPluginInfo:获取插件的名称和版本信息。
- CO 调用 GetPluginCapabilities:获取插件支持的能力集。
- CO 选择版本:CO 根据自身支持的 CSI 版本列表和插件声明的版本,选择一个双方都支持的版本。通常,CO 会选择插件支持的最高版本,但不会超过 CO 自身支持的最高版本。
- CO 发送请求:CO 在后续的 gRPC 请求中,通过 gRPC 元数据(metadata)携带选定的版本号。
- 插件验证:插件收到请求后,验证版本号是否在其支持范围内。如果版本不兼容,插件应返回
Unimplemented或InvalidArgument错误。
- Major 版本不兼容:Major 版本号不同,表示协议不兼容,无法通信。
- Minor 版本向后兼容:CO 可以使用比插件声明的 Minor 版本更低的版本进行通信(降级),但插件应支持所有低于其声明的 Minor 版本。
- Patch 版本:Patch 版本仅用于 bug 修复,不影响协议兼容性。
2.6 三大服务交互流程与版本协商 SVG 图
下图展示了 Kubernetes(作为 CO)与 CSI 插件之间的典型交互流程,包括版本协商、Controller 服务调用和 Node 服务调用三个阶段。
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 自身支持的最高版本。