Storage
Manage Kubernetes storage resources including PersistentVolumes, PersistentVolumeClaims, StorageClasses, and ResourceQuotas for persistent data storage.
Storage Resources
PersistentVolumes (PV)
Cluster-wide storage resources that exist independently of pods and persist beyond pod lifecycle.
Viewing PersistentVolumes
| Column | Description |
|---|---|
| Name | PV name |
| Status | Available, Bound, Released, Failed |
| Claim | PVC using this PV (if bound) |
| Storage Class | StorageClass used for provisioning |
| Capacity | Storage size (e.g., 100Gi) |
| Access Modes | ReadWriteOnce, ReadOnlyMany, ReadWriteMany |
| Reclaim Policy | Retain, Delete, or Recycle |
| Age | Time since creation |
PV Status Lifecycle
| Status | Meaning |
|---|---|
| Available | PV is free and not yet bound to a PVC |
| Bound | PV is bound to a PVC and in use |
| Released | PVC was deleted but PV not yet reclaimed |
| Failed | Automatic reclamation failed |
PV Details
Click a PV to view:
- Capacity: Storage size in Gi
- Access Modes:
ReadWriteOnce(RWO) - Single node read/writeReadOnlyMany(ROX) - Multiple nodes read-onlyReadWriteMany(RWX) - Multiple nodes read/write
- Reclaim Policy: What happens when PVC is deleted
- Storage Class: Provisioner used to create PV
- Volume Source: AWS EBS, GCE PD, NFS, etc.
- Node Affinity: Node constraints for volume attachment
Reclaim Policies
| Policy | Behavior |
|---|---|
| Retain | PV remains after PVC deletion (manual cleanup needed) |
| Delete | PV and underlying storage deleted automatically |
| Recycle | Data scrubbed, PV becomes available again (deprecated) |
PersistentVolumeClaims (PVC)
User requests for storage that bind to PersistentVolumes or trigger dynamic provisioning.
Viewing PersistentVolumeClaims
| Column | Description |
|---|---|
| Name | PVC name |
| Namespace | Kubernetes namespace |
| Status | Pending, Bound, Lost |
| Volume | Bound PV name |
| Capacity | Requested storage size |
| Access Modes | RWO, ROX, RWX |
| Storage Class | StorageClass for dynamic provisioning |
| Age | Time since creation |
PVC Status
| Status | Meaning | Action Needed |
|---|---|---|
| Bound | PVC bound to PV, ready to use | None |
| Pending | Waiting for PV or provisioning | Check StorageClass, capacity, node availability |
| Lost | Bound PV deleted or unavailable | Recreate PV or restore volume |
PVC Binding Process
PVC Created
User creates PVC requesting specific capacity and access mode.
Matching PV Search
Kubernetes looks for available PV matching requirements.
Bind or Provision
- Static Provisioning: Bind to existing PV
- Dynamic Provisioning: Create new PV using StorageClass
Bound
PVC status changes to Bound, ready for pod to use.
Using PVCs in Pods
PVCs are mounted into pods to provide persistent storage:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: nginx-pvc
Expanding PVCs
Some StorageClasses allow volume expansion:
Check StorageClass
Verify allowVolumeExpansion: true is set.
Edit PVC
Increase the spec.resources.requests.storage value.
Expansion Triggers
Kubernetes expands the underlying volume.
Wait for Completion
PVC status shows FileSystemResizePending until complete.
Verify New Size
Check pod sees expanded storage capacity.
- Volume expansion is one-way only (cannot shrink)
- Some volume types require pod restart to recognize new size
- Not all StorageClasses support expansion
StorageClasses
Templates for dynamic PersistentVolume provisioning with cloud provider-specific parameters.
Viewing StorageClasses
| Column | Description |
|---|---|
| Name | StorageClass name |
| Provisioner | Volume plugin (ebs.csi.aws.com, pd.csi.storage.gke.io, etc.) |
| Reclaim Policy | Delete or Retain |
| Volume Binding Mode | Immediate or WaitForFirstConsumer |
| Allow Expansion | Whether volumes can be resized |
| Default | Is this the default StorageClass |
Common StorageClasses by Provider
AWS EKS
| Name | Provisioner | Type | IOPS | Use Case |
|---|---|---|---|---|
| gp3 | ebs.csi.aws.com | General Purpose SSD | 3000-16000 | Default, balanced performance |
| gp2 | ebs.csi.aws.com | General Purpose SSD | Burstable | Legacy default |
| io2 | ebs.csi.aws.com | Provisioned IOPS SSD | 100-64000 | High performance databases |
| st1 | ebs.csi.aws.com | Throughput Optimized HDD | - | Big data, log processing |
Google GKE
| Name | Provisioner | Type | Use Case |
|---|---|---|---|
| standard | pd.csi.storage.gke.io | Standard PD | Default, cost-effective |
| balanced | pd.csi.storage.gke.io | Balanced PD | General purpose workloads |
| ssd | pd.csi.storage.gke.io | SSD PD | High performance |
| extreme | pd.csi.storage.gke.io | Extreme PD | Highest performance |
Azure AKS
| Name | Provisioner | Type | Use Case |
|---|---|---|---|
| default | disk.csi.azure.com | Standard HDD | Low cost, infrequent access |
| managed-premium | disk.csi.azure.com | Premium SSD | Production workloads |
| managed-csi-premium | disk.csi.azure.com | Premium SSD | High performance |
| azurefile | file.csi.azure.com | Azure Files | ReadWriteMany support |
Volume Binding Modes
| Mode | Behavior | Use When |
|---|---|---|
| Immediate | PV provisioned immediately when PVC created | Single-zone clusters, default behavior |
| WaitForFirstConsumer | PV provisioned when pod using PVC is scheduled | Multi-zone clusters, ensures volume in same zone as pod |
StorageClass Parameters
AWS EBS Example:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "5000"
throughput: "250"
encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
Google PD Example:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Default StorageClass
Mark a StorageClass as default for PVCs without explicit class:
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
Behavior:
- PVCs without
storageClassNameuse default class - Only one default class should exist per cluster
- Helps simplify PVC creation for users
ResourceQuotas
Limit storage consumption per namespace to prevent resource exhaustion.
Viewing ResourceQuotas
| Column | Description |
|---|---|
| Name | Quota name |
| Namespace | Kubernetes namespace |
| Resource | What is limited (storage, PVCs, etc.) |
| Used | Current consumption |
| Hard | Maximum allowed |
Storage Quota Types
| Quota Type | Description |
|---|---|
| requests.storage | Total storage requested across all PVCs |
| persistentvolumeclaims | Maximum number of PVCs allowed |
<storageclass>.storageclass.storage.k8s.io/requests.storage | Storage limit for specific StorageClass |
<storageclass>.storageclass.storage.k8s.io/persistentvolumeclaims | PVC count limit for specific StorageClass |
Example ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage-quota
namespace: development
spec:
hard:
requests.storage: "500Gi"
persistentvolumeclaims: "20"
gp3.storageclass.storage.k8s.io/requests.storage: "300Gi"
gp3.storageclass.storage.k8s.io/persistentvolumeclaims: "15"
Enforcement:
Used / Hard
─────────────────────────────────────
Storage (All): 250Gi / 500Gi
PVC Count: 12 / 20
Storage (gp3): 180Gi / 300Gi
PVC Count (gp3): 10 / 15
Quota Exceeded Behavior
When quota is exceeded, PVC creation fails:
Error creating PVC:
Exceeded quota: storage-quota
Requested: requests.storage=100Gi
Used: 450Gi
Hard: 500Gi
Resolution:
- Request quota increase
- Delete unused PVCs
- Use different StorageClass with available quota
- Archive data to cheaper storage
Example: StatefulSet with Persistent Storage
Scenario
Deploy PostgreSQL database with persistent storage using StatefulSet and PVC.
StatefulSet Configuration
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql
namespace: production
spec:
serviceName: postgresql
replicas: 3
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:15
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "gp3"
resources:
requests:
storage: 100Gi
Created Resources
PVCs Created:
NAME STATUS VOLUME CAPACITY STORAGE CLASS
data-postgresql-0 Bound pvc-abc123 100Gi gp3
data-postgresql-1 Bound pvc-def456 100Gi gp3
data-postgresql-2 Bound pvc-ghi789 100Gi gp3
PVs Auto-Provisioned:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
pvc-abc123 100Gi RWO Delete Bound production/data-postgresql-0
pvc-def456 100Gi RWO Delete Bound production/data-postgresql-1
pvc-ghi789 100Gi RWO Delete Bound production/data-postgresql-2
Scaling Behavior
Scale to 5 replicas:
kubectl scale statefulset postgresql --replicas=5
New PVCs created automatically:
data-postgresql-3 Pending - - gp3
data-postgresql-4 Pending - - gp3
After provisioning:
data-postgresql-3 Bound pvc-jkl012 100Gi gp3
data-postgresql-4 Bound pvc-mno345 100Gi gp3
Total Storage Used: 5 × 100Gi = 500Gi
Troubleshooting Storage Issues
- No PV matches requested capacity/access mode
- StorageClass not found or misconfigured
- ResourceQuota exceeded
- Volume provisioning failed (check events)
Solution: Check PVC events with kubectl describe pvc [name]. Verify StorageClass exists and provisioner is healthy.
- Volume already attached to different node
- Volume doesn't exist in cloud provider
- Node doesn't have permissions to attach volume
- Volume zone doesn't match node zone
Solution: Check pod events. Verify PV exists in cloud console. Ensure node and volume are in same availability zone.
- PVC is still in use by a pod (protection finalizer)
- PVC has deletion finalizer preventing removal
Solution: Delete or scale down pods using the PVC first. Check for finalizers with kubectl get pvc [name] -o yaml. Never force-remove finalizers without understanding implications.
- StorageClass doesn't allow expansion
- Requested size exceeds cloud provider limits
- Volume type doesn't support online resizing
Solution: Verify allowVolumeExpansion: true in StorageClass. Some volumes require pod restart after expansion. Check cloud provider volume limits.