Skip to content

Commit bc25aa9

Browse files
committed
Initial phase 1-3 of a per-node Kata config delivery
Signed-off-by: Erick Bourgeois <erick@jeb.ca>
1 parent ad7b3ea commit bc25aa9

24 files changed

Lines changed: 2706 additions & 4 deletions

.claude/CHANGELOG.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,164 @@ The format is based on the regulated environment requirements:
99

1010
---
1111

12+
## [2026-06-09 01:00] - Kata config delivery Phase 3 — node agent host-sync engine
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Changed
17+
- `src/kata_config_agent.rs`: new host-filesystem sync engine — `sha256_hex`,
18+
`file_sha256`, `SyncAction` + `decide_action` (pure decision), `atomic_write`
19+
(temp + `rename`, mode 0644, parent-dir create, crash-safe), `remove_if_present`,
20+
and `sync_once``SyncOutcome` (Wrote/Deleted/Unchanged). Drift self-heals.
21+
- `src/kata_config_agent_tests.rs`: 22 unit tests (SHA-256 vectors, decision
22+
matrix, atomic write incl. mode/parent-dirs/no-temp-leak/crash-safety,
23+
idempotent unlink, end-to-end sync incl. drift correction + GitOps delete).
24+
- `src/bin/kata_config_agent.rs`: new `5spot-kata-config-agent` binary — clap
25+
CLI (`SOURCE_PATH`/`DEST_PATH`/`NODE_NAME`/`POLL_INTERVAL_SECS`/`--oneshot`)
26+
driving a drift-watch poll loop over `sync_once`.
27+
- `src/lib.rs`: export `kata_config_agent` module.
28+
- `Cargo.toml`: add `sha2` dependency (already in the graph transitively) and
29+
the `5spot-kata-config-agent` bin target.
30+
31+
### Why
32+
Phase 3 of ADR 0003 — land the drop-in on the host and self-heal drift. The
33+
applied-hash node annotation and the `nsenter` host k0s-service restart are
34+
Phase 4; the DaemonSet manifest, agent RBAC, `.trivyignore`, and metrics land
35+
in Phase 4 with the final (privileged) security posture so they are decided as
36+
one unit. The drift-watch is a deliberate node-local poll — out-of-band host
37+
edits raise no Kubernetes event to watch on.
38+
39+
### Impact
40+
- [ ] Breaking change
41+
- [ ] Requires cluster rollout
42+
- [ ] Config change only
43+
- [ ] Documentation only
44+
45+
> New binary only; not yet deployed (no DaemonSet until Phase 4). No controller
46+
> or CRD behavior change. Verified end-to-end via `--oneshot`: write → no-op →
47+
> delete, parent dirs created, no temp-file leakage.
48+
49+
---
50+
51+
## [2026-06-09 00:00] - Kata config delivery Phase 2 — controller projection + RBAC
52+
53+
**Author:** Erick Bourgeois
54+
55+
### Changed
56+
- `src/constants.rs`: new kata-config constants — `KATA_CONFIG_LABEL`
57+
(`5spot.finos.org/kata-config`), `KATA_CONFIG_LABEL_ENABLED`,
58+
`KATA_CONFIG_NAMESPACE`, `KATA_CONFIG_CONFIGMAP_PREFIX`,
59+
`KATA_CONFIG_DATA_KEY`, `KATA_CONFIG_FIELD_MANAGER`
60+
(`5spot-controller-kata-config`).
61+
- `src/reconcilers/helpers.rs`: kata projection — pure builders
62+
`per_node_kata_configmap_name`, `build_kata_config_configmap`,
63+
`build_kata_config_label_patch`; content extractors
64+
`extract_kata_content_from_configmap` / `_from_secret`; async
65+
`resolve_kata_config_content` and `reconcile_kata_config_provision`
66+
(resolve source → stamp label → SSA per-node ConfigMap; tear down on
67+
`None`, 404-on-delete benign). Mirrors the reclaim-agent projection.
68+
- `src/reconcilers/helpers_tests.rs`: 18 new tests — pure builders/extractors
69+
(happy/missing-key/non-UTF-8) plus five mock-client orchestrator tests
70+
(ConfigMap source, Secret source w/ custom key, tear-down, 404-benign,
71+
source-404 short-circuit).
72+
- `src/reconcilers/scheduled_machine.rs`: `provision_kata_config_best_effort`
73+
wired into `handle_active_phase` beside reclaim/taints — child-cluster-scoped
74+
writes, management-cluster source read, non-fatal.
75+
- `deploy/deployment/rbac/clusterrole.yaml`: new `configmaps`
76+
get/create/patch/delete rule (first such rule); secrets rule comment notes
77+
kataConfigRef source reads.
78+
79+
### Why
80+
Phase 2 of ADR 0002 — project the referenced kata drop-in into a per-node
81+
ConfigMap on the child cluster and stamp the opt-in label, so the Phase 3 node
82+
agent can consume it. Implements the `rel-controller-kata-config-projection`
83+
relationship already modeled in CALM.
84+
85+
### Impact
86+
- [ ] Breaking change
87+
- [x] Requires cluster rollout
88+
- [ ] Config change only
89+
- [ ] Documentation only
90+
91+
> Apply the updated controller ClusterRole (new `configmaps` rule) before
92+
> rolling out the controller, or kata-config projection will fail with RBAC
93+
> Forbidden. Behavior is inert for ScheduledMachines without `kataConfigRef`.
94+
95+
---
96+
97+
## [2026-06-08 01:00] - Kata config delivery Phase 1 — CRD `spec.kataConfigRef`
98+
99+
**Author:** Erick Bourgeois
100+
101+
### Changed
102+
- `src/crd.rs`: new `kata_config_ref: Option<KataConfigRef>` field on
103+
`ScheduledMachineSpec`; new `KataConfigRef` struct (kind/name/key/destPath/
104+
restartService, `deny_unknown_fields`, in-namespace only) and
105+
`KataConfigSourceKind` enum (`ConfigMap`/`Secret`); four field schema
106+
functions and three default functions.
107+
- `src/crd_tests.rs`: 12 new TDD tests (serde defaults, round-trip, PascalCase
108+
kind, rejects invalid kind / unknown fields, required kind+name, optional in
109+
schema, name/destPath/restartService schema bounds). Existing spec literals
110+
updated with `kata_config_ref: None`.
111+
- `src/reconcilers/{scheduled_machine_tests,helpers_tests,child_client_tests}.rs`,
112+
`tests/integration_child_kubeconfig.rs`: spec literals updated with
113+
`kata_config_ref: None`.
114+
- `src/bin/crddoc.rs`: documents the new `kataConfigRef` field + example.
115+
- `deploy/crds/scheduledmachine.yaml`, `docs/src/reference/api.md`: regenerated
116+
(`make crds``make crddoc`).
117+
118+
### Why
119+
Phase 1 of ADR 0002 — establish the `spec.kataConfigRef` CRD contract (types,
120+
schema bounds, serde defaults) test-first before the controller projection
121+
(Phase 2) and node agent (Phases 3–4). No behavioral/controller code yet.
122+
123+
### Impact
124+
- [ ] Breaking change
125+
- [ ] Requires cluster rollout
126+
- [x] Config change only
127+
- [ ] Documentation only
128+
129+
> CRD schema change (additive, optional field). Apply the regenerated
130+
> `deploy/crds/scheduledmachine.yaml` to pick up `kataConfigRef`; existing
131+
> ScheduledMachines are unaffected (field is optional, omitted when unset).
132+
133+
---
134+
135+
## [2026-06-08 00:00] - ADR + CALM: per-node Kata config delivery (design only)
136+
137+
**Author:** Erick Bourgeois
138+
139+
### Changed
140+
- `docs/adr/0002-per-node-kata-config-delivery.md`: new ADR (Proposed) — the
141+
`spec.kataConfigRef` contract and controller-side per-node ConfigMap
142+
projection, mirroring the reclaim-agent projection; in-namespace source only
143+
(like `KubeconfigSecretRef`).
144+
- `docs/adr/0003-in-pod-host-service-restart-via-nsenter.md`: new ADR (Proposed)
145+
— the privileged `5spot-kata-config-agent` DaemonSet that writes the host
146+
drop-in and restarts the host k0s service via `nsenter -t 1 … systemctl
147+
restart`, with the applied-hash annotation as the restart-loop guard.
148+
- `docs/adr/README.md`: index updated with ADR-0002 and ADR-0003.
149+
- `docs/architecture/calm/architecture.json`: modeled the feature — new nodes
150+
`service-kata-config-agent` and `data-asset-kata-config-configmap`; new
151+
relationships `rel-controller-kata-config-projection`,
152+
`rel-kata-agent-workload-kube-api`, `rel-kata-agent-writes-host`,
153+
`rel-kata-configmap-stored-in-workload-api`; new flow
154+
`flow-kata-config-delivery`; `adrs` linked. `make calm-validate` passes;
155+
`make calm-diagrams` renders.
156+
157+
### Why
158+
ADD requires the decision to be recorded and modeled before code. This entry
159+
covers the ADR + CALM deliverables only; no Rust, CRD, or manifest changes yet —
160+
implementation (TDD) is gated on review of these two ADRs.
161+
162+
### Impact
163+
- [ ] Breaking change
164+
- [ ] Requires cluster rollout
165+
- [ ] Config change only
166+
- [x] Documentation only
167+
168+
---
169+
12170
## [2026-05-31 00:00] - Adopt Architecture Driven Development (ADD) methodology
13171

14172
**Author:** Erick Bourgeois

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ lazy_static = "1.5"
6868
# enabled explicitly.
6969
warp = { version = "0.4", default-features = false, features = ["server"] }
7070
toml = "1.1"
71+
# SHA-256 for the kata-config agent's content/drift hashing and (Phase 4)
72+
# the applied-hash node annotation. RustCrypto, pure-Rust, no system deps.
73+
sha2 = "0.10"
7174

7275
# Linux-only — used by the reclaim-agent's rung 2 netlink proc connector
7376
# subscriber (`src/netlink_proc.rs`). The portable byte parsers in that
@@ -81,6 +84,10 @@ nix = { version = "0.31", default-features = false, features = ["socket", "net",
8184
name = "5spot-reclaim-agent"
8285
path = "src/bin/reclaim_agent.rs"
8386

87+
[[bin]]
88+
name = "5spot-kata-config-agent"
89+
path = "src/bin/kata_config_agent.rs"
90+
8491
[dev-dependencies]
8592
tokio-test = "0.4"
8693
mockall = "0.14"

deploy/crds/scheduledmachine.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,78 @@ spec:
134134
- kind
135135
- spec
136136
type: object
137+
kataConfigRef:
138+
description: |-
139+
Optional reference to a `Secret` or `ConfigMap` (in this
140+
ScheduledMachine's own namespace) holding a Kata containerd drop-in to
141+
deliver to the node(s) this resource owns.
142+
143+
When set, the controller projects the referenced content into a per-node
144+
ConfigMap (`kata-config-<node-name>`) in the `5spot-system` namespace on
145+
the workload cluster and stamps the `5spot.finos.org/kata-config=enabled`
146+
opt-in label on the backing Node. The `5spot-kata-config-agent`
147+
DaemonSet — scheduled onto labelled nodes — writes the drop-in to the
148+
configured `destPath` on the host and restarts `restartService` so
149+
containerd reloads it. Clearing the field (or deleting the SM) removes
150+
the ConfigMap, the label, and the host file (GitOps semantics).
151+
152+
This is config *delivery*, not a Kata install: the `/opt/kata` binaries
153+
remain `kata-deploy`'s responsibility, and the existing
154+
`katacontainers.io/kata-runtime` opt-in label is unaffected. Cross-
155+
namespace sources are NOT supported (security boundary). See ADR 0002 and
156+
ADR 0003.
157+
nullable: true
158+
properties:
159+
destPath:
160+
default: /etc/k0s/container.d/kata-containers.toml
161+
description: |-
162+
Absolute host path the agent writes the drop-in to. Defaults to
163+
`/etc/k0s/container.d/kata-containers.toml` (the k0s containerd drop-in
164+
directory). Configurable because layouts vary (k0s-in-docker, kairos,
165+
vanilla containerd).
166+
maxLength: 4096
167+
minLength: 1
168+
pattern: ^/[^\u0000]*$
169+
type: string
170+
key:
171+
default: kata-containers.toml
172+
description: |-
173+
Key within the source's `data` map whose value is the drop-in content.
174+
Defaults to `kata-containers.toml`.
175+
maxLength: 253
176+
minLength: 1
177+
pattern: ^[A-Za-z0-9._-]+$
178+
type: string
179+
kind:
180+
description: Whether the source is a `ConfigMap` or a `Secret`. Required (no default).
181+
enum:
182+
- ConfigMap
183+
- Secret
184+
type: string
185+
name:
186+
description: |-
187+
Name of the source object. RFC-1123 DNS subdomain (max 253 chars). The
188+
object MUST live in the same namespace as the ScheduledMachine — there
189+
is no `namespace` field by design.
190+
maxLength: 253
191+
minLength: 1
192+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
193+
type: string
194+
restartService:
195+
default: k0sworker.service
196+
description: |-
197+
systemd unit the node agent restarts (via `nsenter`) so containerd
198+
reloads the drop-in. Defaults to `k0sworker.service`; override with
199+
`k0scontroller.service` on single-node / controller-runs-workloads
200+
layouts.
201+
maxLength: 255
202+
minLength: 1
203+
pattern: ^[A-Za-z0-9@._-]+\.service$
204+
type: string
205+
required:
206+
- kind
207+
- name
208+
type: object
137209
killIfCommands:
138210
description: |-
139211
Optional list of process patterns that trigger an emergency node

deploy/deployment/rbac/clusterrole.yaml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,31 @@ rules:
6161
resources: ["events"]
6262
verbs: ["create", "patch"]
6363

64-
# Secrets for SSH keys and bootstrap data
64+
# Secrets for SSH keys and bootstrap data.
65+
#
66+
# Read-only: also covers reading a kataConfigRef source Secret in the
67+
# ScheduledMachine namespace (ADR 0002). The controller never writes
68+
# Secrets, so no create/patch/delete here.
6569
- apiGroups: [""]
6670
resources: ["secrets"]
6771
verbs: ["get", "list", "watch"]
6872

73+
# ConfigMaps for kata-config delivery (ADR 0002).
74+
#
75+
# `get` reads a kataConfigRef source ConfigMap in the ScheduledMachine
76+
# namespace. `create`/`patch`/`delete` project (server-side apply) and
77+
# tear down the per-node `kata-config-<node>` ConfigMap in 5spot-system.
78+
# In multi-cluster deployments these writes target the child cluster via
79+
# the resolved kubeconfig (whose own RBAC must permit them); this rule
80+
# additionally covers the degenerate single-cluster case where the
81+
# management client is also the workload client. A ClusterRole cannot be
82+
# scoped by name prefix or namespace, so the verbs are cluster-wide — the
83+
# blast radius is bounded by the controller writing only to the
84+
# `kata-config-*` names it owns via the dedicated SSA field manager.
85+
- apiGroups: [""]
86+
resources: ["configmaps"]
87+
verbs: ["get", "create", "patch", "delete"]
88+
6989
# Nodes for drain operations and reclaim-agent label / taint management.
7090
#
7191
# `update` was removed in Phase 5 of the 2026-04-25 security audit

0 commit comments

Comments
 (0)