$ run as a normal user# run as root (or with sudo)
Skills in this chapter
- StorageClasses, dynamic provisioning, PVs and PVCs
Know the storage objects
The app asks with a PVC; Kubernetes binds it to a matching PV (existing or newly created).
| Object | Who makes it | Meaning |
|---|---|---|
| StorageClass | Admin | A type of storage + its provisioner |
| PersistentVolume | Admin or provisioner | A piece of real storage |
| PersistentVolumeClaim | App team | "I need 10 Gi, ReadWriteOnce" |
| Volume in the pod | App team | Mounts the PVC into a container |
Know access modes and reclaim policies
Access modes say how many nodes may mount the volume; the reclaim policy says what happens after the PVC is deleted.
| Access mode | Short | Meaning |
|---|---|---|
| ReadWriteOnce | RWO | Read-write by one node |
| ReadOnlyMany | ROX | Read-only by many nodes |
| ReadWriteMany | RWX | Read-write by many nodes (NFS, CephFS, Azure Files) |
| ReadWriteOncePod | RWOP | Read-write by one pod only |
| Reclaim policy | After the PVC is deleted |
|---|---|
| Delete | The PV and the real disk are deleted (default for dynamic) |
| Retain | The PV stays (status Released); an admin cleans up by hand |
Use dynamic provisioning
With a StorageClass, a PVC alone is enough — the provisioner creates the PV.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
annotations: { storageclass.kubernetes.io/is-default-class: "true" }
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: db-data, namespace: shop }
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast
resources:
requests: { storage: 10Gi }kubectl get storageclass
kubectl get pvc,pv -n shop
kubectl patch pvc db-data -n shop -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'Remember
WaitForFirstConsumer creates the volume only when a pod uses it — in the zone of that pod's node. The PVC stays Pending until then; that is normal.
Create a PV by hand
Without a provisioner, an admin creates PVs; a PVC with the same class, size and mode binds to one.
apiVersion: v1
kind: PersistentVolume
metadata: { name: pv-logs }
spec:
capacity: { storage: 5Gi }
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: { path: /mnt/data/logs }Mount the PVC in a pod
The pod refers to the claim, the container mounts the volume.
apiVersion: v1
kind: Pod
metadata: { name: db, namespace: shop }
spec:
containers:
- name: postgres
image: postgres:16
env: [{ name: POSTGRES_PASSWORD, value: change-me }]
volumeMounts:
- { name: data, mountPath: /var/lib/postgresql/data }
volumes:
- name: data
persistentVolumeClaim: { claimName: db-data }Use other volume types
Not every volume is persistent.
| Volume | Lives as long as | Use |
|---|---|---|
emptyDir | The pod | Scratch space, shared between containers of a pod |
hostPath | The node | Node agents; avoid for apps (pins the pod to a node) |
configMap, secret | The object | Config files |
persistentVolumeClaim | The PVC | App data |