The Status section still said phases 0-6 were deployed on the DEV cluster and that Phase 7 was not open with clusters/prod 'a design-only overlay'. Dev was decommissioned on 2026-09-12 and prod has been the live cluster serving the apex since. It also claimed 36/51 requirements covered with a safety-critical gap, and that Velero was blocked on OQ-BKP. Now states Phase 7 as open with its three exit criteria and which one is actually outstanding; corrects the node inventory from the Proxmox dev IPs to the bare-metal prod nodes; records Velero as unblocked to R2; and adds the distinction the old text elided — coverage (51/51) is not proof (PROVEN: 0), because the only dr-restore and node-loss proofs were recorded on a cluster that no longer exists, against MinIO rather than R2. Also records the owner's decision that Velero+kopia to R2 is the accepted volume backup, with no Longhorn backup target, and that Longhorn config belongs in source. Co-Authored-By: Claude Opus 5 <[email protected]>
102 lines
6.8 KiB
Markdown
102 lines
6.8 KiB
Markdown
# home-network-k3s-impl
|
||
|
||
Implementation of the home k3s + Longhorn platform. This repo holds the **reconciled artifacts**
|
||
(Flux config, Kustomize overlays, KubeVirt/Helm manifests, ServiceDefinitions, Kyverno policies,
|
||
Chainsaw conformance tests, CI). It is driven by — and subordinate to — the **read-only
|
||
specification** at [`../home-network-k3s`](../home-network-k3s).
|
||
|
||
## Rules (from the spec — non-negotiable)
|
||
- **The spec is read-only.** Never modify anything under `../home-network-k3s`. Implement against it;
|
||
escalate spec problems, don't edit them (spec `AGENTS.md` §11, R-TEST-06).
|
||
- **Done = a green, reproducible, requirement-tagged conformance test** — never "merged," never a
|
||
manual demo (spec `docs/14`, `agents/definition-of-done.md`).
|
||
- **No work on `main`.** Every change is a feature branch merged into `main` (spec `CONTRIBUTING.md`).
|
||
- **No plaintext secrets** — SOPS-encrypted only (spec `docs/07`).
|
||
- **Phase discipline** — only build what the active roadmap phase authorizes (spec `roadmap/phases.md`).
|
||
|
||
## Layout
|
||
```
|
||
infra/ node provisioning: OpenTofu (Proxmox VE) + cloud-init + Ansible, and the
|
||
pinned dev container that carries the whole toolchain
|
||
bootstrap/ one-time cluster bootstrap: Flux install, SOPS/age key, host Gitea
|
||
platform/ platform CONTROLLERS (Flux HelmReleases / operator bases): kyverno, cilium,
|
||
longhorn, external-snapshotter, cert-manager, externaldns, gateway, coredns,
|
||
vpn-egress, monitoring, cnpg, keycloak, oauth2-proxy, kubevirt
|
||
platform-config/ platform CONFIGS — the custom resources of the CRDs installed above
|
||
(ClusterIssuers, Gateway, Kyverno policies, KubeVirt/CDI CRs, Keycloak CRs).
|
||
Split out because CRs and the CRDs they use cannot apply in one pass.
|
||
apps/ application tier, reconciled as dependency-ordered WAVES:
|
||
wave-0/ wave-1/ wave-2/ rendered container apps (see DESIGN-waves.md)
|
||
vms/ KubeVirt VirtualMachines (non-gating tier)
|
||
catalog/ ServiceDefinition entries (the onboarding surface) + render.py
|
||
schemas/ ServiceDefinition JSON Schema + renderer contract
|
||
clusters/ Flux entrypoints + dev/ and prod/ overlays
|
||
conformance/ Chainsaw behavior/conformance suite + harness + coverage report
|
||
ci/ the gate as scripts (lint · schema · Kyverno L0 · L1 render · decision)
|
||
.gitea/ Gitea Actions CI running that gate on an in-cluster runner
|
||
```
|
||
|
||
## Status
|
||
|
||
Roadmap phases **0–6 are delivered**, and **Phase 7 (Production Readiness) is OPEN and in
|
||
progress**. `clusters/prod` is no longer a design-only overlay: prod is the live cluster, serving the
|
||
apex `home.keithjackson.dev`. The dev cluster was **decommissioned on 2026-09-12** (full migration:
|
||
dev out, prod onto the apex — see the DECISIONS-LOG), so phases 0–6 are now exercised on prod
|
||
hardware rather than on Proxmox VMs.
|
||
|
||
**Phase 7 exit criteria** — *prod runs the same config via the prod overlay; DR proven; monitoring +
|
||
alerts live*:
|
||
|
||
| criterion | state |
|
||
|---|---|
|
||
| prod runs the prod overlay | **met** — 3× mini-PC bare metal, one Flux tree, prod overlay only |
|
||
| monitoring + alerts live | **met 2026-09-13** — see the note below; this was not true before that date |
|
||
| **DR proven** | **the open item** — R-STATE-03's only ledger proof was run on dev against MinIO |
|
||
|
||
The DR gap is specific, not vague. `conformance/runs/ledger.jsonl` holds proofs for `dr-restore`
|
||
(R-STATE-03) and `node-loss` (R-STATE-02) that were both recorded **on the dev cluster**, which no
|
||
longer exists, and the dr-restore proof was against **MinIO** rather than today's Cloudflare R2
|
||
target. `--status` therefore reports `PROVEN: 0`. Re-proving those two on prod is what closes Phase 7.
|
||
|
||
| Phase | Delivered |
|
||
|---|---|
|
||
| 0 Foundations | ServiceDefinition schema + renderer, Kyverno L0 gate, conformance harness, CI |
|
||
| 1 Cluster bring-up | 3-node k3s HA, embedded etcd, kube-vip VIP, Cilium, Flux, SOPS. Originally Proxmox VMs; **now 3× mini-PC bare metal** (`k3s-prod-1/2/3`, `192.168.2.10/.11/.12`, k3s v1.36.4+k3s1, Debian 13) |
|
||
| 2 Platform core | Cilium · Longhorn · cert-manager (LE-prod wildcard) · ExternalDNS · Gateway API · split-horizon CoreDNS |
|
||
| 3 Egress / VPN | ADR-0015 node-level OpenVPN split-tunnel + Cilium egress-gateway steering + host kill-switch |
|
||
| 4 Observability | kube-prometheus-stack · Loki · Alloy · Alertmanager. **Velero unblocked 2026-09-13** — backs up to Cloudflare R2 (OQ-BKP closed; the `velero-plugin-for-aws` tagging incompatibility needed a pinned plugin). Longhorn has **no** backup target by decision: volume data is protected by Velero + kopia to R2. image-automation authored but disabled |
|
||
| 5 IaaS | KubeVirt + CDI on real nested KVM; live-migratable RWX VM storage. The UniFi VM runs, but **UniFi OS Server is not yet installed in the guest** |
|
||
| 6 Identity + CI | Keycloak (Operator + CNPG) · oauth2-proxy forward-auth · Grafana OIDC · **kubectl OIDC SSO with group→RBAC** · in-cluster Gitea Actions merge gate |
|
||
| **7 Production readiness** | **IN PROGRESS.** Prod live on bare metal serving the apex; dev decommissioned; CNPG HA 3 instances with primaries spread one per node; NFS bulk media + legacy Nextcloud mounted; VPN egress on prod. Alerting made real 2026-09-13 (Longhorn, cert-manager, Flux and twelve app SLIs had all been unable to fire). **Open: DR proven on prod.** |
|
||
|
||
**Conformance: 51 / 51 requirements covered (100%)**, 0 gaps, 0 safety-critical gaps (closed
|
||
2026-09-13; `R-C2-01`/`R-C2-02` were the last two). Regenerate with
|
||
`./conformance/run.sh --coverage-only`; the full report is
|
||
`conformance/_artifacts/coverage/coverage.md`.
|
||
|
||
Coverage is **not** proof. A requirement is *covered* when an executable test exists for it, and
|
||
*PROVEN* only when a recorded run of that test is in `conformance/runs/ledger.jsonl` at the current
|
||
SHA on the cluster it claims. Read both numbers: `--coverage-only` for the first, `--status` for the
|
||
second.
|
||
|
||
The running narrative — decisions, root causes, incidents, follow-ups — is
|
||
[`DECISIONS-LOG.md`](DECISIONS-LOG.md). It, not this table, is the authoritative record.
|
||
|
||
## Working on this repo
|
||
|
||
Everything the gate needs is baked into the pinned dev container — **do not install the
|
||
toolchain on the host**:
|
||
|
||
```bash
|
||
infra/devcontainer/dev.sh build # once
|
||
infra/devcontainer/dev.sh run make -C ci gate # the same gate CI runs
|
||
infra/devcontainer/dev.sh # interactive shell (sops, flux, kubectl, tofu, ansible)
|
||
```
|
||
|
||
The container mounts the repo at `/work`, your age keyring, and the sibling spec repo read-only
|
||
so the spec-immutability check can actually run. See
|
||
[`infra/devcontainer/README.md`](infra/devcontainer/README.md).
|
||
|
||
A green run prints `MERGE GATE: PASS`. The L2/L3 conformance layers stay `SKIPPED` unless
|
||
`CLUSTER_AVAILABLE=true` — the static gate never pretends to have run them.
|