docs: bring the specification in line with the accepted implementation
The operator ruled the implementation repo's main (50c8719) the latest and accepted state. Sync the spec to it. Accepted changes are reflected: one bare-metal environment, TrueNAS bulk storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway with Cilium fail-closed, inbound WireGuard, per-app auth sidecars, Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025; ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended. Where the implementation has simply not met a requirement, the requirement stands and the shortfall is recorded in docs/16-known-gaps.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1ddc14b918
commit
250fb54154
43 files changed
+1672
-575
No files matched your search
+110
-50
@@ -1,6 +1,10 @@
|
||||
# 05 — GitOps & Delivery
|
||||
|
||||
Serves: **R-GITOPS-01..03**, **R-PLAT-02/04**, **R-ENV-02**. Related: [ADR-0004 (Flux)](../decisions/adr-0004-gitops-flux.md).
|
||||
Serves: **R-GITOPS-01..03**, **R-PLAT-02/04**, **R-ENV-02**, **R-TEST-03**. Related:
|
||||
[ADR-0004 (Flux)](../decisions/adr-0004-gitops-flux.md),
|
||||
[ADR-0023 (updates)](../decisions/adr-0023-automated-updates-renovate.md),
|
||||
[ADR-0024 (delivery authority)](../decisions/adr-0024-delivery-authority-ci-gate.md),
|
||||
[ADR-0025 (pm-agent)](../decisions/adr-0025-in-cluster-ai-program-manager.md).
|
||||
|
||||
## Model
|
||||
|
||||
@@ -8,35 +12,40 @@ Pull-based GitOps with **Flux CD**. The cluster continuously reconciles its stat
|
||||
the single source of truth; the cluster conforms to git, not the reverse (R-GITOPS-01). Drift is
|
||||
detected and corrected automatically (R-GITOPS-03).
|
||||
|
||||
## Repository layout (the implementation repo, created in a later phase)
|
||||
## Repository layout (the implementation repo)
|
||||
|
||||
> This spec repo (`home-network-k3s`) holds intent. The **implementation** repo(s) hold the
|
||||
> reconciled manifests. Proposed structure:
|
||||
> This spec repo (`home-network-k3s`) holds intent. The
|
||||
> [implementation repo](https://gitea.home.keithjackson.dev/Home-Network/home-network-k3s-impl)
|
||||
> holds the reconciled manifests, the gate and the tests. Its top-level areas:
|
||||
|
||||
```
|
||||
platform/ ← cluster-wide platform services (Flux-managed)
|
||||
cilium/ ← CNI, egress gateway, network baseline
|
||||
longhorn/ ← storage
|
||||
cert-manager/ ← TLS issuers + ClusterIssuer
|
||||
externaldns/ ← DNS automation
|
||||
gateway/ ← Gateway API + listeners/TLS
|
||||
monitoring/ ← kube-prometheus-stack, Loki, Alloy, Alertmanager
|
||||
velero/ ← backup/DR
|
||||
kubevirt/ ← KubeVirt + CDI
|
||||
image-automation/ ← Flux image update automation config
|
||||
apps/ ← application workloads, one dir per service
|
||||
plex/ sonarr/ radarr/ jackett/ seerr/ pihole/ nextcloud/
|
||||
minecraft/ unifi/ semaphore/ kasm/ gitea/ qbittorrent/
|
||||
catalog/ ← ServiceDefinition entries (the onboarding surface)
|
||||
catalog/ ← ServiceDefinition entries (the onboarding surface) + the renderer
|
||||
schemas/ ← ServiceDefinition JSON Schema
|
||||
apps/ ← application workloads by reconcile wave, plus VMs; rendered from
|
||||
catalog/ and committed (the render is checked against them)
|
||||
platform/ ← cluster-wide platform services (Flux-managed): CNI, storage, TLS, DNS,
|
||||
Gateway, public tunnel, observability, backup/DR, KubeVirt, identity,
|
||||
admission policy, image builder, delivery automation
|
||||
platform-config*/ ← platform configuration Flux applies after the platform tier, including
|
||||
the admission policies
|
||||
clusters/
|
||||
dev/ ← Flux entrypoint + dev overlay/vars
|
||||
prod/ ← Flux entrypoint + prod overlay/vars (design-only)
|
||||
base/ ← shared Flux entrypoints and reconcile ordering
|
||||
prod/ ← the live cluster's overlay and environment values
|
||||
bootstrap/ ← Flux, SOPS and Gitea bootstrap
|
||||
infra/ ← node provisioning as code; Gitea settings; dev toolbox; agent image
|
||||
ci/ .gitea/ ← the merge gate and the workflows that run it
|
||||
conformance/ ← requirement registry, behavior tests, harness, run ledger
|
||||
runbooks/ ← operator procedures (also served to the alert links)
|
||||
decisions/ ← dated implementation handoff log (distinct from this repo's ADRs)
|
||||
```
|
||||
|
||||
- **Kustomize overlays** (or equivalent) express environment differences (R-ENV-02). Base is shared;
|
||||
`clusters/dev` and `clusters/prod` supply scoped values only.
|
||||
- **Kustomize overlays** express environment differences (R-ENV-02). The base is shared; an
|
||||
environment overlay supplies scoped values only. `prod` is the one live environment; the dev
|
||||
environment was decommissioned in 2026-09 ([ADR-0017](../decisions/adr-0017-single-environment-bare-metal-production.md)).
|
||||
- **HelmReleases** (Flux) wrap upstream charts (Cilium, Longhorn, cert-manager, kube-prometheus-
|
||||
stack, etc.); app workloads use charts where good ones exist, otherwise plain manifests.
|
||||
- **Rendered output is committed.** `apps/` is generated from `catalog/`; the merge gate fails when
|
||||
the two disagree, so a ServiceDefinition is always the source of a rendered workload.
|
||||
|
||||
## The onboarding surface: ServiceDefinition (R-GITOPS-02)
|
||||
|
||||
@@ -48,14 +57,52 @@ platform renders the rest.
|
||||
|
||||
## Reconciliation flow
|
||||
|
||||
1. Operator opens a PR against the implementation repo (new/updated ServiceDefinition or platform
|
||||
change).
|
||||
2. CI validates (lint, policy checks, schema validation, dry-run/build).
|
||||
3. Merge to the tracked branch.
|
||||
4. Flux reconciles: applies manifests, reports status, corrects drift on an interval.
|
||||
1. A contributor opens a PR against the implementation repo (new/updated ServiceDefinition or
|
||||
platform change). Contributors are the operator, implementation agents, Renovate and pm-agent;
|
||||
all of them take the same path.
|
||||
2. The **merge gate** validates the change (below).
|
||||
3. Merge to `main`, the tracked branch.
|
||||
4. Flux reconciles from the platform's own Gitea: applies manifests, reports status, corrects drift
|
||||
on an interval.
|
||||
5. Health/rollout status surfaces in the C2 UI ([12](12-command-center-ui.md)) and alerts on failure
|
||||
([10](10-observability-and-alerting.md)).
|
||||
|
||||
Nothing reaches `main` by any other route: direct pushes are rejected for every account.
|
||||
|
||||
## The merge gate (R-TEST-03)
|
||||
|
||||
The implementation repo's `main` is protected by one required status check, the merge gate, which
|
||||
runs on every PR as a Gitea Actions job. **A red gate is unmergeable by everyone, administrators
|
||||
included.** The gate runs in the same pinned toolbox image locally and in CI (R-TEST-02) and
|
||||
establishes, in order: the adopted spec pin (R-TEST-06), the handoff log's format, lint, L0 schema +
|
||||
policy + repository invariants, render-equals-committed, L1 rendering, the sensitive-path approval
|
||||
policy, that the live Gitea settings match their declaration, and L2/L3 conformance
|
||||
([14](14-testing-and-conformance.md); see [known gaps](16-known-gaps.md)).
|
||||
|
||||
Its verdict is one of three: **pass**, **blocked**, or **undetermined**. A stage that did not run is
|
||||
never green, and no automation may read a non-answer as approval. The rule is that a capability the
|
||||
environment *declares* absent is named in the verdict, while a stage that *could not run where it
|
||||
must* blocks. Detail: `ci/README.md` in the implementation repo.
|
||||
|
||||
## Who approves ([ADR-0024](../decisions/adr-0024-delivery-authority-ci-gate.md))
|
||||
|
||||
**The CI gate is the approver.** A green gate is sufficient for an ordinary change; branch
|
||||
protection requires no approval count. A weak gate is a defect to fix, not a reason to add a human.
|
||||
|
||||
**One exception: a change to the gate cannot be gated by the gate.** A change touching the pipeline,
|
||||
the tests and harness, the Flux wiring, the admission policies, the SOPS recipients and secrets, the
|
||||
RBAC, or the delivery contract needs an approving review from someone other than its author. The
|
||||
path list is data, with a stated reason per entry (`infra/gitea/approval-policy.json` in the
|
||||
implementation repo), and is itself a sensitive path. This stage is fail-closed: if it cannot
|
||||
evaluate, it blocks.
|
||||
|
||||
**Attributed administrator override.** There is one human on this platform, and Gitea refuses
|
||||
self-approval, so an administrator may waive that review with an explicit, reasoned directive on the
|
||||
PR. Authority is re-checked live, a new commit invalidates it, and the overrider, time and reason
|
||||
are recorded in the gate output. For an administrator the exception above is therefore advisory, not
|
||||
structural; what it guarantees is an attributed record of each waiver. **Agents never post this
|
||||
directive to clear their own work**, whatever permission their account holds.
|
||||
|
||||
## Secrets in GitOps
|
||||
|
||||
Secrets are committed **encrypted** with **SOPS + age**; Flux decrypts them in-cluster at
|
||||
@@ -64,15 +111,32 @@ and [ADR-0007](../decisions/adr-0007-secrets-sops-age.md).
|
||||
|
||||
## Automated updates interplay (R-UPD-*)
|
||||
|
||||
GitOps and hands-off updates coexist:
|
||||
- **Container image tags** advance automatically via **Flux image-update-automation** driven by
|
||||
per-service **image policies** (semver range / stable channel) — the controller resolves and
|
||||
reconciles the new version with **no human-authored change** (R-UPD-02).
|
||||
- **Helm chart / manifest version bumps** are proposed by **Renovate** with auto-merge for allowed
|
||||
update classes.
|
||||
GitOps and hands-off updates coexist because the update bot is one more contributor to the same
|
||||
flow. **Renovate** runs on a schedule and proposes machine-authored PRs for container images, Helm
|
||||
charts and manifest versions; each passes the merge gate, and allowed update classes auto-merge on
|
||||
green (R-UPD-02). No in-cluster controller holds a credential to write to the repo that defines the
|
||||
cluster. Full detail is in [`11-automated-updates.md`](11-automated-updates.md) and
|
||||
[ADR-0023](../decisions/adr-0023-automated-updates-renovate.md).
|
||||
|
||||
Full detail and the "is an automated commit a GitOps change?" nuance is in
|
||||
[`11-automated-updates.md`](11-automated-updates.md) and [ADR-0009](../decisions/adr-0009-automated-image-updates.md).
|
||||
## Images are built by CI
|
||||
|
||||
Container images the platform itself produces (the toolbox, the agent runner, the conformance
|
||||
runner) are built, verified and published only by the pipeline, never from a workstation. A PR
|
||||
builds and verifies; a push to `main` publishes to the Gitea registry. References are pinned by
|
||||
digest, and Renovate carries each new digest into git as a PR. **No CI job may reach a privileged
|
||||
container daemon**: builds go to a rootless builder over mutually-authenticated TLS.
|
||||
|
||||
## Delivery automation: pm-agent ([ADR-0025](../decisions/adr-0025-in-cluster-ai-program-manager.md))
|
||||
|
||||
An in-cluster AI Program Manager reads labelled issues on the implementation repo, decides what
|
||||
should happen about each thread, and runs one worker agent per assignment to author a change, run
|
||||
the merge gate and open a PR. It is delivery tooling rather than a catalog service: it declares
|
||||
Kubernetes objects a ServiceDefinition cannot express, and it requires no `R-*` of its own.
|
||||
|
||||
It is contributor-shaped, never approver-shaped. It cannot push to `main`, it never merges, its
|
||||
account is not an administrator, and its workers hold no cluster credential. It is an implementation
|
||||
agent bound by [AGENTS.md](../AGENTS.md) and R-TEST-06: spec work is out of its reach and is
|
||||
declined and escalated.
|
||||
|
||||
## Bootstrap & recovery
|
||||
|
||||
@@ -80,25 +144,21 @@ Full detail and the "is an automated commit a GitOps change?" nuance is in
|
||||
- Because desired state is in git and data is on Longhorn/backups, a rebuilt cluster reconverges by
|
||||
re-bootstrapping Flux and restoring volumes ([04](04-storage-and-state.md)).
|
||||
|
||||
## Source-of-truth: local bootstrap → self-hosted Gitea
|
||||
## Source-of-truth: self-hosted Gitea
|
||||
|
||||
The platform hosts its own git, which creates a deliberate bootstrapping sequence
|
||||
([`../CONTRIBUTING.md`](../CONTRIBUTING.md)):
|
||||
The platform hosts its own git at `gitea.home.keithjackson.dev`
|
||||
([`../CONTRIBUTING.md`](../CONTRIBUTING.md)); Flux reconciles from it and Gitea Actions runs the
|
||||
merge gate. Standing the platform up from nothing follows a deliberate order:
|
||||
|
||||
1. **Bootstrap source.** Initially there is no remote git on this network (the prior `home-network`
|
||||
Gitea is gone from here). Development is local; the cluster is first reconciled from a
|
||||
**bootstrap source** (this local repo / an out-of-band source).
|
||||
2. **Gitea comes online early.** **Gitea is one of the first services onboarded** ([13](13-service-catalog.md))
|
||||
so the platform gains its own git host at `gitea.home.keithjackson.dev`.
|
||||
3. **Cut Flux over to Gitea.** Once Gitea is stable, Flux's `GitRepository` source is migrated from
|
||||
the bootstrap source to Gitea; the platform then reconciles from its own git.
|
||||
4. **CI/CD moves to Gitea Actions.** The Kyverno L0 policy gate and the trigger for the in-cluster
|
||||
Chainsaw conformance Job ([14](14-testing-and-conformance.md)) run as Gitea Actions pipelines on
|
||||
push/PR; PRs become the enforced merge gate into `main`.
|
||||
1. **Bootstrap source.** The cluster is first reconciled from an out-of-band copy of the repo.
|
||||
2. **Gitea comes online early.** Gitea is one of the first services onboarded
|
||||
([13](13-service-catalog.md)).
|
||||
3. **Cut Flux over to Gitea**, so the platform reconciles from its own git.
|
||||
4. **CI/CD runs in Gitea Actions**, with PRs as the enforced merge gate into `main`.
|
||||
|
||||
**Circularity & DR.** Gitea runs *on* the cluster Flux reconciles *from* Gitea. Mitigations:
|
||||
- Keep an **off-cluster mirror** of the platform repo so a source-of-truth exists that outlives the
|
||||
cluster.
|
||||
cluster (see [known gaps](16-known-gaps.md)).
|
||||
- Treat **Gitea as DR-critical**: its repos/DB are backed up off-cluster ([04](04-storage-and-state.md)),
|
||||
so a cluster loss never loses the git history needed to rebuild the platform.
|
||||
|
||||
@@ -112,4 +172,4 @@ The platform hosts its own git, which creates a deliberate bootstrapping sequenc
|
||||
- Adding a ServiceDefinition PR results in a fully wired service (compute, storage, policy, TLS, DNS,
|
||||
monitoring) with no manual steps beyond the merge.
|
||||
- Manually mutating a live resource is auto-reverted by Flux (drift correction proven).
|
||||
- The same base config renders correctly for both dev and prod overlays.
|
||||
- The base config renders correctly under the environment overlay, which supplies scoped values only.
|
||||
Reference in new issue
Block a user