Why this lesson can save your data
kubectl delete namespace shop takes a second to type. With the wrong settings it deletes every claim in the namespace, every volume behind them, and every disk behind those - the database included. This lesson is about what happens to storage when claims and pods go away, how to get a volume back, how to grow one, and how StatefulSets keep one disk per replica.
What you need to know already: PV, PVC, StorageClass, binding, claimRef and volumeName (16.39), access modes and zones (16.41), StatefulSets and ordinals (15.19), JSON patch (16.10), kubectl describe events (15.14).
What happens when the claim goes away
The reclaim policy decides. It is persistentVolumeReclaimPolicy on the PV, copied from the StorageClass's reclaimPolicy when the volume was provisioned:
Delete the PV AND the backing storage are deleted. The disk is gone.
Default for dynamically provisioned volumes.
Retain the PV stays, phase Released, data intact. Nobody can bind it until
an admin intervenes. Default for manually created PVs.
Recycle deprecated (rm -rf of the volume). Do not use.
Production data gets Retain. With Delete, kubectl delete namespace deletes every PVC in it, which deletes every PV, which deletes every disk. That is one command away from a very bad day. You can switch an existing PV:
k patch pv pvc-ca5c067a-... -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
But not a StorageClass: reclaimPolicy: Forbidden: updates to reclaimPolicy are forbidden. - make a new class.
Getting a Released volume back
$ k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
pvc-ca5c067a-48e0-43ad-bf16-2195742424e1 3Gi RWO Retain Released shop/d ceph-rbd
STATUS Released = its claim was deleted, the data is still there. The PV still carries claimRef to the dead claim, including that claim's uid (the unique ID every object gets). A new claim with the same name has a different uid and does not match.
So: remove the reference, the PV becomes Available, then bind a new claim to it by name. A JSON patch remove deletes one field:
k patch pv pvc-ca5c067a-... --type=json -p='[{"op":"remove","path":"/spec/claimRef"}]'
# or keep the name and drop only the uid:
k patch pv pvc-ca5c067a-... --type=json -p='[{"op":"remove","path":"/spec/claimRef/uid"}]'
kind: PersistentVolumeClaim
spec:
storageClassName: ceph-rbd # must match the PV's class
volumeName: pvc-ca5c067a-... # this exact PV
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 3Gi}}
Protection: why a PVC hangs in Terminating
A finalizer is a marker on an object that says "do not really delete this yet - someone must clean up first". Deleting such an object only marks it Terminating; it disappears once every finalizer has been removed by the controller that owns it.
Every PVC gets the finalizer kubernetes.io/pvc-protection, every PV kubernetes.io/pv-protection. Deleting a claim that a pod still uses (any pod not Succeeded/Failed - a crash-looping one counts) only marks it:
# old-data = a claim still mounted by a forgotten pod
k get pvc old-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
old-data Terminating pvc-7d1c... 1Gi RWO lab-disk
k describe pvc old-data | grep -E 'Finalizers|Used By'
Finalizers: [kubernetes.io/pvc-protection]
Used By: forgotten-debug
Used By names the pod. Delete that pod and the claim finishes deleting. Removing the finalizer by hand "works" and leaves a pod writing to a volume whose claim no longer exists - don't.
Expansion
To grow a claim, raise its requested size:
k patch pvc logs -p '{"spec":{"resources":{"requests":{"storage":"4Gi"}}}}'
Allowed only if the class has allowVolumeExpansion: true and the volume was dynamically provisioned:
Error from server (Forbidden): persistentvolumeclaims "logs" is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
The sequence, visible in the claim's events and conditions:
- The driver's resizer grows the backend volume (
ExternalExpanding,Resizing). - The filesystem on it must grow too (like
resize2fsafter growing a disk). The claim shows condition FileSystemResizePending until the kubelet does it on a node where the volume is mounted (FileSystemResizeSuccessful).
Most CSI drivers do it while the pod runs.
Shrinking is not possible: spec.resources.requests.storage: Forbidden: field can not be less than status.capacity. To make a volume smaller you copy the data to a new one.
StatefulSet claims
A StatefulSet (15.19) can create one claim per replica from a template, the volumeClaimTemplates:
kind: StatefulSet
spec:
volumeClaimTemplates:
- metadata: {name: data}
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 1Gi}}
The claims are named <template>-<statefulset>-<ordinal>: data-queue-0, data-queue-1... Pod queue-1 always gets data-queue-1, whichever node it lands on (within the volume's zone). The claims outlive the pods and, by default, the StatefulSet:
scale 3 -> 1 data-queue-1 and data-queue-2 stay (Bound, unused)
scale 1 -> 3 queue-1 and queue-2 come back with their old data
delete the sts all claims stay - reinstall and the data is there
persistentVolumeClaimRetentionPolicy (stable since 1.32) changes that:
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # delete the claims with the StatefulSet (Retain = default)
whenScaled: Retain # Delete = remove claims of scaled-away ordinals
It works by making the claims "owned" by the StatefulSet (or by the pod, for whenScaled) through ownerReferences, so deleting the owner deletes them. The claims still follow their PVs' reclaim policy - a Retain PV survives even a deleted claim.
Backups are not optional
None of this is a backup. Retain protects against a deleted object, not a corrupted database or a dead disk. Real backups are snapshots (point-in-time copies of a volume: VolumeSnapshot objects through the CSI driver, or the cloud's disk snapshots) plus copies outside the cluster (a backup tool such as Velero, or the database's own dump tools) - and a restore you have actually tested.
What you can now do:
- choose Delete or Retain, and change it on an existing PV
- bring a Released volume back under a new claim
- grow a claim, and find why a PVC is stuck Terminating
- predict what happens to StatefulSet claims on scale-down and delete