TechLead
Lesson 9 of 25
5 min read
Cloud & Kubernetes

Persistent Volumes

Understand Kubernetes storage with PersistentVolumes, PersistentVolumeClaims, StorageClasses, and StatefulSets for data persistence

Storage in Kubernetes

Containers are ephemeral by nature — when a Pod is deleted or restarted, any data stored inside the container is lost. Kubernetes provides a storage abstraction layer that allows data to persist beyond the lifecycle of individual Pods through Volumes, PersistentVolumes (PV), and PersistentVolumeClaims (PVC).

Storage Concepts

  • Volume: A directory accessible to containers in a Pod. Its lifetime is tied to the Pod.
  • PersistentVolume (PV): A piece of storage provisioned by an administrator or dynamically provisioned. Exists independently of any Pod.
  • PersistentVolumeClaim (PVC): A request for storage by a user. Pods use PVCs to consume PV resources.
  • StorageClass: Defines the "class" of storage (SSD, HDD, NFS) and how volumes are dynamically provisioned.

PersistentVolume and PersistentVolumeClaim

# persistent-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
  labels:
    type: local
    app: postgres
spec:
  storageClassName: standard
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce    # Can be mounted by a single node
  persistentVolumeReclaimPolicy: Retain  # Keep data after PVC is deleted
  hostPath:
    path: /data/postgres  # Local path (for development only)
---
# persistent-volume-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: production
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  selector:
    matchLabels:
      app: postgres

Access Modes

Mode Abbreviation Description
ReadWriteOnceRWOVolume can be mounted as read-write by a single node
ReadOnlyManyROXVolume can be mounted as read-only by many nodes
ReadWriteManyRWXVolume can be mounted as read-write by many nodes
ReadWriteOncePodRWOPVolume can be mounted as read-write by a single Pod

StorageClasses

# storage-classes.yaml
# Fast SSD storage class (AWS)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
# Standard storage class (GCP)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard-gcp
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-standard
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

Using PVCs in Pods

# postgres-with-pvc.yaml
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
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: postgres-credentials
              key: username
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-credentials
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-pvc

StatefulSets

For stateful applications (databases, message queues), use StatefulSets instead of Deployments. StatefulSets provide stable network identities, stable persistent storage, and ordered deployment and scaling.

# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 20Gi
# Manage PVs and PVCs
kubectl get pv
kubectl get pvc -n production
kubectl describe pvc postgres-pvc -n production

# Check StorageClasses
kubectl get storageclass

# Expand a PVC (if StorageClass allows)
kubectl patch pvc postgres-pvc -n production -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

# Check StatefulSet pods (they get stable names: postgres-0, postgres-1, postgres-2)
kubectl get pods -l app=postgres

Key Takeaways

  • PersistentVolumes provide storage that outlives individual Pods
  • PersistentVolumeClaims are requests for storage that bind to available PVs
  • StorageClasses enable dynamic provisioning — no need to pre-create PVs
  • StatefulSets provide stable identities and per-replica storage for stateful apps
  • Always set reclaimPolicy to Retain for production databases to prevent data loss

Continue Learning