188 lines
8.9 KiB
Markdown
188 lines
8.9 KiB
Markdown
# CFGMON
|
|||
|
|
|
||
|
|
Monitoring-Stack, Gitea und der Reverse Proxy für alles Öffentliche.
|
||
|
|
|
||
|
|
| | |
|
||
|
|
|---|---|
|
||
|
|
| **Hostname** | `CFGMON` |
|
||
|
|
| **OS** | Ubuntu 24.04.4 LTS |
|
||
|
|
| **IPv4** | `188.245.193.243` |
|
||
|
|
| **IPv6** | `2a01:4f8:c17:93eb::1` |
|
||
|
|
| **Privat** | `10.0.0.3` (`enp7s0`, Hetzner-Netz — dort liegt auch k3s auf `10.0.0.2`) |
|
||
|
|
| **DNS** | `rohana.axion1337.de` → Gitea, `selendis.axion1337.de` → Grafana |
|
||
|
|
| **Stand** | 2026-07-30 |
|
||
|
|
|
||
|
|
## Dienste
|
||
|
|
|
||
|
|
| Container | Image | Compose-Projekt | Definition |
|
||
|
|
|---|---|---|---|
|
||
|
|
| prometheus | `prom/prometheus:v3.3.1` | `monitoring` | `sorb/threadnet-operating`, `monitoring/` |
|
||
|
|
| loki | `grafana/loki:3.7.1` | `monitoring` | dito |
|
||
|
|
| grafana | `grafana/grafana:12.0.0` | `monitoring` | dito |
|
||
|
|
| alloy | `grafana/alloy:v1.16.0` | `monitoring` | dito |
|
||
|
|
| node-exporter | `prom/node-exporter:v1.9.1` | `monitoring` | dito |
|
||
|
|
| traefik | `traefik:v3.7.9` | `thread-net-git` | **nicht versioniert**, Portainer-Stack in `/data/compose/8` |
|
||
|
|
| gitea | `gitea/gitea:latest` | `thread-net-git` | dito |
|
||
|
|
| cadvisor | `gcr.io/cadvisor/cadvisor:latest` | `thread-net-git` | dito |
|
||
|
|
| portainer_agent | `portainer/agent:2.27.5` | — | standalone, kein Compose |
|
||
|
|
|
||
|
|
Prometheus-Jobs: `operating_prometheus`, `operating_node-exporter`,
|
||
|
|
`operating_cadvisor`, `operating_traefik` (alle lokal), `k3s_host_node`
|
||
|
|
(`10.0.0.2:9100`), `pterodactyl_host_node` und `gameserver_cadvisor`
|
||
|
|
(beide `157.90.155.206`, siehe [game](game.md)).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CFGMON-01 — Zertifikatserneuerung braucht offene Ports (IPv4 **und** IPv6)
|
||
|
|
|
||
|
|
**Status:** offen — zeitkritisch ab **2026-09-28**
|
||
|
|
|
||
|
|
Das Cert für `selendis.axion1337.de` wurde am 2026-07-30 ausgestellt und läuft am
|
||
|
|
**2026-10-28** ab. Traefik erneuert ca. 30 Tage vorher, also ab Ende September.
|
||
|
|
Gitea (`rohana.axion1337.de`) hängt am selben Resolver und ist damit genauso
|
||
|
|
betroffen — inklusive der Erreichbarkeit dieses Repos per HTTPS.
|
||
|
|
|
||
|
|
Traefik nutzt TLS-ALPN-01
|
||
|
|
(`--certificatesresolvers.letsencrypt.acme.tlschallenge=true`). Die Challenge läuft
|
||
|
|
ausschließlich über **Port 443, erreichbar aus dem offenen Internet**. Bei
|
||
|
|
gesperrtem Port scheitert sie mit:
|
||
|
|
|
||
|
|
```
|
||
|
|
urn:ietf:params:acme:error:connection :: Timeout during connect (likely firewall problem)
|
||
|
|
```
|
||
|
|
|
||
|
|
Traefik liefert dann sein Default-Self-Signed-Cert aus. Das fällt niemandem auf, bis
|
||
|
|
jemand die Seite aufruft — genau so ist es am 2026-07-30 passiert.
|
||
|
|
|
||
|
|
Eine IP-Allowlist ist **keine** Option: Let's Encrypt veröffentlicht seine
|
||
|
|
Validierungs-IPs nicht und prüft seit Einführung der Multi-Perspective Validation
|
||
|
|
aus mehreren Regionen gleichzeitig.
|
||
|
|
|
||
|
|
**IPv6 ist der Fallstrick.** `rohana`, `selendis` (und noch `www.*`, siehe
|
||
|
|
[ZONE-01](../shared/zone-axion1337.md)) haben AAAA-Records auf
|
||
|
|
`2a01:4f8:c17:93eb::1`. Damit validiert LE **bevorzugt über IPv6**. Die
|
||
|
|
Hetzner-Cloud-Firewall braucht für 443 deshalb eine Regel mit Quelle `::/0` —
|
||
|
|
`0.0.0.0/0` deckt IPv6 **nicht** mit ab. Fehlt sie, scheitert die Erneuerung, obwohl
|
||
|
|
IPv4 offen ist.
|
||
|
|
|
||
|
|
Vom Host aus ist das **nicht verifizierbar**: eine Verbindung auf die eigene
|
||
|
|
öffentliche Adresse läuft lokal und sagt nichts über den Weg von außen. Das
|
||
|
|
Ausliefern über IPv6 funktioniert (`rohana` und `selendis` antworten mit 200,
|
||
|
|
Traefik lauscht auf `[::]:443`) — das ist aber nicht dasselbe wie von außen
|
||
|
|
erreichbar.
|
||
|
|
|
||
|
|
**Nächster Schritt:** Entscheidung zwischen
|
||
|
|
|
||
|
|
- **A — Port 443 zur Erneuerung öffnen**, für IPv4 *und* IPv6. Nichts umbauen, aber
|
||
|
|
alle ~60 Tage daran denken, und das Scheitern fällt nicht auf.
|
||
|
|
- **B — auf DNS-01 umstellen** (empfohlen). Validierung über einen TXT-Record,
|
||
|
|
**kein offener Port nötig**, läuft bei dichter Firewall durch. Braucht ein
|
||
|
|
API-Token für die Zone bei IONOS, als Secret für Traefik hinterlegt. Ermöglicht
|
||
|
|
zusätzlich Wildcard-Certs (`*.axion1337.de`), also künftige Subdomains ohne
|
||
|
|
Portöffnung. Setzt voraus, dass der Traefik-Stack anfassbar ist — siehe
|
||
|
|
[CFGMON-02](#cfgmon-02--traefik-gitea-und-cadvisor-sind-nicht-versioniert).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CFGMON-02 — Traefik, Gitea und cAdvisor sind nicht versioniert
|
||
|
|
|
||
|
|
**Status:** offen
|
||
|
|
|
||
|
|
Die drei laufen im Compose-Projekt `thread-net-git` aus `/data/compose/8`, einem
|
||
|
|
**von Portainer verwalteten Stack**. Es gibt kein Repo dazu. Betroffen ist damit
|
||
|
|
ausgerechnet die Infrastruktur, an der alles andere hängt:
|
||
|
|
|
||
|
|
- **Traefik** — die einzige Stelle, an der Entrypoints, ACME-Resolver und
|
||
|
|
Zertifikatsspeicher definiert sind. Die Resolver-Namen aus dieser Datei sind der
|
||
|
|
Grund, warum ein Label `certresolver=le` monatelang ins Leere zeigte.
|
||
|
|
- **Gitea** — hostet die Repos, in denen die Infrastruktur beschrieben wird. Das ist
|
||
|
|
ein Bootstrapping-Problem: die Definition von Gitea könnte in Gitea liegen, wäre
|
||
|
|
bei einem Ausfall aber genau dann nicht erreichbar, wenn man sie braucht.
|
||
|
|
- **cAdvisor** — wird von Prometheus als `operating_cadvisor` gescrapt, ist also eine
|
||
|
|
Abhängigkeit des Monitoring-Stacks, die selbst außerhalb der IaC liegt.
|
||
|
|
|
||
|
|
**Nächster Schritt:** Stack-Definition aus Portainer exportieren
|
||
|
|
(`/data/compose/8/docker-compose.yml` plus zugehörige Files), in ein Repo überführen
|
||
|
|
und dabei die `:latest`-Tags pinnen — `gitea/gitea:latest` und
|
||
|
|
`cadvisor:latest` machen jeden Pull zu einem unkontrollierten Update. Beim Übertragen
|
||
|
|
denselben Projektnamen `thread-net-git` beibehalten, sonst legt Compose neue leere
|
||
|
|
Volumes an. Für Gitea zusätzlich klären, wo die Definition liegen soll, damit sie
|
||
|
|
bei einem Gitea-Ausfall erreichbar bleibt.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CFGMON-03 — Prometheus-Remote-Write und Loki sind öffentlich ohne Auth
|
||
|
|
|
||
|
|
**Status:** offen
|
||
|
|
|
||
|
|
Prometheus läuft mit `--web.enable-remote-write-receiver` und Port `9090` ist
|
||
|
|
veröffentlicht, weil der k3s-Cluster und der Matrix-Server dorthin pushen. Loki ist
|
||
|
|
auf `3100` ebenfalls veröffentlicht.
|
||
|
|
|
||
|
|
Ein offener Remote-Write-Receiver ohne Auth erlaubt Fremden, **beliebige Metriken
|
||
|
|
einzuspeisen** — damit lassen sich Dashboards und Alerts unbrauchbar machen — und
|
||
|
|
über die Query-API alle Daten auszulesen. Bei Loki auf `3100` gilt dasselbe für
|
||
|
|
Logs, die typischerweise mehr Interna enthalten als Metriken.
|
||
|
|
|
||
|
|
In `monitoring/docker-compose.yml` steht dazu bereits ein Warnhinweis als Kommentar,
|
||
|
|
umgesetzt ist er nicht.
|
||
|
|
|
||
|
|
**Nächster Schritt:** Beide Ports per Hetzner-Cloud-Firewall auf die Absender
|
||
|
|
einschränken. Sauberer wäre, sie gar nicht öffentlich zu binden: k3s liegt mit
|
||
|
|
`10.0.0.2` im selben privaten Netz wie CFGMON (`10.0.0.3`), der Push könnte also
|
||
|
|
über das private Netz laufen. Für den Matrix-Server auf `49.13.132.245` prüfen, ob
|
||
|
|
er in dasselbe Netz aufgenommen werden kann — siehe [matrix](matrix.md).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CFGMON-04 — Grafana-Admin-Credentials aus `.env` gelten nicht für die HTTP-API
|
||
|
|
|
||
|
|
**Status:** offen, geringe Priorität
|
||
|
|
|
||
|
|
`GF_SECURITY_ADMIN_USER` und `GF_SECURITY_ADMIN_PASSWORD` greifen nur beim
|
||
|
|
allerersten Start mit leerem Volume. Der Live-Admin wurde offenbar später in der UI
|
||
|
|
geändert, die Werte aus der `.env` sind damit funktionslos. Verifikation von
|
||
|
|
Datasources und Dashboards läuft deshalb über `grafana.db` statt über die API.
|
||
|
|
|
||
|
|
Kein Defekt, aber eine Falle: die `.env` sieht aus, als wäre sie die Quelle der
|
||
|
|
Wahrheit, ist es aber nicht.
|
||
|
|
|
||
|
|
**Nächster Schritt:** entweder das echte Admin-Passwort in die `.env` nachziehen und
|
||
|
|
dokumentieren, dass es nur den Ersteinrichtungsfall abdeckt, oder einen
|
||
|
|
Service-Account mit API-Token für Verifikationszwecke anlegen. Letzteres ist
|
||
|
|
sauberer, weil dafür kein Admin-Passwort nötig ist.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Erledigt
|
||
|
|
|
||
|
|
### CFGMON-05 — Monitoring-Stack unter IaC bringen · erledigt 2026-07-30
|
||
|
|
|
||
|
|
Der Stack lief aus `/opt/monitoring` ohne Versionierung und mit `:latest`-Tags. Jetzt
|
||
|
|
in `sorb/threadnet-operating` unter `monitoring/`, Images gepinnt,
|
||
|
|
Grafana-Datasources und 9 Dashboards provisioniert. Projektname `monitoring`
|
||
|
|
beibehalten, dadurch blieben die Volumes erhalten.
|
||
|
|
|
||
|
|
### CFGMON-06 — Grafana-Certresolver zeigte ins Leere · erledigt 2026-07-30
|
||
|
|
|
||
|
|
Das Label sagte `certresolver=le`, Traefik kennt den Resolver aber als
|
||
|
|
`letsencrypt`. Traefik protokollierte `Router uses a nonexistent certificate
|
||
|
|
resolver` und lieferte für `selendis.axion1337.de` sein Default-Self-Signed-Cert
|
||
|
|
aus. Aus dem Altbestand in `/opt/monitoring` unverändert übernommen und dort
|
||
|
|
mindestens seit dem 2026-07-27 vorhanden.
|
||
|
|
|
||
|
|
Behoben in `threadnet-operating`, Commit `a400f8a`. Cert von Let's Encrypt (YR2)
|
||
|
|
ausgestellt, gültig bis 2026-10-28 — die Nachfolge davon ist
|
||
|
|
[CFGMON-01](#cfgmon-01--zertifikatserneuerung-braucht-offene-ports-ipv4-und-ipv6).
|
||
|
|
|
||
|
|
### CFGMON-07 — Alloy verlor seine Positions-Datei bei jedem Deploy · erledigt 2026-07-30
|
||
|
|
|
||
|
|
`--storage.path=/var/lib/alloy/data` war gesetzt, aber ohne Volume: die
|
||
|
|
Positions-Datei lag im Container-Layer. Nach jedem Recreate las Alloy alle
|
||
|
|
Docker-Logdateien von vorn, worauf Loki alles älter als 7 Tage mit HTTP 400 abwies
|
||
|
|
(`timestamp too old`, `reject_old_samples`). Betroffen waren nur Alt-Logzeilen bis
|
||
|
|
zurück zu 2025, keine aktuellen Daten.
|
||
|
|
|
||
|
|
Behoben durch ein `alloy_data`-Volume, Commit `edac97e`. Verifiziert: Positions
|
||
|
|
überleben `--force-recreate`, zweiter Recreate erzeugt 0 Fehler.
|