Skip to content

feat(federation): FederatedCluster registry + fleet status rollup (#1234, #1235)#1241

Merged
Defilan merged 10 commits into
defilantech:mainfrom
Defilan:feat/630-federation-registry
Jul 23, 2026
Merged

feat(federation): FederatedCluster registry + fleet status rollup (#1234, #1235)#1241
Defilan merged 10 commits into
defilantech:mainfrom
Defilan:feat/630-federation-registry

Conversation

@Defilan

@Defilan Defilan commented Jul 23, 2026

Copy link
Copy Markdown
Member

Description

First slice of the multi-cluster federation epic (#630): register each edge site once on the datacenter, and surface fleet-wide inference health in one place. Status only in this slice - no Model artifacts, no inference traffic, no prompts cross clusters.

Design + decomposition: the epic is split into a foundation (this) plus four later slices (#1236 distribution, #1237 router, #1238 dispatch), tracked on #630.

What this adds

  • FederatedCluster CRD (new federation.llmkube.dev/v1alpha1 group, cluster-scoped). Two-writer status by design: the edge writes the observed fields (lastHeartbeatTime, observedVersion, capacity, inference); the datacenter controller writes ONLY phase.
  • Spoke-initiated connectivity. Edge clusters dial home (outbound only), so it works behind NAT/firewall for air-gapped factory-floor sites. The edge operator, in a config-gated --federation-role=edge mode, lists local Models/InferenceServices/node-GPU capacity and patches its OWN FederatedCluster status subresource on the datacenter over a per-site RBAC-scoped client. The datacenter never connects to edges.
  • Datacenter controller sets phase from heartbeat staleness (Connected <= 3x interval, Stale <= 10x, else Unreachable).
  • llmkube fleet register mints a per-site least-privilege ServiceAccount + ClusterRole (exactly: get/update/patch on federatedclusters/status and get on federatedclusters, both resourceNames-scoped to that one site - nothing broader) + token, and prints the edge-operator config.
  • llmkube fleet status reads all FederatedClusters for a per-site + fleet-wide health table.

Two-writer safety (the load-bearing invariant)

The edge patch is a MergeFrom on a live-fetched datacenter object with phase never assigned, so the merge patch omits the phase key entirely and can never clobber it. The datacenter's Status().Update carries the resourceVersion, so a concurrent edge write causes a 409 + requeue rather than a silent revert. Verified end to end by the reviews, including an envtest that seeds phase=Connected, runs an edge tick, and asserts phase survives.

Default install is unchanged

--federation-role defaults to hub; a default install runs only the datacenter controller, which with no FederatedCluster CRs reconciles nothing. No federation side effects unless explicitly configured.

Type of Change

  • New feature (non-breaking; behind a default-off flag)

How Has This Been Tested?

  • Pure unit tests (phase-from-staleness table, status-summary counting incl. multi-vendor GPU sum, least-privilege RBAC shape asserted exactly, fleet-status golden table)
  • Envtest: datacenter phase transitions (Connected/Stale/Unreachable); edge push reflects seeded counts and leaves phase untouched
  • make check-helm-rbac green (federation rules covered); go build ./..., GOOS=linux golangci-lint ./... clean; make manifests generate + CRD chart sync

Follow-ups now folded in

  • Live RBAC-403 envtest (done). A dedicated RBAC-enforcing envtest authenticates as the per-site scoped token (with the admin client cert stripped so the token is the only credential) and asserts a real 403: own-status patch succeeds, other-cluster status / list / own-spec patch are all Forbidden. It calls the shipped fleet register code, so it tracks production, and closes the design's "provably cannot write another cluster's status" acceptance item at runtime, not just by construction.
  • Real token expiry surfaced (done). fleet register now prints the apiserver's returned ExpirationTimestamp and warns when the cluster capped the token lifetime below what was requested, so a short-lived token is visible rather than a silent early expiry.

Remaining follow-up (Minor, non-blocking)

  • observedVersion reports dev until an operator-build ldflags target wires the version. Token rotation automation is a separate later slice.

Checklist

  • Follows project style; self-reviewed; docs added (docs/federation/registry-and-status.md, sample)
  • No new warnings; unit + envtest pass locally
  • AI assistance disclosed: implemented via AI subagents under human orchestration (per-task implement + review, then a whole-branch review that verified the two-writer invariant and RBAC scoping). I directed the design and reviews and own this PR.

Assisted-by: Claude Code (design, implementation, and multi-stage review under human direction); I reviewed and own the final result.

Fixes #1234
Fixes #1235

Defilan added 8 commits July 22, 2026 19:27
…roup (defilantech#1234)

Signed-off-by: Christopher Maher <chris@mahercode.io>
Signed-off-by: Christopher Maher <chris@mahercode.io>
…rom staleness (defilantech#1234)

Signed-off-by: Christopher Maher <chris@mahercode.io>
… datacenter (defilantech#1235)

Signed-off-by: Christopher Maher <chris@mahercode.io>
Signed-off-by: Christopher Maher <chris@mahercode.io>
…alth (defilantech#1235)

Signed-off-by: Christopher Maher <chris@mahercode.io>
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Defilan added 2 commits July 22, 2026 21:48
fleetRegister's ClusterRole was only proven least-privilege BY
CONSTRUCTION (exact-rules assert against a fake client that never
evaluates RBAC). Add a runtime proof: mint a real token for edge-a's
ServiceAccount against an RBAC-enforcing envtest apiserver, authenticate
a client with ONLY that bearer token (every client-cert field stripped
so it can't ride the admin system:masters identity through mutual TLS),
and assert the resulting Allow/Forbid decisions. OWN status patch
succeeds; another cluster's status patch, a federatedclusters list, and
OWN spec patch all return a genuine apierrors.IsForbidden.

Runs its own *envtest.Environment, separate from
internal/controller/suite_test.go, so that suite's admin-access
assumption is untouched. envtest's apiserver already defaults
--authorization-mode=RBAC; the real trap was the admin rest.Config's
system:masters client cert bypassing RBAC entirely, which this test's
scopedRestConfig strips.

Reuses fleetRegister and mintFleetToken verbatim (not a hand-rolled
lookalike), so the ClusterRole this test exercises is the exact one
fleet.go ships.

Signed-off-by: Christopher Maher <chris@mahercode.io>
mintFleetToken requested a ~10y ExpirationSeconds but discarded
tr.Status.ExpirationTimestamp. If the datacenter apiserver caps token
lifetime (--service-account-max-token-expiration), the printed
edge-config snippet silently expired far sooner than it implied.

mintFleetToken now returns the apiserver's actual
ExpirationTimestamp alongside the token, and buildEdgeConfigSnippet
prints it as "# token expires: <RFC3339>". When the real expiry falls
below half of what was requested, it also prints a one-line WARNING
that the cluster capped the token's lifetime and rotation will be
needed by then.

Signed-off-by: Christopher Maher <chris@mahercode.io>
@Defilan
Defilan merged commit 1b5bf08 into defilantech:main Jul 23, 2026
24 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 23, 2026
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request Jul 24, 2026
…10 ➔ 0.9.11) (#1703)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/defilantech/charts/llmkube](https://github.com/defilantech/LLMKube) | patch | `0.9.10` → `0.9.11` |

---

### Release Notes

<details>
<summary>defilantech/LLMKube (ghcr.io/defilantech/charts/llmkube)</summary>

### [`v0.9.11`](https://github.com/defilantech/LLMKube/blob/HEAD/CHANGELOG.md#0911-2026-07-24)

[Compare Source](defilantech/LLMKube@v0.9.10...v0.9.11)

##### Features

- cache prep subcommand with direct chown/chmod syscalls ([#&#8203;890](defilantech/LLMKube#890) item 3) ([#&#8203;1258](defilantech/LLMKube#1258)) ([bde1a87](defilantech/LLMKube@bde1a87))
- **charts/foreman:** expose the agent --accelerator flag ([#&#8203;1242](defilantech/LLMKube#1242)) ([#&#8203;1261](defilantech/LLMKube#1261)) ([a39fd71](defilantech/LLMKube@a39fd71))
- declarative bindAddress on InferenceService ([#&#8203;1240](defilantech/LLMKube#1240)) ([#&#8203;1247](defilantech/LLMKube#1247)) ([bf01598](defilantech/LLMKube@bf01598))
- **federation:** FederatedCluster registry + fleet status rollup ([#&#8203;1234](defilantech/LLMKube#1234), [#&#8203;1235](defilantech/LLMKube#1235)) ([#&#8203;1241](defilantech/LLMKube#1241)) ([1b5bf08](defilantech/LLMKube@1b5bf08))
- **foreman:** tiered str\_replace matching (exact, trailing-ws, uniform-indent) ([#&#8203;1231](defilantech/LLMKube#1231)) ([0372e24](defilantech/LLMKube@0372e24))
- **grafana:** chart the InferenceService metrics with no panel ([#&#8203;1245](defilantech/LLMKube#1245)) ([a5ce923](defilantech/LLMKube@a5ce923))
- Kueue prerequisites: spec.suspend, GPUQuota deferral, pkg/apiutil GPU mapping ([#&#8203;1254](defilantech/LLMKube#1254)) ([c95e06f](defilantech/LLMKube@c95e06f))
- **metrics:** publish operator state metrics from observed state ([#&#8203;1229](defilantech/LLMKube#1229)) ([5db0915](defilantech/LLMKube@5db0915))
- prefetch Model artifacts into the shared cache without an InferenceService ([#&#8203;1218](defilantech/LLMKube#1218)) ([7c1161b](defilantech/LLMKube@7c1161b))

##### Bug Fixes

- **chart:** make PrometheusRule select the chart's own scrape targets ([#&#8203;1239](defilantech/LLMKube#1239)) ([ffc697a](defilantech/LLMKube@ffc697a))
- delete GPUQuota metric series when the CR is deleted ([#&#8203;1230](defilantech/LLMKube#1230)) ([#&#8203;1246](defilantech/LLMKube#1246)) ([1f58c3c](defilantech/LLMKube@1f58c3c))
- **foreman:** edits disarm a nudged RepeatedToolCall hash ([#&#8203;1215](defilantech/LLMKube#1215)) ([#&#8203;1216](defilantech/LLMKube#1216)) ([60df2ad](defilantech/LLMKube@60df2ad))
- gate bite check reverts to the upstream merge-base, never the fork tip ([#&#8203;1260](defilantech/LLMKube#1260)) ([8a6928f](defilantech/LLMKube@8a6928f))
- **grafana:** delete dashboard panels for metrics nothing emits ([#&#8203;1244](defilantech/LLMKube#1244)) ([9c586d9](defilantech/LLMKube@9c586d9))
- **metrics:** report GPU queue depth per namespace ([#&#8203;1243](defilantech/LLMKube#1243)) ([5779c05](defilantech/LLMKube@5779c05))
- recycle inference pods without invalid deadlines ([#&#8203;1232](defilantech/LLMKube#1232)) ([0b716d7](defilantech/LLMKube@0b716d7))
- waitForIdle proceeds past crashlooping pods; truthful deferral reason on mixed state ([#&#8203;1250](defilantech/LLMKube#1250)) ([#&#8203;1262](defilantech/LLMKube#1262)) ([88c87f9](defilantech/LLMKube@88c87f9))

##### Documentation

- envtest test-craft guide (status subresource, probe stubs, asset hygiene) ([#&#8203;1263](defilantech/LLMKube#1263)) ([#&#8203;1264](defilantech/LLMKube#1264)) ([ea4eee6](defilantech/LLMKube@ea4eee6))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/1703
doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request Jul 24, 2026
…mkube (0.9.10 ➔ 0.9.11) (#152)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/home-operations/charts-mirror/llmkube](https://github.com/defilantech/LLMKube) | patch | `0.9.10` → `0.9.11` |

---

### Release Notes

<details>
<summary>defilantech/LLMKube (ghcr.io/home-operations/charts-mirror/llmkube)</summary>

### [`v0.9.11`](https://github.com/defilantech/LLMKube/blob/HEAD/CHANGELOG.md#0911-2026-07-24)

[Compare Source](defilantech/LLMKube@v0.9.10...v0.9.11)

##### Features

- cache prep subcommand with direct chown/chmod syscalls ([#&#8203;890](defilantech/LLMKube#890) item 3) ([#&#8203;1258](defilantech/LLMKube#1258)) ([bde1a87](defilantech/LLMKube@bde1a87))
- **charts/foreman:** expose the agent --accelerator flag ([#&#8203;1242](defilantech/LLMKube#1242)) ([#&#8203;1261](defilantech/LLMKube#1261)) ([a39fd71](defilantech/LLMKube@a39fd71))
- declarative bindAddress on InferenceService ([#&#8203;1240](defilantech/LLMKube#1240)) ([#&#8203;1247](defilantech/LLMKube#1247)) ([bf01598](defilantech/LLMKube@bf01598))
- **federation:** FederatedCluster registry + fleet status rollup ([#&#8203;1234](defilantech/LLMKube#1234), [#&#8203;1235](defilantech/LLMKube#1235)) ([#&#8203;1241](defilantech/LLMKube#1241)) ([1b5bf08](defilantech/LLMKube@1b5bf08))
- **foreman:** tiered str\_replace matching (exact, trailing-ws, uniform-indent) ([#&#8203;1231](defilantech/LLMKube#1231)) ([0372e24](defilantech/LLMKube@0372e24))
- **grafana:** chart the InferenceService metrics with no panel ([#&#8203;1245](defilantech/LLMKube#1245)) ([a5ce923](defilantech/LLMKube@a5ce923))
- Kueue prerequisites: spec.suspend, GPUQuota deferral, pkg/apiutil GPU mapping ([#&#8203;1254](defilantech/LLMKube#1254)) ([c95e06f](defilantech/LLMKube@c95e06f))
- **metrics:** publish operator state metrics from observed state ([#&#8203;1229](defilantech/LLMKube#1229)) ([5db0915](defilantech/LLMKube@5db0915))
- prefetch Model artifacts into the shared cache without an InferenceService ([#&#8203;1218](defilantech/LLMKube#1218)) ([7c1161b](defilantech/LLMKube@7c1161b))

##### Bug Fixes

- **chart:** make PrometheusRule select the chart's own scrape targets ([#&#8203;1239](defilantech/LLMKube#1239)) ([ffc697a](defilantech/LLMKube@ffc697a))
- delete GPUQuota metric series when the CR is deleted ([#&#8203;1230](defilantech/LLMKube#1230)) ([#&#8203;1246](defilantech/LLMKube#1246)) ([1f58c3c](defilantech/LLMKube@1f58c3c))
- **foreman:** edits disarm a nudged RepeatedToolCall hash ([#&#8203;1215](defilantech/LLMKube#1215)) ([#&#8203;1216](defilantech/LLMKube#1216)) ([60df2ad](defilantech/LLMKube@60df2ad))
- gate bite check reverts to the upstream merge-base, never the fork tip ([#&#8203;1260](defilantech/LLMKube#1260)) ([8a6928f](defilantech/LLMKube@8a6928f))
- **grafana:** delete dashboard panels for metrics nothing emits ([#&#8203;1244](defilantech/LLMKube#1244)) ([9c586d9](defilantech/LLMKube@9c586d9))
- **metrics:** report GPU queue depth per namespace ([#&#8203;1243](defilantech/LLMKube#1243)) ([5779c05](defilantech/LLMKube@5779c05))
- recycle inference pods without invalid deadlines ([#&#8203;1232](defilantech/LLMKube#1232)) ([0b716d7](defilantech/LLMKube@0b716d7))
- waitForIdle proceeds past crashlooping pods; truthful deferral reason on mixed state ([#&#8203;1250](defilantech/LLMKube#1250)) ([#&#8203;1262](defilantech/LLMKube#1262)) ([88c87f9](defilantech/LLMKube@88c87f9))

##### Documentation

- envtest test-craft guide (status subresource, probe stubs, asset hygiene) ([#&#8203;1263](defilantech/LLMKube#1263)) ([#&#8203;1264](defilantech/LLMKube#1264)) ([ea4eee6](defilantech/LLMKube@ea4eee6))

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/New_York)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzIuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/152
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant