--- # Flux Kustomization: alert runbooks — reconciles ./runbooks. # # WHAT THIS SERVES. Every alert rule in this repo carries # `runbook_url: "https://${BASE_DOMAIN}/runbooks/"` — 42 annotations over 34 distinct names. # Until this tier existed that path returned 404 on the live cluster, so every alert that reached a # human handed them a dead link at the moment they needed help. This Kustomization renders the # markdown under ./runbooks into a ConfigMap and a two-replica nginx that serves it at that exact # prefix. See runbooks/README.md and runbooks/serving/httproute.yaml. # # POST-PLATFORM TIER (dependsOn platform-config), NON-GATING (wait:false) — the same placement and # the same reasoning as homepage/headlamp/oauth2-proxy. It needs the wildcard TLS cert, the shared # Gateway (including the `https-apex` listener) and split-horizon DNS, all of which arrive with # `platform` + `platform-config`; and a leaf web server must never be able to stall the platform # chain, which is the whole lesson of DECISIONS-LOG's "Headlamp gating stall". wait:false guarantees # its health can never gate anything even if something is later mistakenly pointed dependsOn it. # # NO postBuild.substituteFrom — THE ONE DELIBERATE DIVERGENCE FROM EVERY OTHER TIER, AND THE MOST # IMPORTANT LINE IN THIS FILE. # This tier renders the runbook PROSE into a ConfigMap, and runbooks are full of shell: `$POD`, # `$(kubectl ...)`, and — unavoidably, since they document these very annotations — the literal text # `${BASE_DOMAIN}`. Flux's postBuild substitution runs envsubst over the ENTIRE built stream, and it # FAILS OPEN: an undefined `${X}` becomes the empty string while the Kustomization still reports # Ready (DECISIONS-LOG 2026-09-01 — that is how `k8sServiceHost` was emptied and Cilium broke on 2 # of 3 dev nodes). Enabled here it would silently mutilate the documentation: every braced expansion # in every command example blanked, in a file nobody re-reads after writing. Worse, the L0 check # `conformance/tests/l0-static/flux-postbuild-substitution` scans only files reachable through # kustomization `resources:`, not `configMapGenerator` inputs, so the damage would be invisible to # the gate as well. # The fix is to have nothing to substitute. The only knob this tier would have wanted is the apex # hostname, and runbooks/serving/httproute.yaml avoids needing it by declaring NO `hostnames:` and # inheriting the `https-apex` listener's own hostname instead. So this tier is # environment-independent by construction and R-ENV-02 ("authored once, promoted dev -> prod") holds # without substitution rather than because of it. # IF A FUTURE KNOB EVER GENUINELY NEEDS SUBSTITUTION HERE: do not add substituteFrom to this # Kustomization. Split the serving manifests into their own substituted tier and leave the content # ConfigMap in an unsubstituted one, or the runbooks lose their shell examples. # # NO SOPS decryption stanza: this tier carries no encrypted Secret (markdown plus five plain # manifests), so — like homepage — neither clusters/dev nor clusters/prod patches it. apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: runbooks namespace: flux-system spec: interval: 10m retryInterval: 2m timeout: 5m prune: true wait: false dependsOn: - name: platform-config # wildcard TLS + Gateway (incl. the https-apex listener) first sourceRef: kind: GitRepository name: flux-system path: ./runbooks