Container Storage Interface (CSI)
Decoupling storage providers from Kubernetes — the plugin architecture
Why CSI Exists
- Storage drivers compiled into K8s binaries
- Every new storage backend = K8s code change + release cycle
- Bugs in storage drivers could crash the kubelet
- Vendors had to wait for K8s releases to ship fixes
- Growing codebase — 20+ storage plugins in
k8s.io/kubernetes
- Standardized gRPC interface between K8s and storage providers
- Storage vendors ship their own CSI drivers independently
- Decoupled release cycles — vendor ships when ready
- Same spec used by K8s, Mesos, Docker (K8s is the primary consumer)
- Crashes isolated to driver pods, not kubelet
Note
Key insight: CSI is not a K8s-specific standard. It is a container orchestrator-agnostic specification. K8s just happens to be its biggest adopter. The spec defines a set of gRPC services that any storage provider must implement.
CSI Architecture
A CSI driver deploys two components in the cluster:
- Usually 1 replica (with leader election for HA)
- Handles cluster-level operations:
-
- Create / delete volumes
- Attach / detach volumes from nodes
- Create / delete snapshots
- Expand volumes
- Runs anywhere in the cluster — doesn't need to be on the node with the storage
- Runs on every node that needs to mount volumes
- Handles node-level operations:
-
- Stage / unstage volumes on the node
- Mount / unmount volumes into pods
- Report node capabilities
- Communicates via Unix socket on the node
- Registered with kubelet via
node-driver-registrar
Topology Diagram
CSI Controller Container
CreateVolume, DeleteVolume, ControllerPublishVolume, ControllerUnpublishVolume, CreateSnapshot
Sidecar Containers
external-provisioner, external-attacher, external-snapshotter, external-resizer
CSI Node Container
NodeStageVolume, NodePublishVolume, NodeUnstageVolume, NodeUnpublishVolume
node-driver-registrar
Registers plugin with kubelet
CSI Node Container
NodeStageVolume, NodePublishVolume, NodeUnstageVolume, NodeUnpublishVolume
node-driver-registrar
Registers plugin with kubelet
Sidecar Containers
CSI drivers don't talk to the K8s API directly. Instead, sidecar containers (maintained by the K8s storage SIG) bridge the gap between K8s API objects and CSI gRPC calls:
| Sidecar | Role | Watches |
|---|---|---|
external-provisioner |
Creates/deletes PVs for PVCs | PVC objects |
external-attacher |
Attaches/detaches volumes to nodes | VolumeAttachment objects |
external-snapshotter |
Creates/restores volume snapshots | VolumeSnapshot objects |
external-resizer |
Expands volumes (online resize) | PVC resize requests |
node-driver-registrar |
Registers node plugin with kubelet | (runs on node plugin pod) |
livenessprobe |
Health check for the CSI driver | (sidecar health endpoint) |
Tip
Design pattern: The sidecars are the "K8s-aware" part. The CSI driver itself only needs to implement gRPC methods — it never imports K8s client libraries. This clean separation is what makes CSI portable across orchestrators.
Full Volume Lifecycle Through CSI
- Provision (Dynamic)
User creates PVC →external-provisionerwatches PVC → callsCreateVolumeon CSI controller → storage backend creates volume → external-provisioner creates PV → PVC binds to PV. - Attach
Pod is scheduled to a node → kubelet createsVolumeAttachmentobject →external-attacherwatches → callsControllerPublishVolumeon CSI controller → storage backend makes volume accessible to node (e.g., iSCSI login, EBS attach API call). - Stage (Node)
kubelet callsNodeStageVolumeon CSI node plugin → format filesystem if needed (mkfs.ext4) → mount to a global staging path on the node (e.g.,/var/lib/kubelet/plugins/kubernetes.io/csi/...). - Mount (Node)
kubelet callsNodePublishVolumeon CSI node plugin → bind mount from the staging path to the pod's volume directory (e.g.,/var/lib/kubelet/pods/<uid>/volumes/...). - Unmount
Pod terminates → kubelet callsNodeUnpublishVolume→ removes the bind mount from the pod's directory. - Unstage
No more pods using the volume on this node → kubelet callsNodeUnstageVolume→ unmount from the global staging path. - Detach
kubelet deletesVolumeAttachment→external-attachercallsControllerUnpublishVolume→ storage backend detaches volume from node (e.g., EBS detach API call). - Delete
PVC deleted +Deletereclaim policy →external-provisionercallsDeleteVolume→ storage backend deletes volume → PV deleted.
Visual Flow: Mount Path
CreateVolume
Storage backend creates volume
ControllerPublish
Volume attached to node
NodeStage
Format + mount to staging path
NodePublish
Bind mount into pod
NodeUnpublish
Unmount from pod
NodeUnstage
Unmount from staging
ControllerUnpublish
Detach from node
DeleteVolume
Backend deletes volume
Volume Snapshots
Snapshot Abstraction (mirrors PV/PVC pattern)
VolumeSnapshot
User's request (like PVC)
VolumeSnapshotContent
Actual snapshot resource (like PV)
VolumeSnapshotClass defines the snapshot provider — analogous to StorageClass for provisioning.
Creating a Snapshot
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: my-snapshot
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: my-data # PVC to snapshotRestoring from a Snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restored-data
spec:
storageClassName: fast-ssd
dataSource: # restore from snapshot
kind: VolumeSnapshot
name: my-snapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10GiNote
Cross-reference: For Proxmox storage and ZFS snapshots, see Virt 10 — Storage & HA.
Ephemeral CSI Volumes
Short-lived volumes created and deleted with the pod (like emptyDir, but via CSI). The pod spec includes the CSI volume definition inline — no PVC needed.
Use Cases
- Secret injection:
secrets-store-csi-drivermounts secrets from HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault directly into pods - Node-local temp storage: CSI drivers that provide node-local SSDs as ephemeral storage with better performance than
emptyDir - Identity injection: Workload identity tokens mounted as files
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: secrets
mountPath: /mnt/secrets
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: vault-secretsTip
Tip: Ephemeral CSI volumes don't create PV/PVC objects. They are defined inline in the pod spec, created when the pod starts, and destroyed when the pod is deleted. The lifecycle is managed entirely by the kubelet and the CSI node plugin.
CSI Driver Examples
| Driver | Storage | Typical Use |
|---|---|---|
ebs.csi.aws.com |
AWS EBS | Block storage (RWO) |
efs.csi.aws.com |
AWS EFS | File storage (RWX) |
pd.csi.storage.gke.io |
GCE Persistent Disk | Block storage (RWO) |
disk.csi.azure.com |
Azure Disk | Block storage (RWO) |
cephfs.csi.ceph.com |
CephFS | Distributed file storage (RWX) |
rbd.csi.ceph.com |
Ceph RBD | Block storage (RWO) |
secrets-store.csi.k8s.io |
Vault / cloud secrets | Secret injection (ephemeral) |
Note
Finding CSI drivers: The official list is at kubernetes-csi.github.io/docs/drivers.html. Most cloud providers maintain their own CSI drivers with Helm charts for easy installation.
CSI vs In-tree: Summary
| Aspect | In-tree (legacy) | CSI (modern) |
|---|---|---|
| Code location | Inside k8s.io/kubernetes |
Separate repo, vendor-maintained |
| Release cycle | Tied to K8s releases | Independent |
| Crash impact | Can crash kubelet | Isolated to driver pod |
| Interface | Go interfaces compiled in | gRPC over Unix socket |
| Portability | K8s only | Any CSI-compliant orchestrator |
| Status | Deprecated, being migrated | Standard, GA since K8s 1.13 |
Warning
Migration: All in-tree storage plugins are being migrated to CSI. The CSIMigration feature gate (GA since 1.25 for most drivers) transparently redirects in-tree volume API calls to the equivalent CSI driver. If you are using kubernetes.io/aws-ebs as a provisioner, it is already being handled by ebs.csi.aws.com behind the scenes.