Skip to content

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

CSI 规范架构示意图

一篇搞懂 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.gocsi_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.0EXPAND_VOLUME(卷扩容)
v1.2.0卷克隆(Clone)
v1.5.0快照(Snapshots)
v1.9.0ControllerGetVolumeGET_VOLUME_ATTRIBUTES
v1.13.0Snapshot 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 服务接口IdentityServerControllerServerNodeServer
处理卷访问模式VolumeCapability + AccessMode
声明插件能力ControllerServiceCapability + NodeServiceCapability
创建/删除卷CreateVolume / DeleteVolume
挂载卷到节点ControllerPublishVolume + NodeStageVolume + NodePublishVolume
卷扩容ControllerExpandVolume + NodeExpandVolume

这个库的核心价值就是定义了标准的接口,让存储驱动开发者只需要实现这些接口,就能被 Kubernetes 等容器编排系统对接。

参考链接

最后更新2026/07/30 08:51
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal