Skip to content

feat: reliability hardening (auth, self-monitoring, alert delivery, backups)#4

Merged
simonvanlierde merged 14 commits into
mainfrom
feat/reliability-hardening
Jul 4, 2026
Merged

feat: reliability hardening (auth, self-monitoring, alert delivery, backups)#4
simonvanlierde merged 14 commits into
mainfrom
feat/reliability-hardening

Conversation

@simonvanlierde

Copy link
Copy Markdown
Contributor

Features

Ingestion now requires a token: the OTLP endpoint is public behind the tunnel, so anonymous senders get a 401. just up-tunnel refuses to start with default credentials.

  • The stack watches its own host: node-exporter, a disk >80% alert (Loki and Tempo can't cap their own size, so this is the real limit), a Stack Health dashboard, and a size cap on Prometheus storage.
  • Alerts reach a human: Alertmanager delivers to any webhook, and an always-firing Watchdog pings a dead man's switch every 5 minutes, so even "the whole host died" gets noticed.
  • just backup / just restore: tested by wiping every volume and restoring. Restore refuses to touch anything until the backup file is proven readable.
  • Runbook + onboarding docs: how to operate the stack, and copy-paste templates for new projects to send telemetry.
  • Cloudflare as code: the tunnel, routes, and DNS live in infra/ (OpenTofu) instead of dashboard click-ops.

Fixes

  • a data-loss path in restore
  • secrets leaking into backup tarballs
  • a Tempo 3.0 bump that had silently broken tracing

Accurate diagram (Loki named, tunnel fronts both Grafana and OTLP
ingestion, Tempo span-metrics edge, theme-neutral styling) under a brief
Architecture section that absorbs the Design-decisions prose and links
ADR 0001.
The OTLP hostnames are public behind the tunnel; without auth anyone who
finds them can inject telemetry. bearertokenauth on both receivers,
token via OTLP_AUTH_TOKEN (safe local default keeps 'just demo' one
command). Verified: no/wrong token → 401, correct token → 200, demo
still populates Grafana. Also syncs justfile validator image pins with
the bumped compose versions.
…orage

node-exporter (pinned) + a HostDiskSpaceLow alert at 80% — Loki and
Tempo have no total-size cap, so the disk alert is the storage backstop;
Prometheus additionally gets --storage.tsdb.retention.size=15GB. New
provisioned Stack Health dashboard: host disk/CPU/memory + collector
ingest and export-failure rates. Collector 0.155.0 dropped the _total
suffix on internal telemetry; alert and dashboard queries updated and
verified against live data.
Alertmanager (pinned) routes all alerts to ALERT_WEBHOOK_URL and an
always-firing Watchdog to HEARTBEAT_URL every 5m — wire the latter to a
dead man's switch to catch the one failure the host can't report: its
own death. URLs are injected via url_file at start (Alertmanager has no
env expansion); unset means logged-and-harmless delivery errors, so the
stack still runs with zero config. amtool check-config joins just
check. Verified live: Watchdog delivered to a local webhook sink.
Tempo 3.0.2 (bumped in passing) crash-loops on the 2.x config: 3.0 is a
re-architecture that removes the ingester/compactor blocks and changes
the metrics-generator. Pin back to 2.10.4 (verified working) and tell
Dependabot to skip Tempo majors so the migration happens deliberately.
The OtelExportFailures alert flagged the outage — working as designed.
Crash-consistent tarball of all stateful volumes (services paused for
the seconds the copy takes); restore wipes and repopulates them.
Exercised end-to-end: backup → docker compose down -v → restore → a
pre-backup Grafana annotation survived.
RUNBOOK: service-down triage, disk-pressure response with the full
retention/size-limit table, backup/restore, secret rotation, image
upgrades. ONBOARDING: endpoint + conventions plus verified copy-paste
templates for zero-code Python/FastAPI, plain OTLP env vars, the Loki
Docker driver, and Grafana Alloy file logs (Promtail is EOL). README
slims: telemetry section now points at ONBOARDING, alerting section
reflects Alertmanager + Watchdog.
The tunnel, its two ingress routes (grafana, otlp), and DNS were the
only stack config living outside git. infra/ declares them (provider
v5, validated); the tunnel-token output feeds .env for just up-tunnel.
Local state on purpose — one host, one operator. tofu fmt joins just
check; full validate is 'just infra-validate' (downloads the provider,
so kept out of the CI hot path).
Rendered from the live stack; it happens to capture today's real Tempo
outage and the backup/restore drill, which makes it a better runbook
illustration than a clean one.
- restore: validate the tarball exists and is readable BEFORE stopping
  the stack and wiping volumes; absolute paths now work (a typo'd
  filename previously destroyed all data)
- backup: always unpause even when the tar step fails; refuse to run
  under COMPOSE_PROJECT_NAME (hardcoded volume names would silently back
  up empty volumes); tarballs now 0600 (they contain secrets); service/
  volume lists deduplicated into variables
- demo-down: remove only the demo services instead of downing the
  entire stack
- up-tunnel: refuse to expose the stack with the default OTLP token or
  the documented Grafana password
- check: promtool/otelcol/amtool images are read from compose.yml so
  Dependabot bumps can't drift from the validators; dashboards JSON
  check runs in a jq container (recipe claim 'no host installs' is now
  true); yamllint ignores .git/, backups/, infra/.terraform/
- smoke: also fail on crash-looping/exited services, not just Grafana
  health

All guards exercised live: typo'd restore fails before wipe, corrupt
tarball fails before down, up-tunnel rejects defaults, demo-down leaves
the core stack running.
- drop platform: linux/amd64 on the collector: the image is multi-arch,
  and the pin crash-loops arm64 Linux hosts / forces emulation on Apple
  Silicon (verified: native arm64 now runs)
- Alertmanager writes webhook/heartbeat URLs to a tmpfs instead of the
  persistent volume, keeping secrets out of backup tarballs
- OTLP_AUTH_TOKEN loses its weak 'local-dev-token' compose fallback:
  :?-guarded in compose.yml, single source of truth in .env; demo
  overlay no longer duplicates the literal
- remove dead config: Grafana feature toggles absent from v13's
  registry, Tempo's unused OTLP HTTP receiver, redundant demo exporter
  env vars (distro defaults all three to otlp)
- demo services restart: on-failure so a forgotten demo doesn't survive
  reboots; in-file logging anchor replaces duplicated blocks
- infra: DNS record names must be FQDNs under cloudflare provider v5
  (terraform-provider-cloudflare#5620) — fixes perpetual plan drift
- fix stale stack.yaml header (claimed 'No Alertmanager') and stale
  tunnel overlay header (claimed click-ops config)
Demo package pins move to requirements.txt with a Dependabot pip
ecosystem entry (previously nothing watched them), and the CI workflow
gets an explicit read-only permissions block.
The tunnel only routes HTTPS to the collector's OTLP HTTP receiver —
the endpoint table and diagram claimed a public gRPC :4317 path that
doesn't exist. The Loki push templates now say the stack exposes no
push hostname by default. Runbook: backup tarballs are 0600 and carry
secrets; long backups (>5 min) drop telemetry; rotating the OTLP token
requires re-running just demo if the overlay is up.
@simonvanlierde
simonvanlierde merged commit ca50105 into main Jul 4, 2026
4 checks passed
@simonvanlierde
simonvanlierde deleted the feat/reliability-hardening branch July 4, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant