Die Targets standen seit dem ersten Scrape auf der oeffentlichen IP und waren
nie up. Ursache jetzt belegt: Die Exporter-Container auf dem Game-Host
veroeffentlichen ihre Ports gar nicht - docker ps zeigt "9100/tcp" ohne
0.0.0.0-Mapping, sie sind nur im Docker-Netz erreichbar. Weder Firewall noch
Bind-Adresse, wie bisher vermutet.
Targets auf 10.0.0.4 umgestellt (Host liegt im vSwitch, 22/80/443 antworten
dort). cadvisor bewusst auf Host-Port 8081: 8080 ist von coolify-proxy belegt.
Neu angebunden: pterodactyl_exporter auf 9531 - laeuft dort seit drei Monaten
und war nie im Monitoring.
README nachgezogen; sie nannte weiterhin die oeffentliche IP und beschrieb die
Targets als "antworten aktuell nicht", ohne den Grund.
⚠️ Wirksam erst, wenn die Ports auf dem Game-Host auf 10.0.0.4 veroeffentlicht
sind. Bis dahin bleiben die Targets down - unveraendert zum Zustand davor, nur
mit richtiger Adresse und dokumentierter Ursache.
154 lines
7.3 KiB
Markdown
154 lines
7.3 KiB
Markdown
# Monitoring-Stack (Operating-Host)
|
|
|
|
Prometheus + Loki + Grafana + Alloy + node-exporter fuer den Operating-Server
|
|
(`CFGMON`). Vollstaendig provisioniert: Datasources und Dashboards liegen als
|
|
Code in diesem Repo, ein `docker compose up -d` auf einem frischen Host stellt
|
|
den kompletten Stack wieder her.
|
|
|
|
## Deployment
|
|
|
|
```bash
|
|
cp .env.example .env # Werte setzen (nur beim allerersten Start relevant)
|
|
docker network create traefik # falls noch nicht vorhanden
|
|
docker compose up -d
|
|
```
|
|
|
|
### Config-Aenderungen: was wirklich ankommt
|
|
|
|
**Prometheus und Alertmanager mounten seit 2026-08-15 ihr Config-VERZEICHNIS**
|
|
(`./prometheus`, `./alertmanager`) statt einzelner Dateien. Damit ist die frueher
|
|
hier beschriebene Inode-Falle fuer sie beseitigt: `git pull` ersetzt Dateien per
|
|
Rename (neuer Inode); ein Einzeldatei-Mount zeigt danach weiter auf die **alte**
|
|
Datei, waehrend `SIGHUP` seelenruhig Erfolg meldet. Verzeichnis-Mounts loesen bei
|
|
jedem Zugriff ueber den Pfad auf.
|
|
|
|
Warum strukturell statt per Anleitung: genau diese Falle war hier bereits
|
|
ausfuehrlich dokumentiert -- samt richtigem Kommando und Pruefbefehl -- und hat am
|
|
2026-08-14 trotzdem zugeschlagen (geladen wurden die alten Alert-Regeln, Reload
|
|
meldete Erfolg). Eine Fussangel, die man nur durch Lesen umgeht, umgeht man
|
|
irgendwann nicht.
|
|
|
|
Nach einem `git pull`, der Configs anfasst:
|
|
|
|
```bash
|
|
docker compose up -d # Mount-/Service-Aenderungen -> Container wird neu erstellt
|
|
```
|
|
|
|
Reicht bei Prometheus/Alertmanager fuer den Inhalt bereits ein Reload, ist das in
|
|
Ordnung -- die Datei ist jetzt wirklich die neue.
|
|
|
|
**Noch als Einzeldatei gemountet** (gleiche Falle, dort weiterhin
|
|
`--force-recreate` noetig): `loki/loki-config.yaml`, `alloy/config.alloy` sowie die
|
|
Skripte `matrix-alerts.py`, `release-watch.py`, `cve-exporter.py`.
|
|
|
|
Ob eine Aenderung angekommen ist, sieht man nur **im Container**, nie auf der Platte:
|
|
|
|
```bash
|
|
docker exec prometheus md5sum /etc/prometheus/alerts.yml
|
|
md5sum prometheus/alerts.yml # muessen uebereinstimmen
|
|
```
|
|
|
|
Grafana liest **Provider-Definitionen** nur beim Start: ein neuer Ordner in
|
|
`provisioning/dashboards/dashboards.yml` braucht `docker compose restart grafana`.
|
|
Dashboard-JSONs innerhalb eines bestehenden Providers werden laufend nachgezogen.
|
|
|
|
## Struktur
|
|
|
|
| Pfad | Inhalt |
|
|
|---|---|
|
|
| `docker-compose.yml` | Stack-Definition, Images auf feste Versionen gepinnt |
|
|
| `prometheus/prometheus.yml` | Scrape-Konfiguration |
|
|
| `loki/loki-config.yaml` | Loki Single-Binary-Konfiguration, 90 Tage Retention |
|
|
| `alloy/config.alloy` | Docker-Log-Collection -> Loki, node-exporter -> Prometheus |
|
|
| `grafana/provisioning/` | Datasources (feste UIDs!) und Dashboard-Provider |
|
|
| `grafana/dashboards/<ordner>/*.json` | Dashboards, je Unterordner ein Grafana-Ordner |
|
|
| `cve/images.txt` | Liste der real deployten Images, Inventur aus dem Cluster |
|
|
| `cve/scan-loop.sh` | Trivy-Scan-Schleife (24h), schreibt JSON-Reports |
|
|
| `cve/cve-exporter.py` | Reports -> Prometheus-Metriken, mit First-Seen-State |
|
|
|
|
## Externe Abhaengigkeiten
|
|
|
|
- **Remote-Write eingehend** auf `:9090`: k3s-Cluster und Matrix-Server pushen
|
|
Metriken (`flux`, `kube_state_metrics`, `synapse`).
|
|
- **Log-Push eingehend** auf `:3100` (Loki) von externen Alloys.
|
|
- **Scrape ausgehend**: k3s-Host (`10.0.0.2:9100`), Game-Host privat ueber den
|
|
vSwitch (`10.0.0.4:9100`, `:8081` cadvisor, `:9531` pterodactyl_exporter).
|
|
cadvisor liegt dort auf 8081, weil Host-Port 8080 von coolify-proxy belegt ist.
|
|
- `cadvisor` und `traefik` werden gescraped, laufen aber in **anderen** Stacks
|
|
(cadvisor: Portainer-Stack `thread-net-git`; beide haengen im externen
|
|
`traefik`-Netz).
|
|
|
|
## Dashboards aendern
|
|
|
|
Provisionierte Dashboards sind in der UI editierbar (`allowUiUpdates: true`),
|
|
aber die JSON-Datei im Repo ist die Quelle der Wahrheit: UI-Aenderungen
|
|
muessen exportiert und committet werden, sonst gehen sie beim naechsten
|
|
Datei-Update verloren.
|
|
|
|
## CVE-Pipeline (gitops#47)
|
|
|
|
`cve-scan` scannt alle 24h die Images aus `cve/images.txt` mit Trivy und legt
|
|
JSON-Reports in ein Volume; `cve-exporter` serviert sie als Metriken auf
|
|
`:9101`, Prometheus scraped sie als Job `cve_exporter`. Dashboard:
|
|
**Security / CVE-Uebersicht (Trivy)**.
|
|
|
|
Der Exporter merkt sich je `(CVE, Target)` den Erstfund in einem persistenten
|
|
State (`cve_exporter_state`-Volume) -- daher kommt die Spalte
|
|
"erstmals gesehen". Verschwindet ein Finding, faellt der Eintrag raus
|
|
(= "geschlossen").
|
|
|
|
Bei Stack-Aenderungen `cve/images.txt` nachziehen, sonst scannt die Pipeline
|
|
an der Realitaet vorbei.
|
|
|
|
### Alarm-Zustellung (frueher stummgeschaltet — seit gitops#51 wieder scharf)
|
|
|
|
**Stand 2026-08-15: Alarme werden zugestellt.** `alertmanager.yml` hat nur noch die
|
|
Default-Route auf den `matrix`-Receiver; die frueher hier beschriebene
|
|
`room="security"`-Route auf den Null-Receiver existiert nicht mehr. Alarme **ohne**
|
|
`room`-Label (u.a. die `axion-basics`- und `axion-backup`-Gruppen) laufen ueber
|
|
diese Default-Route.
|
|
|
|
Historie, damit der Grund der damaligen Stummschaltung nicht verlorengeht: die
|
|
CVE-Regeln erzeugten je eine Alarm-Instanz **pro CVE pro Image** (beim ersten Lauf
|
|
59 CRITICAL + 445 HIGH), `matrix-alerts.py` schickte eine Nachricht pro Alarm,
|
|
Synapse rate-limitete nach ~10 Nachrichten mit 429, und weil `save_state()` hinter
|
|
der Sende-Schleife stand, wiederholte Alertmanager die komplette Gruppe mit noch
|
|
leerer Deduplizierung. Behoben durch Aggregation auf `count by (target, severity)`
|
|
(eine Instanz je Image statt je CVE); Details im Dashboard `cve-overview`.
|
|
Vollstaendige Historie: gitops#51 / AAR 2026-08-01.
|
|
|
|
**Zustellfehler sind seit 2026-08-15 selbst ueberwacht:** Prometheus scrapt
|
|
Alertmanager (`operating_alertmanager`) und `AlertDeliveryFailing` schlaegt an,
|
|
wenn `alertmanager_notifications_failed_total` steigt. Vorher war Alertmanager zwar
|
|
Alarm-Ziel, aber kein Scrape-Target — eine reissende Alarmkette haette sich also
|
|
selbst nicht melden koennen.
|
|
|
|
### Kleinere offene Punkte der Pipeline
|
|
|
|
- `TrivyScanStale` kann ein Image, das **nie** erfolgreich gescannt wurde,
|
|
nicht melden: ohne ersten Report gibt es keine Serie, an der
|
|
`time() - trivy_last_scan_timestamp` haengen koennte. Ein dauerhaft
|
|
fehlschlagendes Image bleibt damit still. `TargetDown` deckt nur den toten
|
|
Exporter ab, nicht den einzelnen blinden Fleck.
|
|
- `coturn/coturn:latest` ist als einziges Image ungepinnt -- Scan-Ergebnisse
|
|
sind dadurch nicht reproduzierbar.
|
|
- Der Exporter prunt den First-Seen-State bei **jedem** Scrape anhand der
|
|
gerade gelesenen Reports. Ein transienter Lesefehler (`except: continue`)
|
|
loescht die Erstfund-Zeitstempel des betroffenen Targets dauerhaft.
|
|
|
|
## Offene Punkte / Sicherheit
|
|
|
|
- `9090`, `3100`, `9100` sind auf der oeffentlichen IP ohne Auth erreichbar
|
|
(Docker umgeht ufw). Per **Hetzner Cloud Firewall** auf die IPs der
|
|
bekannten Absender einschraenken.
|
|
- Game-Host: Die Targets zeigen seit 2026-08-20 auf die private Adresse
|
|
`10.0.0.4` (management #0002). **Voraussetzung auf dem Host**: Die
|
|
Exporter-Container veroeffentlichen ihre Ports bisher gar nicht -- `docker ps`
|
|
zeigt `9100/tcp` ohne `0.0.0.0`-Mapping, sie sind nur im Docker-Netz erreichbar.
|
|
Solange das so ist, bleiben die Targets down; die Adresse ist dann nur eine
|
|
andere. Veroeffentlichen auf `10.0.0.4`, **nicht** auf `0.0.0.0` -- sonst stehen
|
|
sie wieder im Internet.
|
|
- Loki `table_manager` ist deprecated; Retention-Enforcement laeuft aktuell
|
|
nicht ueber den Compactor -- bei Gelegenheit auf Compactor-Retention
|
|
umstellen.
|