Skip to content

design: component workload overrides - #587

Open
Philip Lombardi (plombardi89) wants to merge 1 commit into
mainfrom
design/component-workload-overrides
Open

design: component workload overrides#587
Philip Lombardi (plombardi89) wants to merge 1 commit into
mainfrom
design/component-workload-overrides

Conversation

@plombardi89

Copy link
Copy Markdown
Collaborator

What

Adds designs/component-workload-overrides.md, a proposal for letting users
customize the Deployments and DaemonSets that unbounded-operator generates and
reconciles.

Design doc only. No code, no CRD changes, no make generate.

Why

Today a user's entire influence over operator-managed workloads is
spec.components.<c>.enabled, metalman.replicas, metalman.dhcpAutoInterface,
and the cluster-wide --image-registry flag. Because the operator applies with
SSA and ForceOwnership (internal/operator/component/env.go:242), there is no
workaround either: a kubectl edit is reverted on the next reconcile and a
GitOps controller will fight the operator indefinitely.

The requirements collected from users and the team are resources, tolerations /
nodeSelector / affinity, sidecars and volumes, env vars, command arguments,
imagePullSecrets, labels / annotations / priorityClassName, and container
images.

Decisions this PR is asking you to ratify

1. Kustomize as a library is rejected. This was the team's initial
suggestion, so §4.1 argues it explicitly rather than dismissing it. Five
reasons, the two load-bearing ones being:

  • Kustomize can rename, add, delete, and re-kind resources, and the operator has
    no pruning (machina.go:57-69). An overlay that renames a DaemonSet orphans
    the original permanently.
  • Site is cluster-scoped and the operator SA installs CRDs, ClusterRoles,
    webhooks, and privileged host-networked DaemonSets. An overlay is arbitrary
    object creation with those credentials. Patching an existing pod template is a
    materially smaller grant.

Also: it would freeze deploy/*/rendered/ layout and every object name in it as
public API, and promote sigs.k8s.io/kustomize/api from transitive
(go.mod:336) to direct.

2. Strategic merge patch against operator-emitted workloads instead.
apimachinery/util/strategicpatch, no new dependency. Everything in the
requirements list lives inside a Deployment or DaemonSet the operator already
emits, so nothing needs object creation or renaming. Merge keys verified against
k8s.io/api@v0.36.3.

3. Stored in a ConfigMap, not Site.spec. The net/machina/gantry
singletons are resolved as "any Site enables it" (machina.go:47-55), so a
per-Site override field is ambiguous when Sites disagree. A cluster-scoped
ConfigMap dissolves that entirely and avoids a v1beta1 conversion obligation.

4. The mechanism is supported; individual patches are not. §2 splits these
explicitly. The schema, merge semantics, validation, and revert behavior are
versioned and maintained. That any given patch stays correct as generated
manifests change, or that an overridden component works or upgrades, is not.

5. Image overrides are permitted. They break the version-lockstep invariant
(main.go:207, manifests_guard_test.go), so §9 makes the drift loud via a
version-drift annotation, a ReconciledWithOverrides condition reason, and an
Event, rather than blocking it.

6. This reverses a documented stance. site_types.go:141-143 and
architecture.md:189 both currently say components are not configurable. §1
quotes both; PR 6 in the implementation plan amends them.

Consequence worth flagging early

kubectl unbounded overrides diff cannot diff against the live object, because
the live object already has the override applied. It needs the operator's
pre-patch render, and today rendering is fused into applying: the mutators are
unexported (net.go:115, machina.go:123, gantry.go:187, storage.go:223)
and metalman's builder is unexported and typed (metalman.go:98).

So diff requires extracting a Render capability from all five components.
That is sequenced as its own behavior-preserving PR (§12, PR 4) ahead of diff
(PR 5). It stands on its own merits, but nobody should be surprised by it later.

Implementation sequence (§12)

PR Scope
1 This doc
2 Override engine, ApplyObject gate, watch. No component file changes.
3 overrides list and overrides validate
4 Render/apply split across all five components. Refactor only.
5 overrides diff
6 Docs, including amending architecture.md:189 and site_types.go:141-143

Open questions for reviewers (§15)

Please reply against the numbers.

  1. overrides diff output format: unified YAML diff, or path-oriented summary
    by default?
  2. Should schema validation move to a ValidatingAdmissionPolicy on the
    ConfigMap? It can only do partial validation, since resolving component and
    sites needs cluster state.
  3. spec.replicas in a patch vs the existing typed
    spec.components.metalman.replicas (site_types.go:167). Which wins, or
    should overrides reject spec.replicas on metalman?
  4. siteSelector (label-based Site matching) is deferred in favour of a sites
    name list. Agree?
  5. Should SSA field disownership be offered separately for the "let VPA own
    resources" case? It is more correct than patching resources to a fixed value.
  6. Reserved container names for sidecars, or is a documented convention enough?

Review notes

  • designs/ is not published to the docs site (docs.yaml only fires on
    docs/**), so this is internal.
  • ci.yaml has no path filter, so the full matrix runs on this docs-only PR and
    should pass trivially.
  • Status is Draft for team review. It flips to Accepted in a final commit on
    this branch once the open questions are resolved, then squash merge.

@plombardi89
Philip Lombardi (plombardi89) requested a review from a team August 7, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant