Moving a HelmRelease to another namespace leaves its PVC behind

A Longhorn volume showed up as detached and unused because Helm never deletes PVCs in the namespace a release moved away from. How to spot the orphan by comparing volumes with running pods, and why the reclaim policy decides how much a delete takes with it.

The Longhorn overview told me one volume was unused. Sixteen volumes, fifteen attached and healthy, and one sitting there detached with a robustness of “unknown”. Detached is not automatically wrong. A volume for a workload I’ve scaled to zero looks the same. But I hadn’t scaled anything to zero, so I went looking for who owned it.

Asking Longhorn who thinks it owns the volume

Longhorn records which PVC, namespace and pods a volume belongs to, so one command lists the lot:

kubectl -n longhorn-system get volumes.longhorn.io -o custom-columns=\
NAME:.metadata.name,STATE:.status.state,ROBUST:.status.robustness,\
PVC:.status.kubernetesStatus.pvcName,NS:.status.kubernetesStatus.namespace,\
WORKLOADS:.status.kubernetesStatus.workloadsStatus[*].podName

The detached one belonged to a PVC called traefik in the traefik namespace, and it named a Traefik pod as its workload. That pod didn’t exist. The workload list is what Longhorn saw the last time something used the volume, not a live view, so a pod name there proves nothing until you check that the pod is still around.

Two claims with the same name

Listing PVs next to their claims showed the real picture: two volumes, both claimed by a PVC named traefik, one in traefik and one in flux-system.

kubectl get pv -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,\
CLAIM:.spec.claimRef.name,RECLAIM:.spec.persistentVolumeReclaimPolicy
kubectl get pvc -A | grep traefik

The older one was 74 days old, the newer 27. The traefik namespace had nothing else in it, no deployment, no service, no pod. The only running Traefik lived in flux-system, and git log on its HelmRelease gave me the reason: I had moved the release there weeks earlier.

Why Helm didn’t clean up

Helm removes what it owns in the release’s own namespace. When I changed the HelmRelease’s namespace, Flux installed a new release in flux-system, and the chart’s persistence: block created a fresh PVC there. Nothing ever told the old namespace to let go of its claim. The PVC stayed bound, the PV stayed bound, and the Longhorn volume stayed allocated, quietly, for two months.

This isn’t specific to Traefik. Any chart that creates PVCs, whether through a persistence: block or a StatefulSet’s volume claim templates, leaves them behind when its release moves.

Deleting it, and what the reclaim policy means

The volume’s reclaim policy was Delete, which is the default for the plain Longhorn class. That made the cleanup a single command:

kubectl -n traefik delete pvc traefik

Deleting the PVC deleted the PV and the Longhorn volume with it, and it can’t be undone. I checked first that the newer volume was the one the running pod had mounted for 27 days, so nothing depended on the old one. Afterwards I confirmed that both the PV and the Longhorn volume object returned NotFound and that Traefik hadn’t noticed.

Volumes on a retaining class behave the other way round. Deleting their PVC leaves the PV in Released and the data on disk, which is what you want for databases and exactly what makes an orphan there more expensive to find.