Overview
Persistent Volumes (PV) provide durable storage for Kubernetes applications. Unlike ephemeral container storage, persistent volumes retain data across pod restarts, rescheduling, and failures.
Storage Architecture
Kubernetes storage has three key components:
PersistentVolume (PV) : Cluster-level storage resource
PersistentVolumeClaim (PVC) : User’s request for storage
StorageClass : Dynamic provisioner for PVs
┌─────────────┐ requests ┌──────────────┐ provisions ┌────────────┐
│ Pod │ ──────────────> │ PVC │ ──────────────> │ PV │
└─────────────┘ └──────────────┘ └────────────┘
│
│ uses
▼
┌──────────────┐
│ StorageClass │
└──────────────┘
PersistentVolumeClaim Examples
PostgreSQL Database Storage
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : postgres-pvc
spec :
# Access mode defines how the volume can be mounted
accessModes :
- ReadWriteOnce # Can be mounted read-write by a single node
# Storage capacity request
resources :
requests :
storage : 5Gi
# Storage class to use for dynamic provisioning
storageClassName : standard-rwo
# Volume should be created as a filesystem
volumeMode : Filesystem
Redis Cache Storage
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : redis-pvc
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 5Gi
storageClassName : standard-rwo
volumeMode : Filesystem
Storage Classes
GKE Storage Classes
GKE provides several pre-configured storage classes:
standard-rwo (Balanced PD)
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : standard-rwo
provisioner : pd.csi.storage.gke.io
parameters :
type : pd-balanced # Balanced performance and cost
replication-type : regional-pd # Replicated across zones
volumeBindingMode : WaitForFirstConsumer # Create PV when pod is scheduled
allowVolumeExpansion : true # Allow resizing
Use cases : General purpose databases (PostgreSQL, MySQL, MongoDB), application data
premium-rwo (SSD PD)
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : premium-rwo
provisioner : pd.csi.storage.gke.io
parameters :
type : pd-ssd # High-performance SSD
replication-type : regional-pd
volumeBindingMode : WaitForFirstConsumer
allowVolumeExpansion : true
Use cases : High-performance databases, latency-sensitive applications
standard (HDD PD)
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : standard
provisioner : pd.csi.storage.gke.io
parameters :
type : pd-standard # Cost-effective HDD
replication-type : none
volumeBindingMode : WaitForFirstConsumer
allowVolumeExpansion : true
Use cases : Backups, logs, archival data
Viewing Storage Classes
# List available storage classes
kubectl get storageclass
# Describe a storage class
kubectl describe storageclass standard-rwo
# Get default storage class
kubectl get storageclass -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}'
Access Modes
Mode Description Use Case ReadWriteOnce (RWO) Volume can be mounted read-write by a single node Databases, stateful apps (most common) ReadOnlyMany (ROX) Volume can be mounted read-only by many nodes Static content, configuration ReadWriteMany (RWX) Volume can be mounted read-write by many nodes Shared storage, multi-pod writes
GKE Persistent Disks support only ReadWriteOnce . For ReadWriteMany, use Filestore or other network storage solutions.
Using PVCs in Deployments
PostgreSQL Deployment with PVC
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : postgres-pvc
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 5Gi
storageClassName : standard-rwo
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : postgres
spec :
replicas : 1 # Must be 1 for ReadWriteOnce volumes
selector :
matchLabels :
app : postgres
template :
metadata :
labels :
app : postgres
spec :
containers :
- name : postgres
image : postgres:16
env :
- name : POSTGRES_PASSWORD
valueFrom :
secretKeyRef :
name : postgres-secret
key : password
- name : PGDATA
value : /var/lib/postgresql/data/pgdata
ports :
- containerPort : 5432
# Mount the PVC
volumeMounts :
- name : postgres-storage
mountPath : /var/lib/postgresql/data
# Define the volume from PVC
volumes :
- name : postgres-storage
persistentVolumeClaim :
claimName : postgres-pvc
Redis Deployment with PVC
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : redis-pvc
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 5Gi
storageClassName : standard-rwo
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : redis
spec :
replicas : 1
selector :
matchLabels :
app : redis
template :
metadata :
labels :
app : redis
spec :
containers :
- name : redis
image : redis:7-alpine
command :
- redis-server
- --appendonly yes
- --dir /data
ports :
- containerPort : 6379
volumeMounts :
- name : redis-storage
mountPath : /data
volumes :
- name : redis-storage
persistentVolumeClaim :
claimName : redis-pvc
StatefulSets with Persistent Storage
For applications requiring stable storage and network identities, use StatefulSets with volumeClaimTemplates:
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : postgres
spec :
serviceName : postgres
replicas : 3
selector :
matchLabels :
app : postgres
template :
metadata :
labels :
app : postgres
spec :
containers :
- name : postgres
image : postgres:16
ports :
- containerPort : 5432
volumeMounts :
- name : postgres-storage
mountPath : /var/lib/postgresql/data
# Automatic PVC creation per replica
volumeClaimTemplates :
- metadata :
name : postgres-storage
spec :
accessModes :
- ReadWriteOnce
storageClassName : standard-rwo
resources :
requests :
storage : 10Gi
This creates:
postgres-storage-postgres-0
postgres-storage-postgres-1
postgres-storage-postgres-2
Each StatefulSet pod gets its own dedicated PVC, ensuring data persistence and stability.
Storage Management
Listing PVCs and PVs
# List PVCs in current namespace
kubectl get pvc
# List PVCs in all namespaces
kubectl get pvc --all-namespaces
# List PVs (cluster-wide)
kubectl get pv
# Describe PVC for details
kubectl describe pvc postgres-pvc
Resizing Volumes
If the storage class allows expansion (allowVolumeExpansion: true):
# Edit the PVC
kubectl edit pvc postgres-pvc
# Update the storage size
spec:
resources:
requests:
storage: 10Gi # Increased from 5Gi
For filesystem resizing:
# Delete and recreate the pod to trigger filesystem resize
kubectl delete pod < pod-nam e >
# Or rollout restart the deployment
kubectl rollout restart deployment postgres
You can only increase volume size, not decrease. Plan capacity accordingly.
Deleting PVCs
# Delete PVC (be careful - this deletes data!)
kubectl delete pvc postgres-pvc
# Check PV reclaim policy before deleting
kubectl get pv -o jsonpath='{.items[*].spec.persistentVolumeReclaimPolicy}'
Reclaim Policies
Controls what happens to the PV when its PVC is deleted:
Retain
PV is kept, manual cleanup required:
persistentVolumeReclaimPolicy : Retain
Delete (Default for Dynamic Provisioning)
PV and underlying storage are automatically deleted:
persistentVolumeReclaimPolicy : Delete
Recycle (Deprecated)
Basic scrub (rm -rf /volume/*) - use Retain or Delete instead.
Backup Strategies
Volume Snapshots
Create point-in-time snapshots of persistent volumes:
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshot
metadata :
name : postgres-snapshot
spec :
volumeSnapshotClassName : pd-snapshot-class
source :
persistentVolumeClaimName : postgres-pvc
# Create snapshot
kubectl apply -f snapshot.yml
# List snapshots
kubectl get volumesnapshot
# Restore from snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc-restored
spec:
dataSource:
name: postgres-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard-rwo
Application-Level Backups
For databases, use native backup tools:
# PostgreSQL backup
kubectl exec postgres-pod -- pg_dump -U postgres dbname > backup.sql
# Redis backup
kubectl exec redis-pod -- redis-cli --rdb /data/dump.rdb
Best Practices
Right-Size Storage Start with conservative estimates and use allowVolumeExpansion to grow as needed
Use Regional Storage Enable replication-type: regional-pd for high availability across zones
Monitor Usage Set up alerts for storage utilization to prevent out-of-space issues
Regular Backups Implement automated backup strategies using snapshots or application-native tools
Storage Class Selection Use standard-rwo for general workloads, premium-rwo for performance-critical apps
StatefulSets for Stateful Apps Use StatefulSets with volumeClaimTemplates for databases and clustered applications
IOPS and Throughput
GKE persistent disk performance scales with size:
Disk Type IOPS (Read/Write) Throughput pd-standard 0.75 per GB 120-180 MB/s per TB pd-balanced 6 per GB 240 MB/s per GB pd-ssd 30 per GB 480 MB/s per GB
If you need higher IOPS, increase volume size or use pd-ssd storage class.
Troubleshooting
PVC Stuck in Pending
# Check PVC status
kubectl describe pvc postgres-pvc
# Common causes:
# - No storage class available
# - Insufficient quota
# - Zone mismatch (WaitForFirstConsumer)
# Check events
kubectl get events --field-selector involvedObject.name=postgres-pvc
Pod Cannot Mount Volume
# Check pod events
kubectl describe pod < pod-nam e >
# Verify PVC is bound
kubectl get pvc
# Check if another pod is using the volume (ReadWriteOnce)
kubectl get pods -o json | jq '.items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName=="postgres-pvc") | .metadata.name'
Storage Full
# Check disk usage in pod
kubectl exec < pod-nam e > -- df -h
# Resize PVC (if allowed)
kubectl edit pvc postgres-pvc
# Clean up old data
kubectl exec < pod-nam e > -- rm -rf /data/old-data