Consolidate components into a single unbounded-system namespace with operator-driven migration#383
Open
plombardi89 wants to merge 10 commits into
Open
Consolidate components into a single unbounded-system namespace with operator-driven migration#383plombardi89 wants to merge 10 commits into
plombardi89 wants to merge 10 commits into
Conversation
Fold the namespace-unification foundation from #372 onto the operator (shared-site) line: - internal/unbounded.SystemNamespace as the Go-side source of truth, with the drift-guard test extended to cover the unbounded-operator and storage-supervisor namespace templates. - Makefile UNBOUNDED_NAMESPACE (default unbounded-system) feeding every per-component *_NAMESPACE var, including UNBOUNDED_OPERATOR_NAMESPACE. - Flip the unbounded-operator manifest template defaults (namespace + --net-namespace) from unbounded-kube/unbounded-net to unbounded-system. - Carry in #372's component templatization (gantry, inventory, orca, machine-ops, storage), scripts, smoke test, and migrate-namespace.sh. Conflicts with the operator redesign resolved in favor of the operator model: the deleted kubectl install paths stay deleted and the stale ensureMachinaIsRunning helper is dropped.
Point the operator and kubectl install Go defaults at unbounded.SystemNamespace: - internal/operator: DefaultNamespace and DefaultNetNamespace both resolve to unbounded-system (net keeps a distinct knob via --net-namespace). - cmd/unbounded-operator: --leader-elect-namespace default. - kubectl install: machinaNamespace and netNamespace constants. Because the Go defaults now equal the rendered manifest namespace, the operator's namespace string-rewrite is an inert no-op on default installs and only engages for explicit overrides.
Add internal/operator/migrate.go: a leader-elected LegacyReaper Runnable, gated behind --reap-legacy-resources (default off), that consolidates an existing split-namespace install onto the unified namespace: - copies non-regenerable state (user/operator Secrets, the machina-config ConfigMap) from unbounded-kube/unbounded-net into the target namespace, skipping auto-managed (SA-token, helm) and regenerable (net serving cert) secrets and never clobbering existing target copies; - rewrites the namespace embedded in cluster-scoped secret references (Machine.spec.pxe.redfish.passwordRef, MachineOperationCredential spec.auth.secretRef); - gates each component on its target workloads being healthy, reaps net last, and deletes only operator-owned resources (matched by app.kubernetes.io/name) in the legacy namespaces; - NEVER deletes the legacy Namespace objects: an operator removes those manually once empty. Grant the operator ClusterRole delete on the component kinds (not on namespaces) plus machine/machineoperationcredential patch for the secret ref rewrites. Idempotent throughout; unit tests cover copy/skip, idempotency, ref rewrite, health-gating, label-scoped reaping, and the namespace-preservation invariant.
- release-upgrade.yaml: rollout waits, image summaries, and the orca-credentials secret check now target unbounded-system (this PR is the release vehicle, so the #374 follow-up is folded in here). - cli.md: document the unbounded-system defaults and a migration runbook for the operator-driven --reap-legacy-resources path plus the manual 'kubectl delete ns' step. - migrate-namespace.sh: note the operator-driven path as preferred for operator-managed clusters.
Make the legacy migration testable end-to-end and fix two reaper gaps surfaced by the e2e: - Add a one-shot 'unbounded-operator migrate-legacy' subcommand that runs the same migrate-then-reap logic to completion and exits (suitable for a migration Job and for deterministic e2e). Refactor the reaper loop into RunToCompletion, shared by the always-on Runnable and the subcommand. - Reap by the labels components actually carry: machina uses 'app', while net/storage/metalman use 'app.kubernetes.io/name' (machina would have been missed before). Selectors are now per-component. - Skip components with no legacy footprint so the reaper never blocks on a target workload that will never exist (e.g. storage never installed). Extend hack/smoke-namespace-migration.py with an 'operator-reap' kind scenario that stages a legacy install plus a neutralized unbounded-system target, runs 'migrate-legacy', and asserts: legacy operator-owned workloads reaped, secrets/machina-config copied (serving-cert and SA-token skipped), apiServerEndpoint preserved, cluster-scoped CR secret refs rewritten, target workloads intact, legacy namespaces retained, and idempotency on re-run. Wire the operator paths into the smoke workflow triggers. Validated on a real kind cluster (operator-reap and happy scenarios both pass).
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.
Problem
Unbounded spreads its components across several namespaces: machina, metalman, and unbounded-storage land in
unbounded-kube, unbounded-net lands inunbounded-net, and the operator itself runs inunbounded-kube(orca, inventory, machine-ops, and gantry add still more). The split is historical rather than meaningful. It fragments RBAC, namespace-scoped discovery, NetworkPolicy, and day-2 operations, and there is no single namespace that represents "Unbounded" on a cluster.This PR consolidates everything - the operator and every component it manages - onto one configurable namespace,
unbounded-system, and provides a safe, automated path to move existing clusters (already running the split layout) onto it. Kubernetes namespaces cannot be renamed in place, so existing clusters need a deliberate migration, not a config flip.Components are deployed by
unbounded-operator, which reconcilesSite.spec.componentsinto the corresponding workloads, so the operator is the natural owner of the migration.What this PR delivers
unbounded-system.unbounded-systemand decommissions the operator-owned resources left behind in the old namespaces.Single-namespace model
internal/unbounded.SystemNamespaceis the Go source of truth for the default namespace; binary defaults and fallbacks derive from it.UNBOUNDED_NAMESPACEvariable (defaultunbounded-system) feeds every per-component*_NAMESPACEvariable, and manifest templates default via{{ default "unbounded-system" .Namespace }}. One override moves everything; individual components can still be overridden.unbounded.SystemNamespace, so the Go constant and the manifest defaults cannot silently diverge.--net-namespaceknob for operators who want it isolated, but it now defaults to the shared namespace.kube-system/kube-publicreads (bootstrap tokens, cluster-info, kube-proxy) are deliberately left untouched.Because the operator's namespace constants now equal the namespace baked into the manifests it applies, the operator's namespace rewrite is a no-op on default installs and only engages when an operator overrides the namespace.
Migration workflow
Migration runs either continuously (start the operator with
--reap-legacy-resources, off by default) or as a one-shot (unbounded-operator migrate-legacy, suitable for a Job). Both run the same idempotent routine; the one-shot variant exits once the migration is complete. Per pass:unbounded-kube,unbounded-net) intounbounded-system: operator/user Secrets and themachina-configConfigMap. Server-managed metadata is stripped, existing target copies are never overwritten, and resources that are regenerated or auto-managed are skipped (the net serving cert, ServiceAccount tokens, Helm release secrets).Machine.spec.pxe.redfish.passwordRef.namespaceandMachineOperationCredential.spec.auth.secretRef.namespace.unbounded-system, delete the resources it left behind in the legacy namespace. Components are matched by the label each manifest actually carries, and deletion is scoped to those labels, so unrelated workloads sharing the namespace are never touched. net is reaped last, because removing the old net data plane before the new one is Ready is briefly disruptive and that cutover should happen after everything else has moved. A component with no legacy footprint is skipped, so the routine never blocks on a workload that will never exist.The operator never deletes the legacy
Namespaceobjects. Deleting a shared namespace is the one irreversible, broad-blast-radius action in the flow (it cascades to any not-yet-migrated workloads and their data), so it is left to a human once the namespaces are confirmed empty:Every step is create-if-absent / patch / delete-if-exists, so an interrupted run is safe to re-run to completion.
Safety properties
namespaces: deletepermission; it deletes only the component kinds it owns.Testing
internal/operator/migrate_test.gocovers the copy/skip rules, idempotency, the cluster-scoped reference rewrites, health-gating, label-scoped reaping, the no-footprint skip, and the invariant that the operator never issues a namespace deletion.operator-reapinhack/smoke-namespace-migration.pyis an end-to-end kind test: it stages a legacy split-namespace install plus operator-owned state and cluster-scoped CRs, stands up a neutralizedunbounded-systemtarget, runsmigrate-legacy, and asserts the full end state (legacy workloads reaped; secrets and config copied while regenerable/auto-managed are skipped;apiServerEndpointpreserved; CR references rewritten; target workloads intact; legacy namespaces retained; idempotency on re-run). The standalonemigrate-namespace.shpath (air-gapped / non-operator) is covered by the existing scenarios. The smoke workflow now also triggers on operator source changes.Reviewer guide
internal/operator/migrate.go- the migration routine (copy, rewrite, health-gate, reap).cmd/unbounded-operator/main.go- the--reap-legacy-resourcesflag and themigrate-legacysubcommand.deploy/unbounded-operator/02-rbac.yaml.tmpl- operator permissions (note: no namespace delete).internal/unbounded/- the namespace source of truth and drift-guard.hack/smoke-namespace-migration.py- the end-to-end coverage.Out of scope / follow-ups
kubectl delete namespaceof the emptied legacy namespaces is intentionally manual.--reap-legacy-resources); it is not enabled by default.