k8s-unfold — design
Class:
design· the Kubernetes target for the airgap pipeline's install step. Two halves, split by Skidbladnir's charter (core stays sync + no shell; verbs as data; network clients behind features).
f0 — two halves
| half | where | sync? | deps |
| renderer | src/k8s.rs KubernetesRenderer: ServiceRenderer |
sync core | none (hand-rolled YAML) |
| applier | src/k8s_apply.rs (feature kubernetes) |
async | kube 0.99 + k8s-openapi 0.24 + tokio, rustls-tls (no OpenSSL/C) |
With the kubernetes feature off, the applier isn't compiled and neither kube nor tokio enters the build — the lean offline core is unchanged (mirrors redfish/terraform/kvm).
f1 — renderer (data)
ComponentSpec (+ additive k8s fields image/namespace/replicas/stateful/ storage) → to_service_spec under Backend::Kubernetes → KubernetesRenderer emits a RenderedService:
- files = manifests as data: Deployment (or StatefulSet when
stateful), Service (when ports), PVC (whenstorage). - health probes (readiness + liveness) render from the SAME
HealthProbethe hostLifecycle::health()probes — one knob, two views (L5).Tcp(port)→tcpSocket;Http(path)→httpGet {path, port=first socket}(needs a declared socket for the port, same rule as the host HTTP probe — no socket ⇒ no probe);None⇒ no probe (render stays byte-identical). Both readiness and liveness share the one action, so k8s gates Service routing AND restart-on-hang on the endpoint the operator already declared once. - env values render through
yaml_scalar(k8s.rs): a k8senv.valueMUST be a string, so a numeric/boolean/null-looking value (KNUT_FLIGHT_SCALE=1) is quoted (value: "1") — barevalue: 1re-parses to int and the apiserver rejects the manifest. Unambiguous strings (URLs,host:port, paths) stay bare, so pre-existing renders are byte-identical. - verbs as data:
enable= server-side apply every manifest;start/stop= scale N/0;status= rollout-wait. Deterministic bytes (unit-tested).
Example — the knut Arrow Flight server (N5): ComponentSpec::knut_flight (product.rs) renders a plain stateless Deployment + Service in namespace knut on port 50055 (env-only config, TCP readiness/liveness). Rendered by cargo run --example render-knut-flight-k8s; checked-in snapshot + a hand-written Namespace + apply/prove/validate steps live in deploy/knut-flight/. The SAME spec drives the systemd install (cargo run --example install-knut-flight), so the two Runtime targets (Jera-systemd | Kube) never drift.
f2 — applier (network, kubernetes feature)
K8sApplier drives those manifests+verbs over the real API — the systemd-over-D-Bus analog for k8s, no kubectl/helm:
manifests()— pure YAML →DynamicObject(unit-tested, no cluster).apply()— server-side apply (PatchParams::apply("skidbladnir").force()) each object, kind resolved via discovery (any GVK: Deployment/Service/PVC).scale()— patchspec.replicas(Deployment/StatefulSet).wait_ready()— pollstatus.ready_replicasto desired, with timeout.delete()— best-effort teardown.k8s_install()— thin entrypoint: apply → wait ready.Client::try_default()— kubeconfig (laptop) or in-cluster ServiceAccount (Job).
Boundary
Platform bring-up only (workload/service/pvc + wait). The app-data load (DB COPY, S3 objects, config) is tillsynia-admin's job, not Skidbladnir's.
Status
Renderer + applier both green: cargo test (default) 111 lib + 11 integration; cargo test --features kubernetes 114 lib + 11. The apply/scale/wait/delete I/O is exercised only against a live cluster — k8s_live_apply_scale_delete is #[ignore] (run on the lab worker node: cargo test --features kubernetes -- --ignored k8s_live).