PVs & PVCs

PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs) are the core Kubernetes resources for managing persistent storage. PVs represent actual storage resources in the cluster, while PVCs are requests for storage by users or applications. This separation allows developers to request storage without knowing implementation details, while administrators maintain control over storage resources.

The PV/PVC Model

The PersistentVolume/PersistentVolumeClaim model separates what storage is needed (declared in a PVC) from how that storage is provided (implemented in a PV). This abstraction makes storage management more flexible and user-friendly.

graph TB A[Developer] --> B[Creates PVC<br/>Requests storage] B --> C[Kubernetes] C --> D[Matches PVC to PV] D --> E[Admin or StorageClass<br/>Provides PV] E --> F[PV Bound to PVC] F --> G[Pod Uses PVC] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#e8f5e9 style E fill:#f3e5f5 style F fill:#e8f5e9 style G fill:#ffe1e1

PersistentVolume (PV)

A PersistentVolume is a cluster resource that represents a piece of storage that has been provisioned. It’s a cluster-wide resource, not namespaced, and exists independently of any pod.

Key characteristics:

  • Cluster-scoped resource
  • Created by administrators or dynamically by StorageClasses
  • Contains storage details (size, access mode, storage type)
  • Can be bound to one PVC at a time

PersistentVolumeClaim (PVC)

A PersistentVolumeClaim is a request for storage by a user. It’s similar to how a pod requests CPU and memory—the pod declares its needs, and Kubernetes fulfills them.

Key characteristics:

  • Namespaced resource
  • Created by developers/users
  • Declares storage requirements (size, access mode, StorageClass)
  • Gets bound to a matching PV

The Binding Process

Here’s how a PVC gets bound to a PV:

sequenceDiagram participant Dev as Developer participant K8s as Kubernetes participant SC as StorageClass participant Storage as Storage System Dev->>K8s: Create PVC K8s->>K8s: Check for matching PV alt Static Provisioning K8s->>K8s: Find existing PV that matches K8s->>K8s: Bind PVC to PV else Dynamic Provisioning K8s->>SC: Get StorageClass details SC->>K8s: Return provisioner info K8s->>Storage: Create volume (via provisioner) Storage->>K8s: Volume created K8s->>K8s: Create PV for new volume K8s->>K8s: Bind PVC to PV end K8s->>Dev: PVC bound and ready

Static Provisioning

With static provisioning, administrators manually create PVs, and users create PVCs that bind to these pre-created PVs.

Example PV (manually created):

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-manual
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data

Example PVC (binds to the PV):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-manual
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: manual

Dynamic Provisioning

With dynamic provisioning, users create PVCs that reference a StorageClass, and Kubernetes automatically creates the PV.

Example PVC (triggers dynamic provisioning):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-dynamic
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd  # References StorageClass
  resources:
    requests:
      storage: 50Gi

Kubernetes automatically:

  1. Looks up the fast-ssd StorageClass
  2. Uses the provisioner to create storage
  3. Creates a PV for the new storage
  4. Binds the PVC to the PV

Using PVCs in Pods

Once a PVC is bound, you can use it in a pod:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-pvc
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: data
      mountPath: /var/www/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: pvc-dynamic  # Reference the PVC

Complete Example

Here’s a complete example showing dynamic provisioning:

# StorageClass (usually created by cluster admin)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
reclaimPolicy: Delete
---
# PVC (created by developer)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  resources:
    requests:
      storage: 10Gi
---
# Pod using the PVC
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: web-storage
      mountPath: /usr/share/nginx/html
  volumes:
  - name: web-storage
    persistentVolumeClaim:
      claimName: web-data

PV States

A PV goes through several states during its lifecycle:

graph LR A[Available] --> B[Bound] B --> C[Released] C --> D[Retained/Deleted] style A fill:#e8f5e9 style B fill:#fff4e1 style C fill:#ffe1e1 style D fill:#f3e5f5
  • Available - PV exists but is not bound to a PVC
  • Bound - PV is bound to a PVC
  • Released - PVC was deleted, but PV hasn’t been reclaimed yet
  • Failed - Volume failed to reclaim

PVC States

A PVC also has states:

  • Pending - PVC created but not yet bound to a PV
  • Bound - PVC is bound to a PV and ready to use
  • Lost - The bound PV was deleted (rare)

Best Practices

  1. Use dynamic provisioning - Prefer StorageClasses over static provisioning
  2. Set appropriate sizes - Request enough storage but not excessive amounts
  3. Match access modes to needs - Use ReadWriteOnce unless you need multi-pod access
  4. Understand reclaim policies - Know what happens to data when PVCs are deleted
  5. Use StatefulSets for ordered deployments - StatefulSets provide ordered, stable pod identity
  6. Monitor PVC status - Check if PVCs are bound before deploying pods
  7. Plan for expansion - Use StorageClasses with allowVolumeExpansion: true when possible

Common Patterns

Database with Persistent Storage

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi

Shared Content Storage

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-content
spec:
  accessModes:
    - ReadWriteMany  # Multiple pods can mount
  storageClassName: nfs-storage
  resources:
    requests:
      storage: 500Gi

Troubleshooting

PVC Stuck in Pending

If a PVC is stuck in Pending state:

  1. Check if StorageClass exists: kubectl get storageclass
  2. Verify provisioner is available
  3. Check for sufficient storage resources
  4. Review PVC events: kubectl describe pvc <name>

PV Stuck in Released

If a PV is stuck in Released state after PVC deletion:

  1. Check the reclaim policy (Retain vs Delete)
  2. For Retain policy, manually delete the PV to clean up
  3. Verify the underlying storage can be deleted (if using Delete policy)

Topics

  • Patterns - Common PV and PVC usage patterns

See Also