2026-07-30 12:13:01 +02:00
|
|
|
# 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
|
|
|
|
|
```
|
|
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
### Config-Aenderungen: was wirklich ankommt
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
**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.
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
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:
|
2026-08-01 14:01:14 +02:00
|
|
|
|
|
|
|
|
```bash
|
2026-08-14 12:00:00 +00:00
|
|
|
docker compose up -d # Mount-/Service-Aenderungen -> Container wird neu erstellt
|
2026-08-01 14:01:14 +02:00
|
|
|
```
|
|
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
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:
|
2026-08-01 14:01:14 +02:00
|
|
|
|
|
|
|
|
```bash
|
2026-08-14 12:00:00 +00:00
|
|
|
docker exec prometheus md5sum /etc/prometheus/alerts.yml
|
|
|
|
|
md5sum prometheus/alerts.yml # muessen uebereinstimmen
|
2026-08-01 14:01:14 +02:00
|
|
|
```
|
|
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
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.
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-07-30 12:13:01 +02:00
|
|
|
## 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 |
|
2026-08-01 14:01:14 +02:00
|
|
|
| `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 |
|
2026-07-30 12:13:01 +02:00
|
|
|
|
|
|
|
|
## 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`), Pterodactyl-Host
|
|
|
|
|
(`157.90.155.206:9100` + `:8080`).
|
|
|
|
|
- `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.
|
|
|
|
|
|
2026-08-01 14:01:14 +02:00
|
|
|
## 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.
|
|
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
### Alarm-Zustellung (frueher stummgeschaltet — seit gitops#51 wieder scharf)
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
**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.
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
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.
|
2026-08-01 14:01:14 +02:00
|
|
|
|
2026-08-14 12:00:00 +00:00
|
|
|
**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.
|
2026-08-01 14:01:14 +02:00
|
|
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
2026-07-30 12:13:01 +02:00
|
|
|
## 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.
|
|
|
|
|
- Pterodactyl-Host `157.90.155.206`: node-exporter und cadvisor antworten
|
|
|
|
|
aktuell nicht (Targets down).
|
|
|
|
|
- Loki `table_manager` ist deprecated; Retention-Enforcement laeuft aktuell
|
|
|
|
|
nicht ueber den Compactor -- bei Gelegenheit auf Compactor-Retention
|
|
|
|
|
umstellen.
|