obmp-docker/docs/PORTABILITY-FINDINGS.md
Sam 05f723556e docs+tooling: land the deploy bundle docs, portproxy script, checkout pinning
deploy.sh referenced DEPLOYMENT-TYPES.md and router-bmp-config.md, which were
never committed (they lived only in a chat session's outputs). Land them under
docs/ along with PORTABILITY-FINDINGS.md covering all 12 greenfield findings,
including the two from today's deploy (chmod-777-vs-psql, telegraf docker API)
and the open router-side gap (cml tooling still targets HOST_IP:5000).

- scripts/wsl-portproxy.ps1: idempotent elevated-PS helper for the Windows
  portproxy + firewall rules; auto-detects the current WSL IP. Replaces the
  copy-paste netsh block as the primary path (raw commands kept as fallback).
- deploy.sh: plan now prints the branch @ commit (+dirty marker) so every
  deploy records exactly what it ran from; doc references point at docs/.
- README: greenfield quickstart with pinned-checkout guidance; warning on the
  manual chmod path that breaks existing Postgres trees (finding 10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:02:36 -07:00

5.6 KiB

Portability findings — what breaks on a greenfield deploy

Every artifact hit while standing this stack up on a fresh host (WSL2 NWE-LT02, Ubuntu 24.04, native docker-ce 29.x — 2026-07), ordered roughly as encountered. Each one is something a clean deploy has to survive; most are now handled automatically by deploy.sh/setup.sh. Kept as a checklist for the next new host and as the rationale for the deploy tooling.

1. Bind-mounted volumes need pre-existing source dirs

docker-compose.yml binds ${OBMP_DATA_ROOT}/postgres/{data,ts} with type:none,o:bind. Docker will not create these; if absent, Postgres fails with no such file or directory. setup.sh creates them — the error only appears when setup.sh wasn't run on the target. deploy.sh double-checks the dirs exist after setup.sh.

2. Stale HOST_IP survives a copied .env

A .env copied from another host carries that host's HOST_IP; setup.sh validation only checks non-empty/not-"changeme", so the stale IP passes and gets rendered into gobgpd.conf and the Grafana root URL. This is the core reason deploy.sh exists — it reconciles HOST_IP (and warns when the .env value isn't a local address) before setup.sh renders anything.

3. A populated .env is not a deployed host

The same copied .env had late-stage artifacts (Authelia secrets) while early-stage host state (Postgres dirs) was missing — proof that setup.sh completed on a different host. Run setup.sh per target, always.

4. WSL: HOST_IP is the NAT address; routers can't reach it

hostname -I in WSL returns the VM's NAT address — it changes on wsl --shutdown and physical routers cannot reach it. Routers must target the Windows LAN IP, forwarded into WSL via netsh portproxy (scripts/wsl-portproxy.ps1). deploy.sh separates "router-facing IP:port" from HOST_IP and auto-detects the Windows LAN IP via powershell.exe interop.

5. Cold-start contention masquerades as component bugs

Bringing the whole stack up at once crash-looped Kafka on a preflight "writable" check that was actually resource contention, not permissions. deploy.sh stages the bring-up: infra (zookeeper/kafka) -> psql -> core -> feeders.

6. Size for BMP burst, not steady state

Generic docs say the collector is light — true, and misleading: the store saturates under burst (full table x monitored sessions, Postgres write amplification). See docs/DEPLOYMENT-TYPES.md for the full model.

7. Ports 5000 and 3000 are crowded

The collector's 5000 and Grafana's 3000 are commonly already occupied. deploy.sh runs a port-collision preflight before touching anything. The router-facing BMP port default moved 5000 -> 1790 (the IANA BMP port); the collector still listens on 5000 inside the container.

8. Docker Desktop breaks host bind mounts on WSL (the big one)

Symptom: Postgres mount fails no such file or directory on a path ls shows exists. Cause: Docker Desktop runs the daemon in its own WSL distro (docker-desktop VM), so host bind mounts resolve against that filesystem, not the distro you ran compose from. No mkdir fixes it. Fix: native docker-in-WSL — disable Desktop's WSL integration, install docker-ce, systemctl enable --now docker, and confirm docker info | grep 'Operating System' does not say Docker Desktop. deploy.sh detects Docker Desktop and warns/refuses.

8b. Desktop residue splits the mount namespace

After installing native docker, the CLI couldn't reach /run/docker.sock even though dockerd held it — a stale mount-namespace split left by Desktop. wsl --shutdown and reopening the distro cleared it.

9. Grafana customizations must live in the repo, not grafana.db

Dashboards hand-built in the UI exist only in that host's grafana.db and vanish on a greenfield deploy. All dashboards are now committed as JSON under obmp-grafana/dashboards/ and setup.sh syncs both provisioning YAML and dashboard JSONs to the data root. If you edit a dashboard in the UI, export and commit the JSON.

10. Blanket chmod -R 777 on the data root breaks Postgres re-deploys

setup.sh's lab-permissive chmod over an existing data tree made psql_server.key world-accessible; Postgres refuses to boot (FATAL: private key file ... has group or world access) and the whole core cascades down. Fresh installs never hit it (the key is generated after the chmod) — it is strictly a re-deploy bug. setup.sh now skips postgres/ (the image entrypoint owns those perms) and re-tightens the key/cert to 0600 if a previous run already clobbered them.

11. docker-ce 29 rejects Docker API < 1.40 — old pinned clients break

telegraf 1.28's [[inputs.docker]] hardcodes Docker API version 1.24, which docker-ce >= 29 refuses (client version 1.24 is too old). Every per-container stack metric silently never reached InfluxDB; the stack dashboards sat empty while disk/postgresql_* inputs worked fine. DOCKER_API_VERSION env is ignored (version pinned in code) — the fix is telegraf >= 1.30, which negotiates the API version (telegraf/Dockerfile now pins 1.33). Watch for the same failure in any other tooling that talks to the daemon with an old vendored client.

12. OPEN: router-side automation still targets the old lab's values

cml/proxmox_bmp_config.py sends routers at HOST_IP port 5000, and cml/xrd-node-definition.yaml bakes in 10.40.40.202:5000 — both correct on the old native-Linux dev lab, wrong on a WSL host (finding 4: routers must target the router-facing address, and the port default is now 1790). Fold ROUTER_FACING_IP/ROUTER_FACING_PORT from .env into that tooling when the 5000-vs-1790 decision lands. See docs/router-bmp-config.md.