diff --git a/services/fleet-prometheus/README.md b/services/fleet-prometheus/README.md index c1d3d3a..9dbf920 100644 --- a/services/fleet-prometheus/README.md +++ b/services/fleet-prometheus/README.md @@ -47,7 +47,34 @@ TSDB lives in the named volume `fleet-prometheus_tsdb` → `/prometheus`. ## Configuration - `prometheus.yml` — committed, **secret-free**. Global config + two scrape jobs. -- `env.example` — no secrets / no required env (kept only for layout parity). +- `env.example` → copy to `.env` (gitignored). Holds `TAILSCALE_BIND_IP`, the + VPS Tailscale interface IP the listen socket is bound to. + +### Deploy — `.env` is mandatory + +The port is published as `${TAILSCALE_BIND_IP}:9090:9090` so the socket is bound +**only** to the Tailscale interface, never `0.0.0.0`. Compose auto-loads a +co-located `services/fleet-prometheus/.env`, but if that file is **missing** the +variable resolves to a blank string and Compose **silently binds to 0.0.0.0** +(publicly reachable on the Hetzner IP) — it only warns, it does not fail. + +Therefore, before `up -d`: + +1. Create `services/fleet-prometheus/.env` from `env.example` with the real + `TAILSCALE_BIND_IP` (verify with `tailscale ip -4` on the VPS). +2. Deploy with the env file explicit, to be safe: + + ```bash + docker compose -f services/fleet-prometheus/docker-compose.yml \ + --env-file services/fleet-prometheus/.env up -d + ``` + +3. Confirm the bind is Tailscale-only (must NOT show `0.0.0.0`): + + ```bash + docker compose -f services/fleet-prometheus/docker-compose.yml \ + --env-file services/fleet-prometheus/.env config | grep host_ip + ``` ### Scrape targets (scaffold) diff --git a/services/fleet-prometheus/docker-compose.yml b/services/fleet-prometheus/docker-compose.yml index 7a404b4..3c5a138 100644 --- a/services/fleet-prometheus/docker-compose.yml +++ b/services/fleet-prometheus/docker-compose.yml @@ -20,11 +20,14 @@ services: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - fleet_prometheus_tsdb:/prometheus ports: - # tailscale-internal: exposed exactly like control-plane (plain published - # HOST:CONTAINER mapping, no public npm reverse-proxy entry). Reachability - # is constrained to the Tailscale mesh at the VPS firewall layer, same as - # control-plane's 18180. Do NOT add this to npm / public DNS. - - "9090:9090" + # tailscale-internal: the listen socket is bound ONLY to the VPS Tailscale + # interface IP (TAILSCALE_BIND_IP), never 0.0.0.0. The port therefore does + # not exist on the public Hetzner IP at all — reachability is enforced in + # the bind itself (defense-in-depth), not solely by the host firewall. + # brain-watchdog on PIHA reaches this over the VPS Tailscale IP. + # Requires --env-file services/fleet-prometheus/.env at deploy (see README). + # Do NOT add this to npm / public DNS. + - "${TAILSCALE_BIND_IP}:9090:9090" # node_exporter on the VPS runs with network_mode: host, so it listens on # the host's :9100. This bridged container reaches it via host-gateway. extra_hosts: diff --git a/services/fleet-prometheus/env.example b/services/fleet-prometheus/env.example index 286f42f..0a26b71 100644 --- a/services/fleet-prometheus/env.example +++ b/services/fleet-prometheus/env.example @@ -1,8 +1,10 @@ -# fleet-prometheus has NO secrets and NO required environment. +# fleet-prometheus has NO secrets, but it does require one host-local value for +# the Tailscale-only port bind. Copy this file to .env (gitignored) and pass it +# at deploy with --env-file services/fleet-prometheus/.env. # -# Configuration lives entirely in prometheus.yml (secret-free by contract) and -# the compose command flags. There is intentionally nothing to copy to a .env. -# -# This template exists only to keep the standard service file layout. If a -# future step needs env (e.g. an Alertmanager URL), add it here and wire an -# env_file into docker-compose.yml at that point. +# Configuration otherwise lives entirely in prometheus.yml (secret-free by +# contract) and the compose command flags. + +# Tailscale IP węzła VPS (ubuntu-4gb-hel1-1). Bind nasłuchu Prometheus +# TYLKO do mesha — nie 0.0.0.0. Zweryfikuj przy odtwarzaniu hosta: tailscale ip -4. +TAILSCALE_BIND_IP=100.95.58.48