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[*].podNameThe 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 traefikThe 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 traefikDeleting 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.