Introduction
As an SRE working with Kubernetes, you'll occasionally need to rename a PersistentVolumeClaim (PVC) without losing the PersistentVolume (PV) and the data behind it. It comes up when you rename a StatefulSet, or just rename a Helm release for cosmetic reasons, like changing myservice to my-service. Because the PVC name is derived from that name in both cases, a simple redeploy provisions a new PVC, and with it a new, empty PV. If your old PV's persistentVolumeReclaimPolicy is Retain, your data stays unreferenced on the old PV (if it's Delete, the volume is gone the moment you delete the PVC).
To keep the data, your first instinct might be a migration: taking a VolumeSnapshot, running rsync between two mounts, or copying the data out to your local machine and back into the PV. But, in this very specific case, there's an easier way: a PV rebind. Instead of moving any data, you keep the PV as is and just re-point it at the new PVC.
Worth getting the vocabulary straight: this is a rebind, not a migration. Not a single byte moves. For block storage the volume ID stays the same; for NFS the same access point remains.
Table of Contents
- Important Notes
- Step 1: Set the persistentVolumeReclaimPolicy to Retain
- Step 2: Scale Down the StatefulSet / Deployment
- Step 3: Delete the PVC
- Step 4: Modify the Existing PV
- Step 5: Apply the New PVC
- Note on block-storage based CSIs on Managed Kubernetes
- Automated Tooling
Important Notes
For dynamic PV provisioning: don't create the new PVC (or a StatefulSet with
volumeClaimTemplates) until after you've re-pointed the existing PV in Step 4, or the provisioner races in and hands you a new, empty PV. If you already created the news resources, delete them along with the associated PVs and PVCs.For static PV provisioning: well, just don't create a new PV. The whole point is to reuse the existing one.
If you're on ArgoCD, turn off auto-sync or it will keep reverting the changes you're making.
Step 1: Set the persistentVolumeReclaimPolicy to Retain
A PV provisioned by a StorageClass (SC) inherits that SC's reclaimPolicy at creation time. So if the SC's reclaimPolicy is Delete, the PV's persistentVolumeReclaimPolicy will be Delete too, and the moment you delete the PVC the underlying volume and your data are gone.
An SC's reclaimPolicy is immutable: even if you delete and recreate the SC, the change only applies to PVs provisioned afterward. The PV's own persistentVolumeReclaimPolicy, however, is mutable, which is exactly what we are going to change.
First, check the reclaim policy of your existing PV:
kubectl get pv <your-pv-name> -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'
If it prints Retain, you're safe to move on. If it prints Delete, change it:
kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
# OR edit the same field interactively:
kubectl edit pv <your-pv-name>
Step 2: Scale Down the StatefulSet / Deployment
You can't delete a PVC while a pod is still using it: the pvc-protection finalizer blocks it. Scale the workload down first.
For Deployments:
kubectl scale deployment <deployment-name> --replicas=0 -n <your-namespace>
For StatefulSets:
kubectl scale statefulset <statefulset-name> --replicas=0 -n <your-namespace>
Step 3: Delete the PVC
Delete your existing PVC:
kubectl delete pvc <your-pvc-name> -n <your-namespace>
With Retain set in Step 1, the PV state changes to Released, keeping your data instead of reclaiming it.
Step 4: Modify the Existing PV
Delete two fields under spec.claimRef on the existing PV:
resourceVersion-
uid
kubectl patch pv <your-pv-name> --type='merge' -p '{"spec":{"claimRef":{"resourceVersion":null,"uid":null}}}'
The uid binds the PV to the old PVC's specific instance; clearing it and the resourceVersion flips the PV from Released to Available and thus free to bind a new claim. You then point claimRef at the new PVC by changing the name (and namespace, if it moved):
kubectl patch pv <your-pv-name> --type='merge' -p '{"spec":{"claimRef":{"name":"new-claim-name","namespace":"new-namespace-name"}}}'
You can also do this interactively with kubectl edit pv <your-pv-name>, but becareful only touch the fields under claimRef. If you delete the PV's top-level metadata.uid, which is what I accidentally did, the kubectl edit will throw error: no original object found.
Step 5: Apply the New PVC
Now create the new PVC using kubectl, Helm, or whatever you use. Give it the same storageClassName and accessModes as the PV, a size no larger than the PV's capacity, and leave volumeName unset. Because the PV is already reserved for this exact name, the binder grabs it instead of provisioning a fresh one.
Note on Personal Experience: if your new PVC requests a larger size, the rebinding won't work and a new PV will provisioned by your SC. Smaller size will work, but you still get whatever the old PV size is which is misleading; so keep the manifest exactly the same as before.
Note on block-storage based CSIs on Managed Kubernetes
Block storage on managed Kubernetes, such as EKS with the EBS CSI driver, is usually AZ-bound. If the PV lives in availability-zone-a, you can't attach it to a pod scheduled in availability-zone-b. There you'd have to take the full migration path, which we won't cover today.
Automated Tooling
This process can also be automated using tools such as rename-pvc from STACKIT. It follows the same PV rebind procedure.
However, you still need to remember to scale down the workload and pause any GitOps reconciliation (e.g. ArgoCD auto-sync) during the operation.
Top comments (0)