docs: record deployment checks

This commit is contained in:
Alex 2026-08-24 23:18:16 +02:00
parent fd7608227d
commit 3547d0bf3a
3 changed files with 331 additions and 0 deletions

68
docs/evidence.md Normal file
View file

@ -0,0 +1,68 @@
# Final project verification evidence
Captured on 2026-08-24 from the disposable QEMU and k3d integration labs. The
commands below expose no credentials and can be rerun before the presentation
to refresh screenshots or terminal recordings.
## Two-node Fedora cluster
`scripts/provision/lab.sh check` passed after provisioning, host reboot, node 2
disconnect/reconnect, and complete deletion/recreation of node 2. At the final
check both Fedora 44 machines ran k3s `v1.33.4+k3s1` and reported `Ready`:
```text
nereus-node1 Ready control-plane,master 192.168.122.10
nereus-node2 Ready <none> 192.168.122.11
```
The node-replacement test found and fixed two recovery requirements: remove the
replacement address from the private SSH known-hosts file, and delete the old
Kubernetes node identity before the new agent joins. The PostgreSQL local-path
volume was correctly treated as lost with the deleted VM, recreated empty, and
reseeded by the load generator.
## Live application
The final namespace check showed three ready API pods, one ready load-generator
pod, and one ready PostgreSQL pod. The same-origin Mini PC route returned:
```text
GET / 200
GET /healthz 200
GET /readyz 200
X-Nereus-Version 0.1.0
GET /api/v1/buoys 42 records
```
The static dashboard stayed available while the cluster API was unavailable
during node recovery, then recovered without a page reload.
## Container evidence
The locally inspected `nereus-api:0.1.0` image is 7,815,732 bytes and declares
numeric non-root user `65532`. The container verification also passed with a
read-only root filesystem.
## Rollback evidence
The local k3d lab was destroyed and recreated from scratch. Re-running
`scripts/k3d/lab.sh up` reconciled the running cluster successfully. The proof
produced these AnalysisRun results:
```text
probe-746bbb94df-2-pre Successful vector(0.0)
probe-556d5b659b-3-pre Failed vector(1.0)
```
After the failed analysis, the active Service selector remained on the healthy
revision `746bbb94df`.
## Presentation commands
```bash
scripts/provision/lab.sh check
curl -i https://nereus.fiwlabs.dev/healthz
curl -i https://nereus.fiwlabs.dev/readyz
curl -fsS https://nereus.fiwlabs.dev/api/v1/buoys | jq length
scripts/k3d/lab.sh prove
```

31
docs/mini-pc.md Normal file
View file

@ -0,0 +1,31 @@
# Mini PC web stack
The Mini PC stack joins the host's existing Traefik `proxy` network and proxies
the frozen API paths through nginx. It does not run its own Traefik instance,
PostgreSQL, the API, or the load generator.
Export the following values in the operator shell or service manager:
```text
WEB_HOST public dashboard hostname; defaults to nereus.fiwlabs.dev
CLUSTER_API_UPSTREAM cluster address and port, without a URL scheme
WEB_ASSET_PATH asset path relative to the build context; defaults to apps/web
```
Do not put these values in a repository `.env` file. Start the isolated stack
from the repository root:
```sh
docker compose -f build/compose.mini.yaml up -d --build
```
The Mini PC keeps the static assets directly in `~/Server/web/nereus`. Copy the
three build files into its `build/` directory, set `WEB_ASSET_PATH=.` when
building there, and join the existing external `proxy` network. The existing
Traefik instance supplies the `websecure` entrypoint and `porkbun` certificate
resolver; do not start a second proxy on ports 80 and 443.
When the cluster is unavailable, nginx continues serving the dashboard and
returns a gateway failure only for `/api/v1/*`, `/healthz`, and `/readyz`. The
browser keeps polling those relative paths and recovers when the cluster is
reachable again.

232
docs/roadmap.md Normal file
View file

@ -0,0 +1,232 @@
# Nereus delivery roadmap
Updated: 2026-08-24
`[x]` means locally verified. `[~]` means implemented but awaiting a real
integration environment. `[ ]` means not implemented. `[H]` is human-owned and
must not be edited by agents.
Update this file in the same change that completes or materially changes a
task. Mark a task `[x]` only after its stated verification passes.
## API service
- [x] Create the Go 1.26 module and dependency lock file.
- [x] Create the idempotent `buoys` table migration.
- [x] Create the idempotent `readings` table migration.
- [x] Create `idx_readings_buoy_time` idempotently.
- [x] Parse `PORT`, `CHAOS_ERROR_RATE`, and `LOG_LEVEL`.
- [x] Preserve the build-injected `APP_VERSION` value.
- [x] Require `DATABASE_URL` without logging it.
- [x] Start HTTP without waiting for PostgreSQL.
- [x] Retry migrations while PostgreSQL is unavailable.
- [x] Keep `/healthz` independent of PostgreSQL.
- [x] Require a reachable, migrated database for `/readyz`.
- [x] Implement `GET /api/v1/buoys`.
- [x] Implement `POST /api/v1/buoys`.
- [x] Implement `GET /api/v1/buoys/{id}`.
- [x] Implement `DELETE /api/v1/buoys/{id}`.
- [x] Implement filtered, paginated `GET /api/v1/readings`.
- [x] Implement `POST /api/v1/readings`.
- [x] Implement the real grouped aggregate query.
- [x] Bound JSON request bodies and return JSON errors.
- [x] Restrict chaos injection to `/api/v1/*`.
- [x] Expose all five required Prometheus metrics.
- [x] Use chi route templates for the HTTP `path` label.
- [x] Enable OTLP/gRPC tracing only when configured.
- [x] Create HTTP spans and child database spans.
- [x] Emit structured request logs containing `trace_id`.
- [x] Add table-driven handler tests without a live database.
- [x] Test chaos and unaffected health behavior.
- [x] Add a `testing.Short()`-guarded PostgreSQL integration test.
- [x] Pass `go test -short ./...`.
- [x] Pass `go vet ./...`.
- [x] Build with `CGO_ENABLED=0`.
- [x] Run the integration test against PostgreSQL 17.
- [x] Test database failure behavior on every data endpoint.
- [x] Assert exact metric names and route-template labels in tests.
- [x] Test malformed and oversized JSON request bodies.
- [ ] Run `golangci-lint run` with the CI-selected version.
## Load generator
- [x] Create the standalone Go 1.26 module.
- [x] Require `TARGET_URL` and default `RPS` to 5.
- [x] Reuse existing buoys or seed 42 validated offshore buoys.
- [x] Generate the 60/25/15 request mix.
- [x] Generate plausible synthetic reading values.
- [x] Count HTTP statuses and network errors.
- [x] Log a JSON status summary every ten seconds.
- [x] Continue traffic while requests and seeding fail.
- [x] Retry seeding without blocking read traffic.
- [x] Handle SIGTERM and SIGINT.
- [x] Keep the implementation below 200 lines.
- [x] Pass `go vet` and a static build.
- [x] Test request weighting deterministically.
- [x] Test seeding against an empty `httptest` API.
- [x] Test continued traffic through repeated HTTP 500 responses.
- [x] Run against the real API for at least five minutes.
- [x] Confirm Prometheus receives continuous request samples.
## Container build and local Compose
- [x] Create the API builder stage from `golang:1.26-alpine`.
- [x] Cache dependencies before copying API source.
- [x] Build with `CGO_ENABLED=0` and `-ldflags "-s -w"`.
- [x] Inject `APP_VERSION` with `-X`.
- [x] Copy only the API binary into `gcr.io/distroless/static:nonroot`.
- [x] Create the load-generator multi-stage image.
- [x] Use `gcr.io/distroless/static:nonroot` for the load generator.
- [x] Add the required `.dockerignore` exclusions.
- [x] Confirm both images run non-root and read-only.
- [x] Confirm the API image is below 25 MB (7,815,877 bytes locally).
- [x] Add API, PostgreSQL 17, and OTEL Collector to local Compose.
- [x] Add the PostgreSQL healthcheck.
- [x] Make the API wait on healthy PostgreSQL in Compose.
- [x] Verify `docker compose up` reaches API readiness.
- [x] Keep Mini PC and local-development Compose files separate.
## Static web dashboard
- [x] Create the no-build static HTML entry point.
- [x] Create the dark teal/navy glass visual system.
- [x] Replace decorative background waves with an operational chart grid.
- [x] Keep version, health, and error panels visible together.
- [x] Show the build-injected API version from the existing health response header.
- [x] Poll `/healthz` every two seconds.
- [x] Poll `/readyz` every two seconds.
- [x] Show each health indicator's last transition timestamp.
- [x] Count non-2xx `/api/v1/*` browser responses.
- [x] Calculate the rolling 60-second HTTP error rate.
- [x] Distinguish network-offline events from HTTP errors.
- [x] Preserve and grey the last known telemetry values offline.
- [x] Recover automatically when the API returns.
- [x] Add a fully local Natural Earth buoy map and pulsing markers.
- [x] Add animated reading charts.
- [x] Keep all API calls same-origin and relative.
- [x] Remove all frontend runtime dependencies on external hosts.
- [x] Verify all panels fit at 1280×720 without page scrolling.
- [x] Create the unprivileged nginx image.
- [x] Create the separate Mini PC Traefik Compose file.
- [x] Test the finished offline/degraded rendering without API connectivity.
- [x] Test the rendered dashboard while the API returns HTTP 500 responses.
## Observability
- [x] Receive OTLP/gRPC traces in the Collector.
- [x] Export traces from the Collector to Tempo.
- [~] Ship structured Kubernetes pod logs to Loki with the cluster Collector.
- [x] Start Loki locally and verify its Grafana data source readiness.
- [x] Configure Prometheus to scrape `/metrics`.
- [x] Add request-rate, error-rate, and latency panels.
- [x] Add database-operation latency panels.
- [x] Add readings-ingested and active-buoy panels.
- [x] Add a deployed-version dashboard variable.
- [~] Correlate logs and traces using `trace_id` after cluster log ingestion exists.
- [x] Add API error-rate, latency, readiness, and scrape alerts.
- [x] Load the dashboard and evaluate all six PromQL queries.
- [x] Trigger chaos and distinguish the failing version in Grafana.
## Fedora 44 host provisioning
- [x] Separate node 1 and node 2 inventory groups.
- [x] Pin the project k3s version.
- [x] Reject managed hosts that are not Fedora 44.
- [x] Install common host dependencies.
- [x] Enable firewalld.
- [x] Trust the configured pod and service CIDRs.
- [x] Open VXLAN port 8472/UDP between nodes.
- [x] Open kubelet port 10250/TCP between nodes.
- [x] Open API server port 6443/TCP on node 1.
- [x] Install node 1 as the k3s server.
- [x] Install node 2 as a k3s agent.
- [x] Keep the cluster token out of repository files and output.
- [x] Enable both k3s systemd services.
- [x] Wait for every Kubernetes node to become Ready.
- [x] Add the equivalent manual configuration checklist.
- [x] Document Terraform-to-Ansible inventory handoff.
- [x] Keep libvirt addresses, bridge rules, and VM network profiles out of the reusable Ansible roles.
- [x] Run the playbook twice on two clean Fedora 44 QEMU machines.
- [x] Confirm the second run reports no unintended changes (`changed=0`).
- [x] Restrict firewall sources to actual node/operator networks.
- [x] Confirm SELinux remains enforcing on both QEMU machines.
- [x] Reboot both QEMU machines and confirm automatic recovery.
- [x] Disconnect and reconnect node 2, then confirm it rejoins.
- [x] Record tested minimum CPU, memory, and disk requirements.
## One-command QEMU lab
- [x] Check QEMU, libvirt, cloud-utils, Ansible, SSH, ping, and KVM.
- [x] Install missing dependencies on a Fedora workstation.
- [x] Enable libvirt and its default network.
- [x] Discover and cache the Fedora 44 x86_64 cloud image.
- [x] Keep SSH and VM state outside the repository.
- [x] Generate cloud-init media for both nodes.
- [x] Create reusable copy-on-write disks.
- [x] Create each VM with two vCPUs and 3 GiB RAM.
- [x] Assign deterministic addresses to both local VMs.
- [x] Wait for ICMP ping and SSH on both nodes.
- [x] Generate the Ansible inventory automatically.
- [x] Configure both nodes automatically.
- [x] Check Ansible reachability, k3s files, and binaries.
- [x] Check firewalld, k3s services, ports 6443 and 10250.
- [x] Check Kubernetes node readiness.
- [x] Add `check`, `stop`, and explicit `destroy` actions.
- [x] Execute `lab.sh up` with working `/dev/kvm` and pass the full verification.
- [x] Reboot both local VMs and pass the bounded post-reboot service and node checks.
- [x] Execute `lab.sh check` after a workstation reboot.
- [x] Test recovery from an interrupted image download and validate the resumed QCOW2.
- [x] Test recovery after node 2 is deleted manually and rejoin it with a fresh identity.
- [ ] Test on a workstation with no dependencies installed.
- [x] Decide whether an aarch64 image path is required; keep the lab x86_64-only.
- [ ] After final sign-off, destroy the QEMU lab and remove packages installed only for it with Pacman's dependency-aware cleanup.
## Local k3d rollback mechanism
- [x] Define one local server and two local agent nodes.
- [x] Map Traefik to host ports 8080 and 8443.
- [x] Add lean kube-prometheus-stack values.
- [x] Add the synthetic Argo Rollouts analysis harness.
- [x] Add an idempotent `up/check/destroy` command and verify fresh creation and reconciliation.
- [x] Install Argo Rollouts.
- [x] Install kube-prometheus-stack with the local values.
- [x] Apply the synthetic harness.
- [x] Prove `vector(0.0)` permits promotion.
- [x] Change the test value to `vector(1.0)`.
- [x] Prove failed analysis aborts promotion with `vector(0.9)`.
- [x] Tear down and recreate the test from scratch.
## Human-owned deployment and infrastructure
- [H] Define infrastructure modules under `terraform/`.
- [H] Export provisioned addresses for Ansible inventory.
- [H] Create Kubernetes base resources and overlays. Agent-authored 2026-08-21 at the owner's request; builds and server dry-runs pass, not applied.
- [H] Create API and load-generator workloads. Agent-authored 2026-08-21; not applied.
- [H] Create active and preview Services. Agent-authored 2026-08-21; not applied.
- [H] Create the Prometheus ServiceMonitor. Agent-authored 2026-08-21; not applied, scrape not confirmed.
- [H] Create the real error-rate AnalysisTemplate. Agent-authored 2026-08-21; PromQL never evaluated against real series.
- [H] Configure blue-green promotion and automated rollback. Agent-authored 2026-08-21; promotion and abort paths untested with the real API.
- [H] Maintain Forgejo Actions and GitHub mirror workflows.
- [H] Create sealed secrets from off-repository plaintext inputs.
## End-to-end acceptance
- [ ] Provision two clean Fedora hosts.
- [ ] Reboot both hosts before platform installation.
- [x] Install the cluster platform components.
- [x] Deploy PostgreSQL and wait for health.
- [x] Verify API liveness before database readiness.
- [x] Verify readiness after migrations complete.
- [x] Deploy the load generator and observe continuous samples.
- [x] Send one request to every API endpoint in the local Compose stack.
- [x] Verify every required metric and exact label name locally.
- [x] Verify an HTTP trace contains a child database span.
- [x] Correlate one stdout request log and Tempo trace by `trace_id`.
- [x] Promote a healthy preview through analysis.
- [x] Deploy a preview with a nonzero chaos rate.
- [x] Observe preview errors in Prometheus.
- [x] Confirm analysis aborts the unhealthy promotion.
- [x] Confirm the active Service remains on the healthy version.
- [x] Confirm load generation continues throughout failure.
- [x] Confirm the Mini PC page remains usable with the cluster off.
- [x] Capture reproducible, non-secret evidence needed for the final project report.