chart № pv-0001 admitted 03:14 UTC phase Terminating attending you, apparently

Kubernetes · OpenShift · CSI

pvdoctor

Your PersistentVolume is stuck.Don't remove the finalizer.

pvdoctor finds volumes stuck in Terminating, Released, or Failed, works out why, and prints the safe fix — next to the dangerous one you were about to paste from Stack Overflow, and what it would actually destroy.

$go install github.com/vikozs/pvdoctor/cmd/pvdoctor@latest
Source on GitHub Download a binary

The hazard

The finalizer is not the problem.

Search for a stuck PV and the first answer is always the same: patch out the finalizer. It works, in the sense that the object goes away.

It also strands the backing disk on your storage array, where it keeps consuming capacity that nothing in the cluster can account for. Do that a few times across a large estate and you have a slow, invisible leak nobody can trace — because the one record tying that disk to a workload was the object you just deleted.

The finalizer is a symptom of a backend condition nothing surfaces to you. pvdoctor finds that condition.

Diagnosis

$ pvdoctor scan8 findings
SeverityObjectRuleSummary
CRITICALpv/pvc-1a2b3c4d-vspherevsphere-orphaned-snapshotBacking FCD still has CNS snapshots
CRITICALpv/pvc-4d5e6f70-node-gonestale-claimrefclaimRef points at a deleted PVC
WARNINGpv/pvc-2b3c4d5e-detachedstale-claimrefclaimRef points at a deleted PVC
WARNINGpv/pvc-3c4d5e6f-kastenbackup-finalizerKasten K10 finalizer is blocking deletion
WARNINGpv/pvc-4d5e6f70-node-gonedangling-volumeattachmentAttachment left behind by a deleted node
WARNINGpv/pvc-5e6f7081-mysteryterminating-no-progressTerminating with no identified cause
INFOpv/pvc-1a2b3c4d-vspherepvc-protection-live-podDeletion correctly blocked by running consumers

Run pvdoctor explain <pv> for the full diagnosis — cause, evidence, ordered fix, and the hazard.

What explain shows you

DO NOT
kubectl patch pv <name> -p '{"metadata":{"finalizers":null}}'

That deletes the Kubernetes object while the backing disk still exists on the storage system. The PV becomes unrecoverable by CSI, the capacity stays allocated, and nothing in the cluster records the orphan.

For vSphere specifically: the stranded VMDK keeps consuming datastore capacity and no longer appears in the CNS container-volume view — that view is populated from Kubernetes metadata, which you have just removed. Finding it later means diffing datastore contents against live volumes by hand.

Resolve the backend condition and the finalizer clears itself. pvdoctor prints the commands; you read them, understand them, and run them. There is no --fix flag, and a unit test fails the build if any rule ever suggests removing a finalizer.

The First Commandment of Storage Thou shalt not strike out the finalizer to hasten thine evening. For the object shall vanish, and the disk shall remain; and it shall drink of thy datastore all its days, and no man shall know its name, nor for what workload it was made.

Nine rules

What it recognises

RuleCondition
vsphere-orphaned-snapshotCNS snapshots on the FCD block CSI DeleteVolume
csi-deletevolume-failingAny CSI driver erroring out of DeleteVolume
dangling-volumeattachmentAttachment pointing at a node that no longer exists
pvc-protection-live-podBlocked by pods still mounting the claim — working as intended
backup-finalizerKasten K10, Velero, Trilio, Stash or Portworx holding the volume
stale-claimrefPV reserved for a PVC that was deleted; nothing new can bind
retain-released-expectedReleased plus reclaimPolicy=Retain is not a bug
multi-attach-rwoRWO volume being pulled toward a second node
terminating-no-progressStuck past 15 minutes with no recognised cause

How it behaves

It never writes to your cluster

Read-only by construction. No cluster-side component, no operator, nothing to deploy, no --fix. It shells out to your existing kubectl or oc, so your kubeconfig, auth plugins and RBAC are unchanged — and there is no client-go version skew against whatever your cluster happens to be running.

  • Dependenciesnone — Go standard library only
  • Installone static binary
  • PlatformsLinux, macOS, Windows · amd64, arm64
  • LicenceApache-2.0

Triage a cluster you can't reach

--from reads a directory of JSON dumps instead of a live cluster. Someone with access hands you a bundle; you diagnose it without credentials. Useful for vendors, consultants, and anyone on the wrong side of a change freeze at 3 AM.

mkdir dump
for r in pv pvc pods nodes \
         volumeattachments namespaces events; do
  kubectl get $r -A -o json > dump/$r.json
done

pvdoctor scan --from dump