Repository navigation
cluster-controller: contend for the leader Lease in a pod on any substrate - #1157
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
ffe0f2a to
a9f278c
Compare
Review — 2 findingsLow
Checked and fine:
Verified against |
the-gigi-pplx
left a comment
There was a problem hiding this comment.
Approved. 2 non-blocking findings in the review comment above.
…trate The controller decided whether to contend for its leader Lease from the substrate's `contends_for_leader_lease` capability, which Direct set false. A Direct controller running in a multi-replica Kubernetes Deployment then ran every reconciler the Direct substrate runs in every replica: two public-operation dispatchers, two trigger fan-outs, and two copies of each sweeper and retention loop driving the same compare-and-set writes. Contend when the kill switch leaves election enabled and either the process runs in a Kubernetes pod or its substrate uses the Kubernetes API. A pod is detected from KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT (both set, the rule the kube client applies before building an in-cluster config), through the Environment seam. The new `uses_kubernetes_api` capability, which replaces `contends_for_leader_lease`, is true for the Kubernetes substrate: its client reaches the apiserver from the process environment wherever it runs, so the Kubernetes-substrate decision is unchanged, kubeconfig included. A Direct controller outside a pod, such as on a systemd host, still runs the reconcilers without election. Remove the Kind Direct lane's one-replica guard. Because a Direct controller did not contend for the Lease, the cluster-controller chart failed the render for `substrate: direct` with any replicaCount but 1, and the lane's values-direct.yaml forced one replica. In its pod the Direct controller now contends, so the lane runs the chart's two replicas, one of them leader, as the Kubernetes lane does. Drop the guard, values-direct.yaml (the Tiltfile already sets `substrate`) and the comments that tied the replica count to the substrate, and update the Kind README's Direct-lane section. The default render's objects are unchanged; only the rewritten leader-election comment in the controller Deployment differs in its text. Name the reason in the no-election log line, say in docs/configuration.md when the controller contends, and update the comments that tied election to the substrate.
a9f278c to
60d4f59
Compare
Summary
The cluster controller decided whether to contend for its leader Lease from the substrate's
contends_for_leader_leasecapability, which Direct set to false. A Direct controller in a multi-replica Kubernetes Deployment therefore ran every reconciler in every replica: two public-operation dispatchers, two trigger fan-outs, and two copies of each sweeper and retention loop driving the same compare-and-set writes.After this change the controller contends when the kill switch leaves election on and either:
KUBERNETES_SERVICE_HOSTandKUBERNETES_SERVICE_PORTboth set, the kube client's in-cluster rule), orThe new
uses_kubernetes_apicapability replacescontends_for_leader_leaseand is true only for Kubernetes, so the Kubernetes-substrate decision is unchanged, kubeconfig included. A Direct controller outside a pod (for example under systemd) still runs the reconcilers without election. The no-election log line now names its reason, anddocs/configuration.mdsays when the controller contends.The Kind Direct lane (#1168) held the controller to one replica because of the old behaviour. This PR removes that guard,
values-direct.yaml, and the comments that tied the replica count to the substrate. The Direct lane now runs the chart's two replicas with one leader, like the Kubernetes lane. The default Kubernetes render's objects are unchanged; only the rewritten leader-election comment in the controller Deployment differs in text.Validation
At
60d4f598:cargo nextest run -p pplx-space-cluster-controller-server --all-features: 1176 passed, 0 skipped, against the Postgres the nextest setup script started (no dependency skips). This includes the 48leader_election::tests, among them the full table of kill switch × pod × substrate.bash ci/scripts/run-rust-checks.sh clippy: passed.cargo archwithorigin/main'sarchitecture-baseline.toml,ARCHITECTURE.tomland renames, as CI runs it: 0 required findings. The only finding in a touched file is the existing advisory thatsubstrate/mod.rsis below its cap.bash ci/scripts/run-shell-tests.sh(each test run individually): 40 of 41 pass.scripts/tests/test-seed-download-verification.shfails on the test host becauseunsquashfscannot write asecurity.selinuxxattr as non-root; this change doesn't touch it.pytest scripts/tests/test_kind_*.py scripts/tests/test_cluster_chart_validate.py scripts/tests/test_space_cluster_smoke.py: 571 passed.helm template, before and after, forspace-cluster-controller-localand theinfra/space/cluster/chartumbrella withvalues.example.yaml(both releases): parsed objects identical.substrate=directwithreplicaCount=2now renders (on main the guard refused it).SPACE_KIND_TELEMETRY_MODE=otel bash infra/space/kind/scripts/up.sh): both replicas contended and one won.local.leader_election_brownout_keeps_one_leaderpassed.SPACE_KIND_SUBSTRATE=direct SPACE_KIND_STORAGE_CREDENTIALS=static bash infra/space/kind/scripts/up.sh), 2/2 controller replicas,SPACE_K8S_CONTROL_PLANE=falseon the API and controller:contending for leadership. Exactly one loggedelected leader; starting reconcilersand started the dispatcher, trigger fan-out and sweepers ("skipping Kubernetes-only reconcilers on direct substrate"). The other stayed Ready with no reconciler output. Thecluster-controllerLease named the leader.examples/first_sandbox.py: create, command ("Hello from SPACE"), list, inspect and delete all succeeded.ecw.pause_connect: passed (SPACE_SMOKE_BACKEND=kind SPACE_SMOKE_SUBSTRATE=direct SPACE_SMOKE_API_SURFACE=ecw).native.resident_pause: passed.native.stopped_restart_preserves_rootfs: every stop/restart phase passed; the final node-cleanup check failed with "cannot locate the fixture sandbox worker". That check finds the worker through the sandbox pod, a Direct gap kind: run the Direct substrate on the Kind cluster (SPACE_KIND_SUBSTRATE=direct) #1168 already lists.Severity and risk
Severity: moderate. Duplicate reconcilers on Direct-in-a-pod deployments double-drive dispatch and sweeps, and only compare-and-set fencing keeps that safe.
Risk:
Rollout
During a rolling update of a multi-replica Direct controller in a pod, old pods run reconcilers unelected (as they do today) alongside the new elected leader until they terminate. That is no worse than the current state. Kubernetes-substrate deployments see no behaviour change.
AI assistance: Claude Code wrote the code change, tests, the rebase and conflict resolution, the guard removal, and this description.