feat(federation): FederatedCluster registry + fleet status rollup (#1234, #1235)#1241
Merged
Merged
Conversation
…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>
defilantech#1234) Signed-off-by: Christopher Maher <chris@mahercode.io>
…alth (defilantech#1235) Signed-off-by: Christopher Maher <chris@mahercode.io>
…ech#1234, defilantech#1235) Signed-off-by: Christopher Maher <chris@mahercode.io>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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>
Merged
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 ([#​890](defilantech/LLMKube#890) item 3) ([#​1258](defilantech/LLMKube#1258)) ([bde1a87](defilantech/LLMKube@bde1a87)) - **charts/foreman:** expose the agent --accelerator flag ([#​1242](defilantech/LLMKube#1242)) ([#​1261](defilantech/LLMKube#1261)) ([a39fd71](defilantech/LLMKube@a39fd71)) - declarative bindAddress on InferenceService ([#​1240](defilantech/LLMKube#1240)) ([#​1247](defilantech/LLMKube#1247)) ([bf01598](defilantech/LLMKube@bf01598)) - **federation:** FederatedCluster registry + fleet status rollup ([#​1234](defilantech/LLMKube#1234), [#​1235](defilantech/LLMKube#1235)) ([#​1241](defilantech/LLMKube#1241)) ([1b5bf08](defilantech/LLMKube@1b5bf08)) - **foreman:** tiered str\_replace matching (exact, trailing-ws, uniform-indent) ([#​1231](defilantech/LLMKube#1231)) ([0372e24](defilantech/LLMKube@0372e24)) - **grafana:** chart the InferenceService metrics with no panel ([#​1245](defilantech/LLMKube#1245)) ([a5ce923](defilantech/LLMKube@a5ce923)) - Kueue prerequisites: spec.suspend, GPUQuota deferral, pkg/apiutil GPU mapping ([#​1254](defilantech/LLMKube#1254)) ([c95e06f](defilantech/LLMKube@c95e06f)) - **metrics:** publish operator state metrics from observed state ([#​1229](defilantech/LLMKube#1229)) ([5db0915](defilantech/LLMKube@5db0915)) - prefetch Model artifacts into the shared cache without an InferenceService ([#​1218](defilantech/LLMKube#1218)) ([7c1161b](defilantech/LLMKube@7c1161b)) ##### Bug Fixes - **chart:** make PrometheusRule select the chart's own scrape targets ([#​1239](defilantech/LLMKube#1239)) ([ffc697a](defilantech/LLMKube@ffc697a)) - delete GPUQuota metric series when the CR is deleted ([#​1230](defilantech/LLMKube#1230)) ([#​1246](defilantech/LLMKube#1246)) ([1f58c3c](defilantech/LLMKube@1f58c3c)) - **foreman:** edits disarm a nudged RepeatedToolCall hash ([#​1215](defilantech/LLMKube#1215)) ([#​1216](defilantech/LLMKube#1216)) ([60df2ad](defilantech/LLMKube@60df2ad)) - gate bite check reverts to the upstream merge-base, never the fork tip ([#​1260](defilantech/LLMKube#1260)) ([8a6928f](defilantech/LLMKube@8a6928f)) - **grafana:** delete dashboard panels for metrics nothing emits ([#​1244](defilantech/LLMKube#1244)) ([9c586d9](defilantech/LLMKube@9c586d9)) - **metrics:** report GPU queue depth per namespace ([#​1243](defilantech/LLMKube#1243)) ([5779c05](defilantech/LLMKube@5779c05)) - recycle inference pods without invalid deadlines ([#​1232](defilantech/LLMKube#1232)) ([0b716d7](defilantech/LLMKube@0b716d7)) - waitForIdle proceeds past crashlooping pods; truthful deferral reason on mixed state ([#​1250](defilantech/LLMKube#1250)) ([#​1262](defilantech/LLMKube#1262)) ([88c87f9](defilantech/LLMKube@88c87f9)) ##### Documentation - envtest test-craft guide (status subresource, probe stubs, asset hygiene) ([#​1263](defilantech/LLMKube#1263)) ([#​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 ([#​890](defilantech/LLMKube#890) item 3) ([#​1258](defilantech/LLMKube#1258)) ([bde1a87](defilantech/LLMKube@bde1a87)) - **charts/foreman:** expose the agent --accelerator flag ([#​1242](defilantech/LLMKube#1242)) ([#​1261](defilantech/LLMKube#1261)) ([a39fd71](defilantech/LLMKube@a39fd71)) - declarative bindAddress on InferenceService ([#​1240](defilantech/LLMKube#1240)) ([#​1247](defilantech/LLMKube#1247)) ([bf01598](defilantech/LLMKube@bf01598)) - **federation:** FederatedCluster registry + fleet status rollup ([#​1234](defilantech/LLMKube#1234), [#​1235](defilantech/LLMKube#1235)) ([#​1241](defilantech/LLMKube#1241)) ([1b5bf08](defilantech/LLMKube@1b5bf08)) - **foreman:** tiered str\_replace matching (exact, trailing-ws, uniform-indent) ([#​1231](defilantech/LLMKube#1231)) ([0372e24](defilantech/LLMKube@0372e24)) - **grafana:** chart the InferenceService metrics with no panel ([#​1245](defilantech/LLMKube#1245)) ([a5ce923](defilantech/LLMKube@a5ce923)) - Kueue prerequisites: spec.suspend, GPUQuota deferral, pkg/apiutil GPU mapping ([#​1254](defilantech/LLMKube#1254)) ([c95e06f](defilantech/LLMKube@c95e06f)) - **metrics:** publish operator state metrics from observed state ([#​1229](defilantech/LLMKube#1229)) ([5db0915](defilantech/LLMKube@5db0915)) - prefetch Model artifacts into the shared cache without an InferenceService ([#​1218](defilantech/LLMKube#1218)) ([7c1161b](defilantech/LLMKube@7c1161b)) ##### Bug Fixes - **chart:** make PrometheusRule select the chart's own scrape targets ([#​1239](defilantech/LLMKube#1239)) ([ffc697a](defilantech/LLMKube@ffc697a)) - delete GPUQuota metric series when the CR is deleted ([#​1230](defilantech/LLMKube#1230)) ([#​1246](defilantech/LLMKube#1246)) ([1f58c3c](defilantech/LLMKube@1f58c3c)) - **foreman:** edits disarm a nudged RepeatedToolCall hash ([#​1215](defilantech/LLMKube#1215)) ([#​1216](defilantech/LLMKube#1216)) ([60df2ad](defilantech/LLMKube@60df2ad)) - gate bite check reverts to the upstream merge-base, never the fork tip ([#​1260](defilantech/LLMKube#1260)) ([8a6928f](defilantech/LLMKube@8a6928f)) - **grafana:** delete dashboard panels for metrics nothing emits ([#​1244](defilantech/LLMKube#1244)) ([9c586d9](defilantech/LLMKube@9c586d9)) - **metrics:** report GPU queue depth per namespace ([#​1243](defilantech/LLMKube#1243)) ([5779c05](defilantech/LLMKube@5779c05)) - recycle inference pods without invalid deadlines ([#​1232](defilantech/LLMKube#1232)) ([0b716d7](defilantech/LLMKube@0b716d7)) - waitForIdle proceeds past crashlooping pods; truthful deferral reason on mixed state ([#​1250](defilantech/LLMKube#1250)) ([#​1262](defilantech/LLMKube#1262)) ([88c87f9](defilantech/LLMKube@88c87f9)) ##### Documentation - envtest test-craft guide (status subresource, probe stubs, asset hygiene) ([#​1263](defilantech/LLMKube#1263)) ([#​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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
FederatedClusterCRD (newfederation.llmkube.dev/v1alpha1group, cluster-scoped). Two-writer status by design: the edge writes the observed fields (lastHeartbeatTime,observedVersion,capacity,inference); the datacenter controller writes ONLYphase.--federation-role=edgemode, lists local Models/InferenceServices/node-GPU capacity and patches its OWNFederatedClusterstatus subresource on the datacenter over a per-site RBAC-scoped client. The datacenter never connects to edges.phasefrom heartbeat staleness (Connected<= 3x interval,Stale<= 10x, elseUnreachable).llmkube fleet registermints a per-site least-privilege ServiceAccount + ClusterRole (exactly:get/update/patchonfederatedclusters/statusandgetonfederatedclusters, bothresourceNames-scoped to that one site - nothing broader) + token, and prints the edge-operator config.llmkube fleet statusreads allFederatedClusters for a per-site + fleet-wide health table.Two-writer safety (the load-bearing invariant)
The edge patch is a
MergeFromon a live-fetched datacenter object withphasenever assigned, so the merge patch omits thephasekey entirely and can never clobber it. The datacenter'sStatus().Updatecarries 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 seedsphase=Connected, runs an edge tick, and asserts phase survives.Default install is unchanged
--federation-roledefaults tohub; a default install runs only the datacenter controller, which with noFederatedClusterCRs reconciles nothing. No federation side effects unless explicitly configured.Type of Change
How Has This Been Tested?
make check-helm-rbacgreen (federation rules covered);go build ./...,GOOS=linux golangci-lint ./...clean;make manifests generate+ CRD chart syncFollow-ups now folded in
Forbidden. It calls the shippedfleet registercode, so it tracks production, and closes the design's "provably cannot write another cluster's status" acceptance item at runtime, not just by construction.fleet registernow prints the apiserver's returnedExpirationTimestampand 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)
observedVersionreportsdevuntil an operator-build ldflags target wires the version. Token rotation automation is a separate later slice.Checklist
docs/federation/registry-and-status.md, sample)Assisted-by: Claude Code (design, implementation, and multi-stage review under human direction); I reviewed and own the final result.
Fixes #1234
Fixes #1235