Appearance
深入解读 container-storage-interface/spec:CSI 的 Go 语言实现

一篇搞懂 CSI spec 库的结构、核心类型和使用方法
前言
如果你正在开发 Kubernetes CSI 驱动,或者想理解 CSI 插件的底层实现,github.com/container-storage-interface/spec 这个库是你绕不开的依赖。它是 CSI 规范的 Go 语言实现,定义了所有 gRPC 接口、消息结构和枚举类型。
本文会从实际使用的角度,带你完整过一遍这个库。
一、库的安装与导入
bash
go get github.com/container-storage-interface/[email protected]在 Go 代码中导入:
go
import (
"github.com/container-storage-interface/spec/lib/go/csi"
)注意:导入路径是
lib/go/csi,不是根目录。
二、目录结构解析
github.com/container-storage-interface/spec/
├── csi.proto # 原始 protobuf 定义(规范源头)
├── spec.md # 规范文档(人类可读)
├── VERSION # 当前版本号(现在是 1.13)
├── lib/
│ └── go/
│ └── csi/
│ ├── csi.pb.go # 所有 message 和 enum 的 Go 定义
│ └── csi_grpc.pb.go # gRPC 服务端/客户端接口
└── Makefile # 用于重新生成 pb.go 文件核心文件就是 csi.pb.go 和 csi_grpc.pb.go,前者是数据结构,后者是服务接口。
三、核心类型详解(最常用)
3.1 Volume —— 卷对象
go
type Volume struct {
VolumeId string
CapacityBytes int64
VolumeContext map[string]string
ContentSource *VolumeContentSource
AccessibleTopology []*Topology
}这是 CreateVolume 的返回值核心字段,代表一个被创建出来的存储卷。
3.2 VolumeCapability —— 访问能力(重中之重)
go
type VolumeCapability struct {
// 访问模式(必填)
AccessMode *VolumeCapability_AccessMode
// 二选一:Mount(文件系统)或 Block(裸块设备)
Mount *VolumeCapability_MountVolume
Block *VolumeCapability_BlockVolume
}访问模式枚举
go
type VolumeCapability_AccessMode_Mode int32
const (
VolumeCapability_AccessMode_SINGLE_NODE_WRITER_ONLY VolumeCapability_AccessMode_Mode = 0
VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 1
VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 2
VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER VolumeCapability_AccessMode_Mode = 3
VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER VolumeCapability_AccessMode_Mode = 4
)对应关系:
| 模式 | 含义 | 使用场景 |
|---|---|---|
SINGLE_NODE_WRITER_ONLY | 单节点读写 | 最常见的 RWO |
SINGLE_NODE_READER_ONLY | 单节点只读 | 只读数据卷 |
MULTI_NODE_READER_ONLY | 多节点只读 | 共享只读数据(如配置) |
MULTI_NODE_SINGLE_WRITER | 多节点单写 | 共享存储,但只允许一个写 |
MULTI_NODE_MULTI_WRITER | 多节点多写 | 共享读写,需应用自己处理冲突 |
MULTI_NODE_MULTI_WRITER是多节点读写模式,在分布式文件系统和共享存储类 CSI 驱动中被广泛使用。
3.3 Topology —— 拓扑信息
go
type Topology struct {
Segments map[string]string
}用于告诉 Kubernetes 卷在哪个区域/可用区,调度器据此调度 Pod。
四、三大 gRPC 服务接口
4.1 Identity 服务 —— 插件自我介绍
go
type IdentityServer interface {
GetPluginInfo(ctx context.Context, req *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
GetPluginCapabilities(ctx context.Context, req *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)
Probe(ctx context.Context, req *ProbeRequest) (*ProbeResponse, error)
}| RPC | 作用 |
|---|---|
GetPluginInfo | 返回驱动名称、版本号 |
GetPluginCapabilities | 返回插件支持的能力 |
Probe | 健康检查,Kubernetes 会定期调用 |
4.2 Controller 服务 —— 卷生命周期管理
go
type ControllerServer interface {
CreateVolume(...) (*CreateVolumeResponse, error)
DeleteVolume(...) (*DeleteVolumeResponse, error)
ControllerPublishVolume(...) (*ControllerPublishVolumeResponse, error) // 将卷"发布"到节点
ControllerUnpublishVolume(...) (*ControllerUnpublishVolumeResponse, error)
ValidateVolumeCapabilities(...) (*ValidateVolumeCapabilitiesResponse, error)
ListVolumes(...) (*ListVolumesResponse, error)
GetCapacity(...) (*GetCapacityResponse, error)
ControllerGetCapabilities(...) (*ControllerGetCapabilitiesResponse, error)
CreateSnapshot(...) (*CreateSnapshotResponse, error)
DeleteSnapshot(...) (*DeleteSnapshotResponse, error)
ListSnapshots(...) (*ListSnapshotsResponse, error)
ControllerExpandVolume(...) (*ControllerExpandVolumeResponse, error) // v1.1+ 新增
ControllerGetVolume(...) (*ControllerGetVolumeResponse, error) // v1.9+ 新增
}关键调用链:
CreateVolume:Kubernetes 创建 PVC 时调用 → 你需要在后端实际创建存储ControllerPublishVolume:Pod 被调度到某节点时调用 → 将卷挂载到该节点(如 Attach 云盘)ControllerExpandVolume:PVC 扩容时调用 → 扩展后端存储容量
4.3 Node 服务 —— 节点上的挂载操作
go
type NodeServer interface {
NodeStageVolume(...) (*NodeStageVolumeResponse, error) // 格式化+挂载到临时路径
NodeUnstageVolume(...) (*NodeUnstageVolumeResponse, error)
NodePublishVolume(...) (*NodePublishVolumeResponse, error) // 挂载到 Pod 目标路径
NodeUnpublishVolume(...) (*NodeUnpublishVolumeResponse, error)
NodeGetInfo(...) (*NodeGetInfoResponse, error) // 返回节点 ID 和拓扑信息
NodeGetCapabilities(...) (*NodeGetCapabilitiesResponse, error)
NodeGetVolumeStats(...) (*NodeGetVolumeStatsResponse, error) // v1.0+ 获取卷统计
NodeExpandVolume(...) (*NodeExpandVolumeResponse, error) // v1.1+ 节点侧扩容
}Stage vs Publish 的区别:
| 阶段 | 操作 | 说明 |
|---|---|---|
NodeStageVolume | 格式化 + 挂载到 /var/lib/kubelet/plugins/.../volumes/ | 一次性的准备动作 |
NodePublishVolume | 从 Stage 路径 bind mount 到 Pod 目录 | 每个 Pod 单独挂载 |
简单理解:Stage 做物理准备,Publish 做逻辑挂载。
五、能力声明(Capabilities)
CSI 要求插件明确声明自己支持哪些功能,Kubernetes 的 external sidecar 会根据这些能力决定是否调用对应的 RPC。
5.1 Controller 能力
go
type ControllerServiceCapability_RPC_Type int32
const (
ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME = 0
ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME = 1
ControllerServiceCapability_RPC_LIST_VOLUMES = 2
ControllerServiceCapability_RPC_GET_CAPACITY = 3
ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT = 4
ControllerServiceCapability_RPC_LIST_SNAPSHOTS = 5
ControllerServiceCapability_RPC_CLONE_VOLUME = 6
ControllerServiceCapability_RPC_EXPAND_VOLUME = 7 // v1.1+
ControllerServiceCapability_RPC_GET_VOLUME = 8 // v1.9+
// ... 更多
)5.2 Node 能力
go
type NodeServiceCapability_RPC_Type int32
const (
NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME = 0
NodeServiceCapability_RPC_GET_VOLUME_STATS = 1
NodeServiceCapability_RPC_EXPAND_VOLUME = 2 // v1.1+
NodeServiceCapability_RPC_GET_VOLUME_ATTRIBUTES = 3 // v1.13+
)实际 CSI 驱动项目里注册的能力示例:
go
// Controller 侧
csiDriver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, // 创建/删除卷
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, // 挂载/卸载
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, // 扩容
})
// Node 侧
csiDriver.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{
csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
})这意味着该驱动实现了:创建/删除卷、挂载/卸载卷、扩容卷(Controller 和 Node 两侧都支持扩容)。
六、在实际 CSI 驱动中使用(代码示例)
6.1 初始化驱动
go
// 通用的 CSI Driver 初始化示例
func NewDriver(nodeID, endpoint, driverName, version string) *MyDriver {
d := &MyDriver{endpoint: endpoint}
// 创建 CSI Driver 实例
csiDriver := csiprovider.NewCSIDriver(driverName, version, nodeID)
// 注册 Controller 侧能力
csiDriver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
})
// 注册 Node 侧能力
csiDriver.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{
csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
})
d.csiDriver = csiDriver
return d
}6.2 实现 CreateVolume 的骨架
go
func (d *MyDriver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
// 1. 从 req 中提取参数
name := req.GetName()
size := req.GetCapacityRange().GetRequiredBytes()
caps := req.GetVolumeCapabilities()
// 2. 校验 VolumeCapability
for _, cap := range caps {
if !isValidCapability(cap) {
return nil, status.Error(codes.InvalidArgument, "unsupported volume capability")
}
}
// 3. 调用后端存储 API 实际创建卷(伪代码)
// volID, err := d.storageClient.CreateVolume(ctx, name, size)
// if err != nil { return nil, err }
// 4. 构造响应
return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: "vol-xxxxxxxx",
CapacityBytes: size,
VolumeContext: map[string]string{
"createdBy": "my-csi-driver",
},
},
}, nil
}6.3 实现 NodePublishVolume 的骨架
go
func (d *MyDriver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
volID := req.GetVolumeId()
volumeCap := req.GetVolumeCapability()
// 1. 判断是 Mount 还是 Block
if mountCap := volumeCap.GetMount(); mountCap != nil {
// 文件系统挂载
// fsType := mountCap.GetFsType()
// err := d.mountDevice(volID, targetPath, fsType)
} else if blockCap := volumeCap.GetBlock(); blockCap != nil {
// 裸块设备
// err := d.mapBlockDevice(volID, targetPath)
} else {
return nil, status.Error(codes.InvalidArgument, "missing mount or block capability")
}
return &csi.NodePublishVolumeResponse{}, nil
}七、版本演进(从 VERSION 文件看)
当前版本:v1.13.0(2025年10月)
| 版本 | 关键新增特性 |
|---|---|
| v1.0.0 | 基础规范(2019) |
| v1.1.0 | EXPAND_VOLUME(卷扩容) |
| v1.2.0 | 卷克隆(Clone) |
| v1.5.0 | 快照(Snapshots) |
| v1.9.0 | ControllerGetVolume、GET_VOLUME_ATTRIBUTES |
| v1.13.0 | Snapshot Topology(2026.7 最新提交) |
最新提交 b516e92(2026.7.22)引入了 Snapshot Topology,允许快照也具备拓扑感知能力。
八、常用代码片段速查
校验 VolumeCapability
go
func isValidCapability(cap *csi.VolumeCapability) bool {
mode := cap.GetAccessMode().GetMode()
if mode != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER {
return false
}
if cap.GetMount() == nil && cap.GetBlock() == nil {
return false
}
return true
}提取参数
go
func getParameter(req *csi.CreateVolumeRequest, key string) string {
if req.GetParameters() == nil {
return ""
}
return req.GetParameters()[key]
}构造错误
go
import "google.golang.org/grpc/codes"
import "google.golang.org/grpc/status"
func invalidArgument(msg string) error {
return status.Error(codes.InvalidArgument, msg)
}
func notFound(msg string) error {
return status.Error(codes.NotFound, msg)
}九、总结
| 你想做什么 | 看这个 |
|---|---|
| 理解 CSI 服务接口 | IdentityServer、ControllerServer、NodeServer |
| 处理卷访问模式 | VolumeCapability + AccessMode |
| 声明插件能力 | ControllerServiceCapability + NodeServiceCapability |
| 创建/删除卷 | CreateVolume / DeleteVolume |
| 挂载卷到节点 | ControllerPublishVolume + NodeStageVolume + NodePublishVolume |
| 卷扩容 | ControllerExpandVolume + NodeExpandVolume |
这个库的核心价值就是定义了标准的接口,让存储驱动开发者只需要实现这些接口,就能被 Kubernetes 等容器编排系统对接。
