Skip to content

Persistent Storage in Kubernetes: PVs, PVCs & StorageClasses

DodaTech 5 min read

In this tutorial, you'll learn about Persistent Storage in Kubernetes: PVs, PVCs & StorageClasses. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Kubernetes persistent storage decouples storage from pod lifecycle using PersistentVolumes and PersistentVolumeClaims, enabling stateful applications to retain data across restarts, rescheduling, and node failures.

What You'll Learn

This tutorial covers static and dynamic volume provisioning, PersistentVolumeClaims, StorageClasses for cloud volumes, access modes and reclaim policies, StatefulSet storage patterns, and backup strategies for persistent data.

Why It Matters

Without persistent storage, databases, Message Queues, and file stores lose all data when pods restart. Understanding Kubernetes storage is essential for running production stateful workloads -- over 40 percent of production Kubernetes clusters now run stateful applications.

Real-World Use

GitLab uses PersistentVolumeClaims with Cloud Computing StorageClasses to provide persistent storage for Git repositories across all their SaaS customers. MongoDB operators use StatefulSets with persistent storage to handle automatic failover and data Replication across availability zones.

graph TD
  A[Pod] --> B[PVC: PersistentVolumeClaim]
  B --> C[PV: PersistentVolume]
  C --> D[StorageClass]
  D --> E[Cloud Storage: EBS / GCE PD / Azure Disk]
  C --> F[Reclaim Policy: Delete / Retain / Recycle]
  B --> G[Access Mode: RWO / RWX / ROX]

Expected output: diagram showing the storage chain from Pod to PVC to PV to StorageClass to actual cloud storage, with reclaim policies and access modes highlighted.

PersistentVolumes and PersistentVolumeClaims

A PV is cluster storage provisioned by an administrator. A PVC is a request for storage by a user.

# PersistentVolume -- static provisioning
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-storage-1
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /export/data
    server: nfs-server.internal
---
# PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-claim
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: ""
# List PersistentVolumes
kubectl get pv

# List PersistentVolumeClaims
kubectl get pvc

# Check PVC binding status
kubectl describe pvc data-claim

Expected output: PVs show their capacity and status (Available or Bound). The PVC shows Bound status with the matched PV name, indicating the volume is ready for pod consumption.

StorageClasses and Dynamic Provisioning

StorageClasses define different tiers of storage and enable automatic volume creation.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
reclaimPolicy: Delete
# List available StorageClasses
kubectl get storageclass

# Set default StorageClass
kubectl annotate storageclass fast-ssd storageclass.kubernetes.io/is-default-class="true"

Expected output: storageclasses list shows provisioner, reclaim policy, and volume binding mode for each class.

Deploying Stateful Applications with Persistent Storage

A database pod using persistent storage through a PVC.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: postgres-data
        persistentVolumeClaim:
          claimName: postgres-pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: fast-ssd
# Deploy PostgreSQL with persistent storage
kubectl apply -f postgres-pvc.yaml
kubectl apply -f postgres-deployment.yaml

# Verify data persists across pod restarts
kubectl exec postgres-xxx -- psql -U postgres -c "CREATE TABLE test (id int);"
kubectl delete pod postgres-xxx
# Wait for new pod
kubectl exec postgres-yyy -- psql -U postgres -c "\dt"

Expected output: the new pod has the same data, proving persistence. The PVC remains Bound to the PV even after the pod is recreated.

StatefulSets and Stable Storage

StatefulSets provide stable network identities and ordered storage for each replica.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-headless
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: bitnami/kafka:3.6
        ports:
        - containerPort: 9092
        volumeMounts:
        - name: data
          mountPath: /bitnami/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 50Gi
      storageClassName: fast-ssd
# Deploy the StatefulSet
kubectl apply -f kafka-statefulset.yaml

# Verify each pod gets its own PVC
kubectl get pvc
# Expected: data-kafka-0, data-kafka-1, data-kafka-2

# Check pod startup order
kubectl logs kafka-0

Expected output: three PVCs are created -- data-kafka-0, data-kafka-1, data-kafka-2 -- each bound to its own PV. Pods start and stop in ordinal order.

Practice Questions

  1. What is the difference between ReadWriteOnce and ReadWriteMany access modes? ReadWriteOnce allows a single node to mount the volume as read-write. ReadWriteMany allows multiple nodes and pods to mount simultaneously, typically used with NFS or CephFS.

  2. What happens to the PV when a PVC is deleted? The behavior depends on the reclaim policy -- Delete deletes the underlying storage, Retain keeps it for manual recovery, and Recycle scrubs it for reuse.

  3. How does dynamic provisioning differ from static provisioning? Dynamic provisioning creates a PV automatically when a PVC requests a StorageClass. Static provisioning requires an administrator to pre-create PVs that PVCs can bind to.

Frequently Asked Questions

Can I expand a PersistentVolume after creation?

Yes, if the StorageClass has allowVolumeExpansion: true. Edit the PVC to increase the storage request: kubectl edit pvc my-claim and change the storage size. The volume controller expands the underlying volume automatically. Supported by most cloud CSI drivers including AWS EBS, GCP PD, and Azure Disk.

What happens to data when a StatefulSet is scaled down?

The PVC and PV are not deleted when a StatefulSet pod is removed. They remain in the cluster with the data intact. If you scale up again, the new pod with the same ordinal reuses the existing PVC. You must manually delete PVCs if you intend to discard the data permanently.

How do I back up persistent volumes?

Use Velero to back up PVC data along with cluster resources. For CSI volumes, use volume snapshots with the CSI Snapshotter controller. For databases, use application-level backup tools (pg_dump, mysqldump) that export logical backups, then store those backups in object storage outside the cluster.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro