PersistentVolumes left Released with their disk still billing
What does ZopNight detect here?
A PersistentVolume in Released state has lost its claim but kept its underlying cloud disk, which keeps billing at the full provisioned rate. With the default Retain policy nothing reclaims it, and a Released PV cannot rebind even to a matching claim without hand-clearing 1 field, spec.claimRef.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1741 · RC-1841 · RC-1941 |
| Category | orphan |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | internal/rules/k8s/pv_released.go |
Where it applies
Released is not deleted
When a PVC is deleted, its bound PV moves to Released. What happens next depends entirely on
the reclaim policy:
- Retain: the PV stays, the cloud disk stays, and both keep existing until a human intervenes.
- Delete: the PV and the underlying disk are removed automatically.
Statically-provisioned volumes commonly use Retain, and it is often the correct choice: it means deleting a claim cannot destroy data. The consequence is that the disk keeps billing, forever, at its full provisioned size.
Why these accumulate invisibly
A Released PV is not attached to anything, so it produces no events, no pod failures and no metrics. Nothing in the cluster complains. The only visible trace is on the cloud bill, as an unattached disk that no Kubernetes-facing dashboard shows.
Namespace deletion is the usual multiplier: tearing down an environment removes every PVC in it and leaves a matching set of Released PVs and live disks behind.
A Released volume cannot be reused as-is
This is the part that catches people. A PV in Released state will not bind to a new claim even if the claim matches perfectly; it retains a reference to the deleted claim’s UID.
Reusing it means clearing spec.claimRef manually, at which point you are hand-editing cluster
state. Deleting and reprovisioning is usually cleaner unless the data matters.
Grepping the PV list for Released status
kubectl get pv --sort-by=.spec.capacity.storage \ -o custom-columns='NAME:.metadata.name,CAP:.spec.capacity.storage,STATUS:.status.phase,POLICY:.spec.persistentVolumeReclaimPolicy,CLAIM:.spec.claimRef.name' \ | grep ReleasedThen confirm the disk still exists on the cloud side. The PV object and the disk are separate things, and one can outlive the other.
Before deleting
Snapshot first if there is any doubt. A Retain policy exists because somebody decided this data should survive claim deletion, and that decision may still hold even if the workload is gone.