Skip to content

In-depth Analysis of container-storage-interface/spec: The Go Implementation of CSI ​

CSI specification architecture diagram

Understand the structure, core types, and usage of the CSI spec library in one article

Preface ​

If you are developing a Kubernetes CSI driver, or want to understand the underlying implementation of CSI plugins,github.com/container-storage-interface/spec This library is a dependency you can't avoid. It is the Go implementation of the CSI specification, defining all gRPC interfaces, message structures, and enum types.

This article will proceed from Actual use from the perspective of, take you through this library in full.

I. Installation and Import of Libraries ​

bash
go get github.com/container-storage-interface/[email protected]

Import in Go code:

go
import (
    "github.com/container-storage-interface/spec/lib/go/csi"
)

Note: The import path is lib/go/csi, not the root directory.

II. Directory Structure Analysis ​

github.com/container-storage-interface/spec/
├── csi.proto              # Original protobuf definition (canonical source)
├── spec.md                # Specification document (human-readable)
├── VERSION                # Current version number (currently 1.13)
├── lib/
│   └── go/
│       └── csi/
│           ├── csi.pb.go      # Go definitions for all messages and enums
│           └── csi_grpc.pb.go # gRPC server/client interface
└── Makefile               # Used to regenerate the pb.go file

The core file is csi.pb.go and csi_grpc.pb.go, the former is a data structure, and the latter is a service interface.

III. Detailed Explanation of Core Types (Most Commonly Used) ​

3.1 Volume— roll object ​

go
type Volume struct {
    VolumeId           string
    CapacityBytes      int64
    VolumeContext      map[string]string
    ContentSource      *VolumeContentSource
    AccessibleTopology []*Topology
}

This is CreateVolume the core field of the return value, representing a created storage volume.

3.2 VolumeCapability—— Access capability (top priority) ​

go
type VolumeCapability struct {
    // Access mode (required)
    AccessMode *VolumeCapability_AccessMode
    // Choose one: Mount (filesystem) or Block (raw block device)
    Mount *VolumeCapability_MountVolume
    Block *VolumeCapability_BlockVolume
}

Access mode enumeration ​

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
)

Correspondence:

ModemeaningUse cases
SINGLE_NODE_WRITER_ONLYSingle-node read/writeThe most common RWO
SINGLE_NODE_READER_ONLYSingle-node read-onlyRead-only data volume
MULTI_NODE_READER_ONLYMulti-node read-onlyShared read-only data (such as configuration)
MULTI_NODE_SINGLE_WRITERMulti-node single-writeShared storage, but only one write is allowed
MULTI_NODE_MULTI_WRITERMulti-node multi-writeShared read/write; the application must handle conflicts itself.

MULTI_NODE_MULTI_WRITER It is a multi-node read-write mode and is widely used in distributed file systems and shared storage CSI drivers.

3.3 Topology—— topology information ​

go
type Topology struct {
    Segments map[string]string
}

Used to tell Kubernetes which region/availability zone a volume is in, so the scheduler can schedule Pods accordingly.

IV. Three Major gRPC Service Interfaces ​

4.1 Identity Service — Plugin Self-Introduction ​

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)
}
RPCeffect
GetPluginInfoReturn the driver name and version number.
GetPluginCapabilitiesReturn the capabilities supported by the plugin.
ProbeHealth check, Kubernetes will call it periodically.

4.2 Controller Service — Volume Lifecycle Management ​

go
type ControllerServer interface {
    CreateVolume(...) (*CreateVolumeResponse, error)
    DeleteVolume(...) (*DeleteVolumeResponse, error)
    ControllerPublishVolume(...) (*ControllerPublishVolumeResponse, error)   // Publish the volume to the node.
    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)      // Added in v1.1+
    ControllerGetVolume(...) (*ControllerGetVolumeResponse, error)            // New in v1.9+
}

critical call chain:

  • CreateVolume: Called when Kubernetes creates a PVC → You need to actually create the storage on the backend

  • ControllerPublishVolume: Called when a Pod is scheduled to a node → attach the volume to that node (e.g., Attach a cloud disk)

  • ControllerExpandVolume: Called when a PVC is expanded → expand backend storage capacity

4.3 Node Service — Mount Operations on Nodes ​

go
type NodeServer interface {
    NodeStageVolume(...) (*NodeStageVolumeResponse, error)     // Format + mount to a temporary path
    NodeUnstageVolume(...) (*NodeUnstageVolumeResponse, error)
    NodePublishVolume(...) (*NodePublishVolumeResponse, error) // Mount to the Pod target path
    NodeUnpublishVolume(...) (*NodeUnpublishVolumeResponse, error)
    NodeGetInfo(...) (*NodeGetInfoResponse, error)             // Return node ID and topology information
    NodeGetCapabilities(...) (*NodeGetCapabilitiesResponse, error)
    NodeGetVolumeStats(...) (*NodeGetVolumeStatsResponse, error) // v1.0+ Get volume statistics
    NodeExpandVolume(...) (*NodeExpandVolumeResponse, error)   // v1.1+ Node-side scaling
}

The difference between Stage vs Publish:

StageOperationDescription
NodeStageVolumeFormat + mount to /var/lib/kubelet/plugins/.../volumes/One-time preparatory action
NodePublishVolumeBind mount from the Stage path to the Pod directoryEach Pod is mounted separately.

Simple understanding:Stage Make physical preparations,Publish Perform logical mounting.

V. Capability Statement (Capabilities) ​

CSI requires plugins to explicitly declare which capabilities they support, and Kubernetes external sidecars decide whether to call the corresponding RPC based on these capabilities.

5.1 Controller Capabilities ​

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+
    // ... more
)

5.2 Node Capabilities ​

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+
)

Actual CSI driver project Examples of capabilities registered in:

go
// Controller side
csiDriver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
    csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,  // Create/Delete Volume
    csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, // Mount/Unmount
    csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,         // Capacity expansion
})

// Node side
csiDriver.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{
    csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
})

This means the driver implements: create/delete volumes, mount/unmount volumes, and expand volumes (expansion is supported on both the Controller and Node sides).

6. Usage in an Actual CSI Driver (Code Example) ​

6.1 Initializing the Driver ​

go
// Generic CSI Driver initialization example
func NewDriver(nodeID, endpoint, driverName, version string) *MyDriver {
    d := &MyDriver{endpoint: endpoint}

    // Create a CSI Driver instance
    csiDriver := csiprovider.NewCSIDriver(driverName, version, nodeID)

    // Register Controller-side capabilities
    csiDriver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
        csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
    })

    // Register Node-side capabilities
    csiDriver.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{
        csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
    })

    d.csiDriver = csiDriver
    return d
}

6.2 Implement the skeleton of CreateVolume ​

go
func (d *MyDriver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
    // 1. Extract parameters from req
    name := req.GetName()
    size := req.GetCapacityRange().GetRequiredBytes()
    caps := req.GetVolumeCapabilities()

    // 2. Validate VolumeCapability
    for _, cap := range caps {
        if !isValidCapability(cap) {
            return nil, status.Error(codes.InvalidArgument, "unsupported volume capability")
        }
    }

    // 3. Call the backend storage API to actually create the volume (pseudocode)
    // volID, err := d.storageClient.CreateVolume(ctx, name, size)
    // if err != nil { return nil, err }

    // 4. Construct the response
    return &csi.CreateVolumeResponse{
        Volume: &csi.Volume{
            VolumeId:      "vol-xxxxxxxx",
            CapacityBytes: size,
            VolumeContext: map[string]string{
                "createdBy": "my-csi-driver",
            },
        },
    }, nil
}

6.3 Implement the skeleton of 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. Determine whether it is Mount or Block
    if mountCap := volumeCap.GetMount(); mountCap != nil {
        // File system mount
        // fsType := mountCap.GetFsType()
        // err := d.mountDevice(volID, targetPath, fsType)
    } else if blockCap := volumeCap.GetBlock(); blockCap != nil {
        // raw block device
        // err := d.mapBlockDevice(volID, targetPath)
    } else {
        return nil, status.Error(codes.InvalidArgument, "missing mount or block capability")
    }

    return &csi.NodePublishVolumeResponse{}, nil
}

VII. Version Evolution (as Seen from the VERSION File) ​

Current version:v1.13.0(October 2025)

VersionKey new features
v1.0.0Basic Specification (2019)
v1.1.0EXPAND_VOLUME(Volume expansion)
v1.2.0Volume Clone (Clone)
v1.5.0Snapshots
v1.9.0ControllerGetVolume,GET_VOLUME_ATTRIBUTES
v1.13.0Snapshot Topology(Latest submission as of July 2026)

Latest commit b516e92(2026.7.22) introduced Snapshot Topology, allowing snapshots to also have topology-awareness capabilities.

VIII. Common Code Snippets Quick Reference ​

Validate 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
}

Extract parameters ​

go
func getParameter(req *csi.CreateVolumeRequest, key string) string {
    if req.GetParameters() == nil {
        return ""
    }
    return req.GetParameters()[key]
}

Construction error ​

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)
}

IX. Summary ​

What do you want to do?Look at this.
Understanding the CSI service interfaceIdentityServer,ControllerServer,NodeServer
Processing volume access modeVolumeCapability+AccessMode
Declare plugin capabilitiesControllerServiceCapability+NodeServiceCapability
Create/Delete VolumeCreateVolume/DeleteVolume
Mount volume to nodeControllerPublishVolume+NodeStageVolume+NodePublishVolume
Volume expansionControllerExpandVolume+NodeExpandVolume

The core value of this library is Defined a standard interface, enabling storage driver developers to integrate with container orchestration systems such as Kubernetes simply by implementing these interfaces.

Last updated on11/07/2026, 02:16 PM
@fishfinal | @fishfinals | [email protected]