Unify unbounded-system namespace management under a single operator-owned object
- Dominant language
- Go
- Stars
- 28
- Forks
- 11
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 55
Description
### Summary
Every operator-managed component ships its own `Namespace/unbounded-system` object, and the operator applies all of them under a single server-side-apply (SSA) field manager (`unbounded-operator`) with `ForceOwnership`. Because SSA replaces a manager's owned field set on each apply, the shared namespace is rewritten several times per reconcile pass and its `app.kubernetes.io/name` label churns between competing component values. There is also no single owner for namespace-level policy - most importantly Pod Security Admission (PSA) labels - which the privileged/hostPath workloads in this namespace require. This is pre-existing tech debt whose surface grows with each new component (#517/gantry is the 5th writer).
### Current behavior / evidence
Each manifest set declares its own `Namespace`, with competing labels:
| Component | Source | `app.kubernetes.io/name` |
|---|---|---|
| net (cluster) | `deploy/net/00-namespace.yaml.tmpl` | `unbounded-net` |
| machina (cluster) | `deploy/machina/01-namespace.yaml.tmpl` | *(none)* |
| gantry (cluster) | `deploy/gantry/serviceaccount.yaml.tmpl` (Namespace doc) | `gantry` |
| storage (per-site) | `deploy/unbounded-storage-supervisor/01-namespace.yaml.tmpl` | *(none)* |
| operator (install-time) | `deploy/unbounded-operator/00-namespace.yaml.tmpl` | `unbounded-operator` |
- Component `applyMutator`s skip only `CRDKind` and their own config ConfigMap; none skip `kind: Namespace`, so net/machina/gantry (and storage, per enabled site) apply the Namespace every reconcile.
- All applies use `component.FieldOwner = "unbounded-operator"` with `client.ForceOwnership` (`internal/operator/component/env.go`, `ApplyObject`).
- Result: within one pass the label ping-pongs (net `unbounded-net` -> machina strips -> gantry `gantry` -> a per-site storage pass strips again), bumping `resourceVersion` on each write; the terminal value is nondeterministic across passes. The competing values *are* the conflict.
### Impact
- Redundant writes / `resourceVersion` churn and audit noise on the shared namespace every reconcile.
- **No owner for PSA labels.** `unbounded-system` must permit privileged/hostPath pods (net-node `hostNetwork`, storage `hostPath`, gantry containerd-socket + `/etc/containerd/certs.d` hostPath). Any component setting `pod-security.kubernetes.io/enforce: privileged` today would have it stripped by the next bare `Namespace{}` apply, so the privileged posture is currently an implicit, undocumented cluster assumption.
- The problem scales with the component registry.
### Proposed solution
Make the operator the sole *runtime* owner of the `unbounded-system` Namespace, while keeping a canonical Namespace definition in each component's manifests so the direct-apply (no-operator) path still works. This mirrors the existing CRD model (CRDs ship in manifests **and** the operator bootstraps them and skips them at reconcile - `internal/operator/bootstrap.go`, `BootstrapCRDs` + `CRDMaintainer`).
1. **Central skip (operator reconcile path).** In `component.Env.applyManifestData` (`internal/operator/component/env.go`), skip `obj.GetKind() == "Namespace"` for all component manifest applies (add an exported `NamespaceKind` const alongside `CRDKind`). One change neutralizes all current and future writers.
2. **Operator-owned bootstrap + maintenance.** Add `BootstrapNamespace(ctx, client, namespace)` that SSA-applies one canonical `Namespace` (name derived from the operator's configured namespace) with:
- `app.kubernetes.io/name: unbounded-cloud`, `app.kubernetes.io/managed-by: unbounded-operator`
- PSA: `pod-security.kubernetes.io/enforce: privileged` (+ pinned `enforce-version`; `warn`/`audit` left unset to avoid noise, or set to taste)
Call it at startup alongside `BootstrapCRDs` and add it to the maintainer tick so drift self-heals.
3. **Full template parity (direct-apply path).** Keep the `Namespace` object in every component's rendered manifests, but unify each template to the **identical** canonical labels + PSA that `BootstrapNamespace` uses (drop the per-component `unbounded-net`/`gantry`/`unbounded-operator` values). Direct apply then yields the same privileged-ready namespace as the operator path, and multi-component direct apply is fully idempotent. Split gantry's Namespace out of `serviceaccount.yaml.tmpl` into a standalone `deploy/gantry/00-namespace.yaml.tmpl` so the templates are uniform and the drift test has a clean target.
4. **Drift guard.** Extend `internal/unbounded/namespace_drift_test.go` (which already asserts the template default *name*) to also assert every component template's Namespace carries the canonical labels + PSA labels, so the N template copies and `BootstrapNamespace` cannot diverge.
### Extensibility (documented, not built)
A single canonical label owned by the operator removes today's label competition, so no component needs to write the namespace. If a future component ever has a genuine additive-label need, the sanctioned mechanism is a targeted SSA apply of only that key under a **component-scoped field manager** (e.g. `unbounded-operator/`) using a **component-prefixed key** (e.g. `.unbounded-cloud.io/...`), so distinct managers co-own distinct fields without conflict. No shared/base key (`app.kubernetes.io/*`, `pod-security.kubernetes.io/*`) may be written by a component. A helper (`Env.EnsureNamespaceLabels`) would be added only when a real caller exists.
### Acceptance criteria
- [ ] No operator-managed component applies a `Namespace` object during reconcile (regression test via the apply-interceptor pattern in `internal/operator/components/gantry/gantry_test.go`).
- [ ] Operator creates/maintains a single `unbounded-system` Namespace with the canonical `unbounded-cloud` / `managed-by` labels and PSA `enforce: privileged`; idempotent and drift-correcting.
- [ ] `Env.applyManifestFS`/`applyManifestData` skips `kind: Namespace` (unit test).
- [ ] `BootstrapNamespace` unit test (canonical labels + PSA present, idempotent).
- [ ] Every component template keeps a `Namespace` with the identical canonical labels + PSA; gantry's Namespace is a standalone `00-namespace.yaml.tmpl`.
- [ ] `namespace_drift_test.go` extended to assert name + canonical labels + PSA for every component template; still passes.
- [ ] Namespace name follows the operator's configured install namespace (retarget-aware).
- [ ] `make fmt`, `make lint`, `go test ./internal/operator/... ./internal/unbounded/...` and affected `*-manifests` renders pass.
### Out of scope
- Gantry's on-node `hosts.toml` cleanup-after-removal gap (separate follow-up).
- Gantry's default-enabled posture (intended).
### References
- PR #517 (adds gantry as the 5th namespace writer; surfaced this during review).
- Existing owned-resource pattern: `internal/operator/bootstrap.go` (`BootstrapCRDs`, `CRDMaintainer`).
Contributor guide
Research direction
Start with internal/operator/component/env.go and internal/operator/bootstrap.go, comparing BootstrapCRDs and CRDMaintainer. Inspect the component namespace templates and internal/unbounded/namespace_drift_test.go, then review the gantry apply-interceptor test pattern. Done means the operator alone maintains the canonical namespace, templates match it, drift and idempotence tests pass, and the listed Go tests and renders succeed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- devops, infrastructure
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 50/100