Skip to content

etcd-multi-location 2.0.0 — deploy into an existing GVC - #508

Open
jacobecox wants to merge 3 commits into
mainfrom
claude/etcd-ml-gvc
Open

etcd-multi-location 2.0.0 — deploy into an existing GVC#508
jacobecox wants to merge 3 commits into
mainfrom
claude/etcd-ml-gvc

Conversation

@jacobecox

Copy link
Copy Markdown
Contributor

Review diff: git diff b5a2bea..HEAD -- etcd-multi-location/versions/2.0.0/
(b5a2bea is the verbatim cp -R of 1.0.2; everything real is in the two commits after it.)

Why a major version

The chart no longer creates a GVC — it deploys into the user's existing one. That is a breaking change and, more importantly, an upgrade that must never happen in place: a chart that stops declaring a GVC makes helm upgrade prune it, and deleting a GVC takes every workload, volumeset and identity inside.

Measured deliberately on a throwaway GVC during this round: an upgrade with no values deleted the GVC and all its contents in 5 seconds while printing upgraded successfully.

So 2.0.0 ships a render-time fail when the legacy gvc values key is present. Testing confirmed the refusal happens before any resource is touched — afterwards the GVC was still at version 1 with its workload, volumeset and identity present and ready. One hole is not closable at render (an upgrade passing no values at all sees only the new defaults), so the migration path — stand up a NEW release against an existing GVC, move the data, remove the old release — is stated in both the README and the briefing.

The three-layer defence, both arms of each

Layer Closes Result
Render-time fail on the legacy gvc key the destructive in-place upgrade refused before any resource touched; GVC intact
defaultOptions.minScale/maxScale: 0 + localOptions GVC has locations the values don't list undeclared location gets desiredScale: 0, starts nothing
Boot-time GVC read (scoped targetKind: gvc policy) values list a location the GVC lacks — the platform does not validate this at all fatal on a FRESH data dir, WARNING on an initialised one

The read returns 200 with only the scoped policy and 403 against another GVC. The fresh/initialised asymmetry is deliberate: the check can never crash a live cluster.

A pre-existing defect found while testing

internalAccess.type: workload-list silently destroyed the cluster, reached by following the README's own advice. The internal firewall list also governs raft peer traffic on 2380 between the members of this workload, so a list naming only clients blocked the members from each other:

http://127.0.0.1:2379 is unhealthy: failed to commit proposal: context deadline exceeded
etcd: "peer became inactive (message send to peer failed)" / "stopped remote peer"

Helm printed upgraded successfully throughout. The chart now appends its own workload link. Re-tested live: the identical upgrade that killed the cluster now holds — 0 failed writes across 14 samples out to 9 min 11 s, past the catalog's 305 s firewall-propagation high-water mark. The enforcement control still discriminates (listed client writes, unlisted client times out), so the fix widened the list by one entry rather than loosening the firewall.

Present in 1.x and unrelated to the conversion.

Two corrected numbers

The README claimed a helm upgrade costs "about 66 seconds". Measured: a ~105 s rollout with ~20-30 s of full quorum loss — and a no-op upgrade costs the same, so there is no safe upgrade to plan around. Nothing serializes the restart; rolloutOptions.maxUnavailableReplicas is dropped by the API on stateful workloads.

Availability, unchanged by the conversion

  • 3-location cluster ready in 31-46 s.
  • Follower-location down ~73 s: 0 failed writes, outage independently evidenced rather than assumed; the recovered member caught up all 41 keys written while down.
  • Leader-location loss: 0 failed writes, via a logged graceful leadership transfer.
  • Valid recovery.forceNewClusterInLocation rebuilt from WAL, with a pre-recovery control showing writes genuinely hung first.

Testing

27 PASS · 0 FAIL. Drift gate clean — all six resources Unchanged, spec byte-identical. test-gvc restored and verified; janitor clean.

Untested and stated as such: same-org was proven permissive, not restrictive (no second org available). Auto-compaction reclamation and the quota read-only transition were exercised as flags only.

Briefing updated in the same PR.

🤖 Generated with Claude Code

jacobecox and others added 3 commits August 27, 2026 15:05
… bootstrap on a bare majority

The chart no longer creates a GVC (createsGvc: false). Every resource lands in
.Values.global.cpln.gvc, and the location list moves from global.gvc.locations
to global.locations.

Why a major: an in-place `helm upgrade` from 1.x drops `kind: gvc` from the
manifest, and Helm prunes what a chart no longer declares — which deletes the
GVC and every workload, volumeset and identity in it. A render-time `fail` on
the legacy `global.gvc` key makes that upgrade impossible rather than merely
discouraged; migration prose is in both the README and the briefing.

The failure this closes is etcd-specific and is the worst one in the audit: a
GVC with fewer locations than the values list makes `--initial-cluster` declare
members that can never exist, and a `new` bootstrap only needs a majority of the
DECLARED set — so 3 declared / 2 present forms a healthy-looking cluster with
zero fault tolerance. Three layers now stop it:

  - defaultOptions.minScale/maxScale: 0, with localOptions supplying the real
    per-location count, so an undeclared GVC location runs nothing.
  - Check A (values only, unconditional): a member whose CPLN_LOCATION is not in
    the configured list exits with a named error instead of etcd's obscure one.
  - Checks B and C (boot-time GVC read, via a new policy scoped `view` on the
    one install GVC): B refuses a fresh bootstrap when quorum is arithmetically
    impossible, C refuses one when any declared location is absent. Both warn
    rather than fail on an already-initialised member, so a live cluster is never
    crashed and the --force-new-cluster recovery path is never blocked. A failed
    GVC read is a warning, never a failure.

Also: peer and self FQDNs now use ${CPLN_GVC} rather than a render-time GVC
name, and forceNewClusterInLocation gains a boot-time warning for the case
render cannot see — a recovery location present in the values but absent from
the GVC, which no member would ever apply.
Testing followed the README -- "Scope it with `workload-list` if the GVC holds
workloads that should not have it" -- and every member died within ~2 minutes:

  http://127.0.0.1:2379 is unhealthy: failed to commit proposal: context deadline exceeded
  etcd: "peer became inactive (message send to peer failed)" / "stopped remote peer"

The internal firewall list governs ALL inbound internal traffic, including raft
peer traffic on 2380 between the members of this same workload. A list that
omits the etcd workload blocks the members from each other and quorum is lost
cluster-wide, while Helm prints `upgraded successfully`. Control proving the
mechanism rather than a coincidence: re-running the identical upgrade with the
etcd workload added to its own list brought the cluster back in 116 s.

No legitimate configuration denies the members to each other, so the chart now
appends itself instead of requiring the user to know. Rendered across four
controls: absent on the default (same-gvc), first in the list when the user
names only clients, present exactly once when the user also names etcd, and the
previous behaviour untouched when workloads are set on a non-list type.

Pre-existing in 1.x, unrelated to the GVC conversion.

Also corrects two measured numbers the round refuted: the upgrade outage is a
~105 s rollout with ~20-30 s of lost quorum, not "about 66 seconds" -- and a
NO-OP upgrade costs the same, so there is no safe upgrade to plan around.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant