Repository navigation
Description
Is there an existing issue already for this bug?
- I searched existing issues and pull requests and did not find this detached-claim case. The terminating-PVC race in [Bug]: Instance recreation deadlocks when the old PVC is still terminating (join pod stuck Pending, never healed) #10985/fix: prevent instance recreation deadlock when the previous PVC is still terminating #11017 is related but different.
I have read the troubleshooting guide
- I read the troubleshooting guide, including the known replica-creation issues.
I am running a supported version of CloudNativePG
This report concerns v1.30.1 and current main source. I have read the supported-releases policy. The official-command path below has not yet been reproduced in a standalone supported test environment.
Contact Details
Replies on this issue.
Version
1.30 (latest patch): v1.30.1, source revision 2a35abb4628f209d149825ef3c38011e0701ff2f.
Also inspected main at 9ed54f3e19a337150d6f52d4d512abed98e4c828; this is not a claim of runtime reproduction on main.
What version of Kubernetes are you using?
Not supplied for this source-based report; the proposed standalone reproduction has not been run.
What is your Kubernetes environment?
Not applicable to the source analysis. A disposable environment with synthetic data is needed to verify the official-command path.
How did you install the operator?
The standalone operator/plugin reproduction has not been installed or run yet.
What happened?
I suspect replica replacement can reuse a PVC explicitly retained and detached by kubectl cnpg destroy --keep-pvc. I am opening this for triage before proposing a patch.
During a custom replica-recovery procedure, detaching a retained claim and removing the failed standby led to a new join Job mounting the same claim. Bootstrap completed, but the operator repeatedly adopted that completed Job rather than restoring the missing instance. This procedure did not invoke the official destroy command, so it is not proof that the supported command has the same result. I am not claiming confirmed loss of the original data.
Expected: claims marked detached should remain out of use during replacement. This follows the destroy documentation and the forensic purpose described in #638. The replacement should be able to reach the declared instance count without reusing those claims. Ordinary name reuse after deletion and reattachment of ready, managed PVCs should still work.
Source evidence
At v1.30.1:
- Destroy removes the Cluster owner reference and sets the
cnpg.io/pvcStatusannotation todetached. It also deletes matching Jobs; an old Job surviving the command is not assumed here. - Managed PVC enumeration filters by ownership. Serial allocation uses
Status.InstanceNames, so the retained name appears available after detachment. - Replica creation creates the join Job before PVC creation. PVC creation accepts an existing non-terminating claim without checking detached status or ownership.
- The bootstrap directory check permits bootstrap after renaming existing PGDATA/WAL when
pg_controldatasucceeds, or removing those directories when it fails. This is a potential preservation risk, not evidence that data was deleted in the observation above.
Current main creates PVCs before a Pod with a bootstrap init container, so the completed-Job adoption symptom should not be attributed to main. However, its allocator and existing-PVC acceptance still appear capable of selecting and mounting a detached claim. That needs runtime verification.
Proposed reproduction — not executed
Use a disposable cluster containing only synthetic data, with the matching v1.30.1 operator and plugin:
- Create a healthy three-instance Cluster and select a current standby whose serial will become the first gap.
- Record its PVC UIDs and run the actual
kubectl cnpg destroy CLUSTER INSTANCE --keep-pvccommand. Do not manually alter metadata or Jobs. - After the command succeeds, verify the retained claims have their original UIDs, no Cluster owner and detached status. Observe which claims any newly created replacement Job/Pod mounts; record its UID and creation time to distinguish it from pre-existing Jobs.
- Check whether the Cluster returns to its declared instance count. Verify retained contents separately rather than inferring preservation from PVC existence.
- Repeat on main using its Pod/init-container flow. Include separate WAL/tablespace claims and negative controls for ordinary deletion/name reuse and ready managed-PVC reattachment.
Could you confirm whether replacement must avoid detached claims and whether this source path warrants a focused regression test? I have not implemented a fix and would like to agree the intended behavior and approach first.
Cluster resource
No standalone fixture yet; the recipe above is unverified. No production manifest is included.
Relevant log output
No standalone reproduction logs yet. The custom-recovery observation is summarized above without production identifiers.
Code of Conduct
- I agree to follow this project's Code of Conduct.
AI assistance: OpenAI Codex was used for the source investigation and preparation of this report.
Activity
devantler commentedon Oct 10, 2026
I have now run this against the v1.30.1 code, so the report no longer rests on source reading alone. It reproduces with the code of the official command.
What I ran. A test added to
internal/cmd/plugin/destroyat tagv1.30.1(2a35abb), using the same fake client the existing tests use. It creates a three-instance cluster (pg-1,pg-2,pg-3) with owned, bound claims and ready Pods, callsDestroy(ctx, "pg", "pg-2", true), and then callsreconcileMissingInstancethree times, marking the join Job as succeeded after the first.Result.
Destroy(..., keepPVC=true)pg-2keeps its UID, has no owner reference,cnpg.io/pvcStatus=detachedStatus.InstanceNamesis[pg-1 pg-3],generateNodeSerialreturns 2, Jobpg-2-joinexists and its Pod template mounts claimpg-2detached, nopg-2Pod is createdkeepPVC=falseInstanceNamesreturns to[pg-1 pg-2 pg-3]So with contiguous serials the replacement is always given the name of the instance that was just destroyed.
createIfNotExistsaccepts the retained claim because it exists and is not terminating. That claim is never owned again, so it never shows up inInstanceNames, and the reconciler picks the same serial on every loop. This matches what I saw on a v1.30.1 cluster: "Creating new Job" followed by "Job already exists, adopting it", repeating, with the instance never coming back.Limits. This is the fake client, not a cluster: no kubelet ran, nothing was mounted and no base backup was taken. I called
reconcileMissingInstancedirectly rather than the wholeReconcile. WhatEnsureTargetDirectoriesDoNotExistthen does with the retained data directory is from reading the code, not from this test. I have not run this onmain, butcreateIfNotExiststhere still has no owner or detached check.Patch against v1.30.1
The forwarder file only exposes unexported reconciler functions to the test; it adds no logic.
go test ./internal/cmd/plugin/destroy/ -v -ginkgo.focus REPRO -ginkgo.vOne possible direction, if you agree this should not happen:
ensureJobAdoptablealready refuses an existing Job the cluster does not own, andcreateIfNotExistscould do the same for an existing claim. I am happy to turn the test into a proper regression test or try that fix once the intended behaviour is agreed.AI assistance: Claude Opus 5.5 was used to write and run this test.
robertobandini commentedon Nov 2, 2026
This reproduces with the official command on a real cluster: CloudNativePG 1.30.1 from
releases/cnpg-1.30.1.yamlon kind (kindest/node:v1.36.4), pluginkubectl-cnpg_1.30.1_linux_x86_64.tar.gzof the release (Build: {Version:1.30.1 Commit:2a35abb46 Date:2026-09-23}), on the replica of a two-instance cluster:kubectl cnpg destroy e2e-actions 1 --keep-pvc -n cnpg-e2e-actions # Instance e2e-actions-1 of cluster e2e-actions has been destroyed and the PVC was kepte2e-actions-1-joinmounts the detached claime2e-actions-1: the bootstrap finds the old data directory (pg_controldata check on existing directory succeeded, renaming the folders), runspg_basebackupand completes in 5 s.e2e-actions-1is never created; the claim staysdetachedwith no owner; the cluster stays in "Creating a new replica", 1 ready of 2.I filed it as #11829 before finding this issue, and closed it as a duplicate; it has the Cluster resource and the operator log lines.