Manage Kubernetes storage resources including PersistentVolumes, PersistentVolumeClaims, StorageClasses, and ResourceQuotas for persistent data storage.
Cluster-wide storage resources that exist independently of pods and persist beyond pod lifecycle.
| 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 |
| 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 |
Click a PV to view:
ReadWriteOnce (RWO) - Single node read/writeReadOnlyMany (ROX) - Multiple nodes read-onlyReadWriteMany (RWX) - Multiple nodes read/write| 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) |
User requests for storage that bind to PersistentVolumes or trigger dynamic provisioning.
| 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 |
| 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 |
User creates PVC requesting specific capacity and access mode.
Kubernetes looks for available PV matching requirements.
PVC status changes to Bound, ready for pod to use.
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
Some StorageClasses allow volume expansion:
Verify allowVolumeExpansion: true is set.
Increase the spec.resources.requests.storage value.
Kubernetes expands the underlying volume.
PVC status shows FileSystemResizePending until complete.
Check pod sees expanded storage capacity.
Templates for dynamic PersistentVolume provisioning with cloud provider-specific parameters.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
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
Mark a StorageClass as default for PVCs without explicit class:
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
Behavior:
storageClassName use default classLimit storage consumption per namespace to prevent resource exhaustion.
| Column | Description |
|---|---|
| Name | Quota name |
| Namespace | Kubernetes namespace |
| Resource | What is limited (storage, PVCs, etc.) |
| Used | Current consumption |
| Hard | Maximum allowed |
| 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 |
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
When quota is exceeded, PVC creation fails:
Error creating PVC:
Exceeded quota: storage-quota
Requested: requests.storage=100Gi
Used: 450Gi
Hard: 500Gi
Resolution:
Deploy PostgreSQL database with persistent storage using StatefulSet and PVC.
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
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
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
Solution: Check PVC events with kubectl describe pvc [name]. Verify StorageClass exists and provisioner is healthy.
Solution: Check pod events. Verify PV exists in cloud console. Ensure node and volume are in same availability zone.
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.
Solution: Verify allowVolumeExpansion: true in StorageClass. Some volumes require pod restart after expansion. Check cloud provider volume limits.