Comprehensive guide to CSI drivers, volume management, and persistent storage in Kubernetes.
Container Storage Interface (CSI) Basics
Comprehensive guide to CSI drivers, volume management, and persistent storage in Kubernetes.
Overview
The Container Storage Interface (CSI) is a standardised API that enables container orchestration systems like Kubernetes to expose arbitrary storage systems to containerised workloads. CSI drivers allow storage vendors to develop plugins once and have them work across different container orchestration platforms, replacing the legacy in-tree volume plugins.
CSI provides dynamic provisioning, volume expansion, snapshotting, and cloning capabilities, making persistent storage management in Kubernetes more flexible and vendor-agnostic.
graph TB
subgraph "Kubernetes Control Plane"
API[API Server]
CSIAttach[External Attacher]
CSIProvision[External Provisioner]
CSIResize[External Resizer]
CSISnapshot[External Snapshotter]
end
subgraph "Node"
Kubelet[Kubelet]
CSINode[CSI Node Plugin]
CSIReg[Node Driver Registrar]
end
subgraph "CSI Driver"
Controller[CSI Controller Plugin]
Identity[Identity Service]
end
subgraph "Storage Backend"
Storage[(Storage System<br/>Ceph/Longhorn/NFS/EBS)]
end
API --> CSIProvision
API --> CSIAttach
API --> CSIResize
API --> CSISnapshot
CSIProvision --> Controller
CSIAttach --> Controller
CSIResize --> Controller
CSISnapshot --> Controller
Controller --> Identity
Controller --> Storage
Kubelet --> CSINode
CSINode --> CSIReg
CSINode --> Storage
style Storage fill:#FFE6E6
style Controller fill:#E6F3FF
style CSINode fill:#E6FFE6
CSI Architecture and Components
Key Concepts
CSI Driver: Plugin implementing the CSI specification for a storage backend
External Provisioner: Watches PVCs and triggers volume creation via CSI driver
External Snapshotter: Manages volume snapshots and restoration
Node Plugin: Runs on each node to mount/unmount volumes
Storage Class: Template defining volume parameters and which CSI driver to use
CSI Driver Lifecycle
sequenceDiagram
participant User
participant K8s as Kubernetes API
participant Prov as External Provisioner
participant CSI as CSI Controller
participant Storage
participant Node as CSI Node Plugin
User->>K8s: Create PVC
K8s->>Prov: PVC Created Event
Prov->>CSI: CreateVolume()
CSI->>Storage: Provision Volume
Storage-->>CSI: Volume Created
CSI-->>Prov: Volume Info
Prov->>K8s: Create PV
K8s->>K8s: Bind PVC to PV
User->>K8s: Create Pod with PVC
K8s->>Node: Schedule Pod
Node->>CSI: NodeStageVolume()
CSI->>Storage: Attach Volume
Node->>CSI: NodePublishVolume()
CSI-->>Node: Mount Complete
Node-->>K8s: Pod Running
Installing CSI Drivers
# List available CSI drivers on node
kubectlgetcsidrivers
# Describe CSI driver
kubectldescribecsidriverebs.csi.aws.com
# View CSI node information
kubectlgetcsinodes
kubectldescribecsinodeworker-node-1
# Check CSI driver pods
kubectlgetpods-nkube-system|grepcsi
Storage Classes
Storage Classes define the provisioner, parameters, and policies for dynamically provisioned volumes.
apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:ebs-gp3provisioner:ebs.csi.aws.comparameters:type:gp3# gp2, gp3, io1, io2, st1, sc1iops:"3000"# Only for gp3, io1, io2throughput:"125"# Only for gp3 (MiB/s)encrypted:"true"fsType:ext4# ext3, ext4, xfsvolumeBindingMode:WaitForFirstConsumerallowVolumeExpansion:truereclaimPolicy:Retain
# List storage classes
kubectlgetstorageclass
kubectlgetsc
# Describe storage class
kubectldescribescfast-ssd
# Set default storage class
kubectlpatchstorageclassfast-ssd-p'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'# Remove default storage class annotation
kubectlpatchstorageclassfast-ssd-p'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'# Create storage class
kubectlapply-fstorageclass.yaml
# Delete storage class (doesn't affect existing PVs)
kubectldeletescfast-ssd
Volume Provisioning and PersistentVolumeClaims
Dynamic Provisioning
# pvc-dynamic.yamlapiVersion:v1kind:PersistentVolumeClaimmetadata:name:web-storagenamespace:productionspec:accessModes:-ReadWriteOncestorageClassName:fast-ssdresources:requests:storage:100Gi# Optional: Volume selector for specific PVselector:matchLabels:environment:productiontier:frontend
# ReadWriteOnce - Block storage (most common)apiVersion:v1kind:PersistentVolumeClaimmetadata:name:database-storagespec:accessModes:-ReadWriteOnce# Single node, single or multiple pods on same nodestorageClassName:ebs-gp3resources:requests:storage:100Gi
# ReadWriteMany - Shared file storageapiVersion:v1kind:PersistentVolumeClaimmetadata:name:shared-assetsspec:accessModes:-ReadWriteMany# Multiple nodes can read and writestorageClassName:efs-scresources:requests:storage:500Gi
# ReadWriteOncePod - Single pod access (GA k8s 1.29; alpha from 1.22)apiVersion:v1kind:PersistentVolumeClaimmetadata:name:single-pod-storagespec:accessModes:-ReadWriteOncePod# Only one pod cluster-widestorageClassName:fast-ssdresources:requests:storage:50Gi
Reclaim Policies
Policy
Behaviour
Use Case
Delete
Volume deleted when PVC deleted
Ephemeral data, development
Retain
Volume retained when PVC deleted, manual cleanup required
Production data, backups
Recycle
Deprecated - Volume scrubbed (rm -rf) and made available
Legacy only
# StorageClass with Retain policyapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:retain-storageprovisioner:ebs.csi.aws.comreclaimPolicy:Retain# Volume kept after PVC deletionvolumeBindingMode:WaitForFirstConsumer
# Change reclaim policy on existing PV
kubectlpatchpvpvc-12345678-1234-1234-1234-123456789012-p'{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'# Reclaim retained PV for reuse# 1. Delete the PVC (PV enters Released state)
kubectldeletepvcweb-storage
# 2. Remove claimRef from PV
kubectlpatchpvpvc-12345678-1234-1234-1234-123456789012-p'{"spec":{"claimRef":null}}'# 3. PV becomes Available again
kubectlgetpv
Volume Binding Modes
# Immediate - Volume provisioned immediatelyapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:immediate-scprovisioner:ebs.csi.aws.comvolumeBindingMode:Immediate# Provision as soon as PVC created
# WaitForFirstConsumer - Volume provisioned when pod scheduled (recommended)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:wait-scprovisioner:ebs.csi.aws.comvolumeBindingMode:WaitForFirstConsumer# Provision in correct AZ/zone
Volume Snapshots and Cloning
Volume Snapshot Classes
# volumesnapshotclass.yamlapiVersion:snapshot.storage.k8s.io/v1kind:VolumeSnapshotClassmetadata:name:csi-snapclassannotations:snapshot.storage.kubernetes.io/is-default-class:"true"driver:ebs.csi.aws.comdeletionPolicy:Delete# or Retainparameters:# Driver-specific parametersencrypted:"true"
# pod-local-affinity.yamlapiVersion:v1kind:Podmetadata:name:app-with-local-storagespec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:kubernetes.io/hostnameoperator:Invalues:-worker-node-1# Pin to specific nodecontainers:-name:appimage:nginx:1.25volumeMounts:-name:datamountPath:/datavolumes:-name:datapersistentVolumeClaim:claimName:local-pvc
Longhorn
Longhorn is a lightweight, reliable distributed block storage system for Kubernetes, providing enterprise-grade persistent storage with snapshots, backups, and disaster recovery.
# storageclass-longhorn-single.yaml - Single replica (development)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:longhorn-singleprovisioner:driver.longhorn.ioallowVolumeExpansion:trueparameters:numberOfReplicas:"1"dataLocality:"strict-local"# Keep data on same node---# storageclass-longhorn-ha.yaml - High availabilityapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:longhorn-haprovisioner:driver.longhorn.ioallowVolumeExpansion:trueparameters:numberOfReplicas:"3"dataLocality:"disabled"replicaAutoBalance:"least-effort"# best-effort, least-effort, disabled
Longhorn Recurring Backups
# recurring-job.yamlapiVersion:longhorn.io/v1beta2kind:RecurringJobmetadata:name:daily-backupnamespace:longhorn-systemspec:cron:"02***"# Daily at 2 AMtask:"backup"groups:-defaultretain:7# Keep 7 backupsconcurrency:2labels:interval:daily---apiVersion:longhorn.io/v1beta2kind:RecurringJobmetadata:name:hourly-snapshotnamespace:longhorn-systemspec:cron:"0****"# Every hourtask:"snapshot"groups:-defaultretain:24concurrency:5labels:interval:hourly
Longhorn Backup Target Configuration
# backup-target.yaml - S3 backupapiVersion:v1kind:Secretmetadata:name:aws-secretnamespace:longhorn-systemtype:Opaquedata:AWS_ACCESS_KEY_ID:base64_encoded_access_keyAWS_SECRET_ACCESS_KEY:base64_encoded_secret_keyAWS_ENDPOINTS:base64_encoded_s3_endpoint# Optional for S3-compatible---# Apply via Longhorn settings# Backup Target: s3://bucket-name@region/# Backup Target Credential Secret: aws-secret
# Configure backup target via kubectl
kubectleditsettings.longhorn.iobackup-target-nlonghorn-system
# Set value to: s3://my-backup-bucket@us-east-1/
kubectleditsettings.longhorn.iobackup-target-credential-secret-nlonghorn-system
# Set value to: aws-secret
# Check if StorageClass allows expansion
kubectlgetscfast-ssd-ojsonpath='{.allowVolumeExpansion}'# Expand PVC
kubectlpatchpvcweb-storage-p'{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'# Check expansion status
kubectldescribepvcweb-storage|grep-A5Conditions
# For StatefulSet volumes, expand via patch
kubectlpatchpvcdata-mysql-0-p'{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
File System Expansion
# Most CSI drivers auto-expand the filesystem# Some require pod restart or manual intervention# Check PVC conditions for filesystem resize statuskubectl get pvc web-storage -o jsonpath='{.status.conditions[?(@.type=="FileSystemResizePending")]}'
# If filesystem resize is pending, restart pod
kubectlrolloutrestartdeployment/web-server
# Or delete and recreate pod
kubectldeletepodweb-server
Offline Expansion
# For volumes requiring offline expansion:# 1. Scale down workload
kubectlscaledeploymentweb-server--replicas=0# 2. Expand PVC
kubectlpatchpvcweb-storage-p'{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'# 3. Wait for expansion to complete
kubectlwait--for=condition=FileSystemResizeSuccessfulpvc/web-storage--timeout=300s
# 4. Scale up workload
kubectlscaledeploymentweb-server--replicas=3
Troubleshooting Storage Issues
Volume Mount Failures
# Check pod events
kubectldescribepodweb-server
# Common issues in events:# - FailedMount: Volume not attached# - FailedAttachVolume: Volume attach timeout# - MountVolume.SetUp failed: mount failed# Check PVC binding
kubectlgetpvcweb-storage
# Status should be "Bound"# Check PV status
kubectlgetpv
kubectldescribepvpvc-12345678-1234-1234-1234-123456789012
# Check CSI driver pods
kubectlgetpods-nkube-system|grepcsi
# View CSI driver logs
kubectllogs-nkube-systemcsi-controller-pod-ccsi-provisioner
kubectllogs-nkube-systemcsi-node-pod-ccsi-driver
PVC Stuck in Pending
# Check PVC events
kubectldescribepvcweb-storage
# Common causes:# 1. No matching StorageClass
kubectlgetsc
# 2. No available PVs for static provisioning
kubectlgetpv
# 3. Insufficient resources in cloud provider# Check controller logs# 4. Volume binding mode is WaitForFirstConsumer but no pod scheduled
kubectlgetpods-owide
# 5. Topology constraints not met
kubectldescribepvcweb-storage|grep-A10"Topology"
PV Stuck in Released
# View PV details
kubectldescribepvpvc-12345678-1234-1234-1234-123456789012
# Check claimRef
kubectlgetpvpvc-12345678-1234-1234-1234-123456789012-oyaml|grep-A5claimRef
# Remove claimRef to make Available
kubectlpatchpvpvc-12345678-1234-1234-1234-123456789012-p'{"spec":{"claimRef":null}}'# Or edit directly
kubectleditpvpvc-12345678-1234-1234-1234-123456789012
# Remove entire claimRef section
Volume Attach/Detach Issues
# Check volume attachments
kubectlgetvolumeattachment
kubectldescribevolumeattachmentcsi-abc123
# Check node CSI driver
kubectlgetcsinodes
kubectldescribecsinodeworker-node-1
# Force detach (dangerous - ensure volume not in use)
kubectldeletevolumeattachmentcsi-abc123
# Check attach/detach controller logs
kubectllogs-nkube-systemkube-controller-manager-master-ckube-controller-manager|grepattachdetach
# Check PV/PVC for IOPS/throughput limits (cloud providers)
kubectlgetpvpvc-12345678-1234-1234-1234-123456789012-oyaml|grep-iiops
kubectlgetpvpvc-12345678-1234-1234-1234-123456789012-oyaml|grep-ithroughput
# Check for I/O throttling in pod
kubectlexec-itweb-server--iostat-x1# Test disk performance
kubectlexec-itweb-server--ddif=/dev/zeroof=/data/testfilebs=1Mcount=1024oflag=direct
# For Ceph/Rook
kubectlexec-it-nrook-cephdeploy/rook-ceph-tools--cephosdperf
# For Longhorn# Check volume metrics in Longhorn UI or via:
kubectlgetvolumes.longhorn.io-nlonghorn-system-owide
*Longhorn RWX is native since v1.6 — a built-in Share Manager pod (internal NFSv4) serves each RWX volume; no external NFS provisioner required (earlier versions needed one).
Common Issues and Solutions
Provisioning Issues
Issue
Cause
Solution
PVC stuck in Pending
StorageClass not found
Verify StorageClass exists: kubectl get sc
PVC stuck in Pending
No nodes in correct topology
Check volumeBindingMode and node zones
PVC stuck in Pending
Quota exceeded
Check namespace ResourceQuota and cloud provider limits