verfahren: Deploy-Uebergabe als Standardverfahren, AAR zu gitops#47

Nach dem Deploy der CVE-Pipeline (gitops#47) als Verfahren festgehalten. Der
Stand war korrekt und gelintet; der Blocker entstand erst aus der Datenmenge,
gegen die er lief -- eine Alarm-Instanz pro CVE, real 126 CRITICAL und 1222
HIGH. So etwas faellt in keinem Diff auf, nur beim Messen vor dem Deploy.

Neu:
- .gitea/ISSUE_TEMPLATE/deploy-uebergabe.yaml -- Uebergabe-Issue mit
  Pflichtfeldern Mengengeruest, vollstaendiges Deploy-Kommando, Verifikation
  im laufenden Dienst, Aussenwirkung samt Not-Aus, Rollback
- verfahren/deploy-uebergabe.md -- Ablauf und Pruefliste
- verfahren/aar-vorlage.md -- AAR-Vorlage
- verfahren/aar/2026-08-01-cve-pipeline-gitops47.md -- der ausloesende AAR

Abgrenzung im README ergaenzt: hosts/ und shared/ halten offene Punkte,
verfahren/ haelt, wie wir arbeiten. Die Uebergabe-Issues laufen bewusst hier
statt im Projekt-Repo, weil das Verfahren repo-uebergreifend gilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thore Cimbal
2026-08-01 12:00:00 +00:00
co-authored by Claude Opus 5
parent 0b6060339e
commit 3e81f40178
6 changed files with 333 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
name: "Deploy-Übergabe"
about: "Ein gebauter, noch nicht ausgerollter Stand wird zum Deploy übergeben"
title: "[DEPLOY] "
body:
- type: markdown
attributes:
value: |
Für die Übergabe „ich habe gebaut — du rollst aus".
Ausführlich: [verfahren/deploy-uebergabe.md](../src/branch/main/verfahren/deploy-uebergabe.md)
Die Felder sind die Punkte, an denen Übergaben real schiefgegangen sind.
Wo nichts zutrifft, `-` eintragen statt das Feld zu löschen.
- type: input
id: stand
attributes:
label: Stand
description: Repo, Branch und Commit, der ausgerollt werden soll
placeholder: "sorb/threadnet-operating @ main, b6007c5"
validations:
required: true
- type: dropdown
id: testtiefe
attributes:
label: Testtiefe
description: Ehrlich einordnen — „ungetestet" ist eine brauchbare Angabe, „läuft" ohne Beleg nicht
options:
- "ungetestet — nur gelintet / statisch geprüft"
- "teilweise getestet — Details unten"
- "getestet gegen echte Daten"
validations:
required: true
- type: textarea
id: mengengeruest
attributes:
label: Mengengerüst
description: |
Wenn der Stand irgendetwas erzeugt (Nachrichten, Alarme, Zeitreihen, Dateien, Requests):
Wie viel davon beim ersten Lauf? Geschätzt oder gemessen — bitte dazuschreiben, was von beidem.
Genau hier entstehen die Überraschungen: „ein Alarm pro Fund" ist harmlos bei 5 Funden und ein Ausfall bei 126.
placeholder: |
Alarme: ~1 pro CVE pro Image. An 3 von 29 Images gemessen: 27 CRITICAL / 126 HIGH.
Zeitreihen: ~2000. Matrix-Nachrichten: 1 pro Alarm.
validations:
required: true
- type: textarea
id: kommando
attributes:
label: Vollständiges Deploy-Kommando
description: |
Inklusive aller Reload-, Restart- und Recreate-Schritte.
Ein `up -d`, das „Running" meldet und nichts tut, ist der häufigste stille Fehlschlag.
render: bash
placeholder: |
cd /opt/<repo> && git pull
cd <stack> && docker compose up -d
docker compose up -d --force-recreate <dienste-mit-geaenderter-config>
validations:
required: true
- type: textarea
id: verifikation
attributes:
label: Woran erkennt man, dass es wirklich greift
description: |
Konkrete Prüfung **dort, wo der Dienst liest** — im Container, in der API, in der laufenden Config.
Ein grüner Linter belegt, dass die Datei gültig ist, nicht dass sie geladen wurde.
render: bash
placeholder: |
docker exec prometheus grep -c axion-cve /etc/prometheus/alerts.yml # erwartet: 1
curl -s localhost:9090/api/v1/rules | grep -c axion-cve # erwartet: >0
validations:
required: true
- type: textarea
id: aussenwirkung
attributes:
label: Außenwirkung und Not-Aus
description: |
Was verlässt beim ersten Lauf das System — Matrix-Räume, Mails, Webhooks, fremde APIs?
Und wie schaltet man **nur diesen Kanal** stumm, ohne den ganzen Deploy zu verschieben?
Datensammlung und Zustellung getrennt scharf zu schalten ist fast immer möglich und
macht aus einer Blockade einen normalen Deploy.
placeholder: |
Kanal: Matrix-Raum „Security" über Label room=security.
Stumm: in alertmanager.yml route room="security" -> receiver "null".
validations:
required: true
- type: textarea
id: rollback
attributes:
label: Rollback
description: Was tun, wenn es schiefgeht — und was davon ist *nicht* zurückholbar (gesendete Nachrichten, externe Writes)?
validations:
required: true
- type: textarea
id: offen
attributes:
label: Bewusst offen gelassen
description: Bekannte Lücken, Nebenfunde, Abgrenzungen. Verhindert, dass die Gegenseite Zeit mit Nachweisen von etwas verbringt, das schon bekannt ist.
validations:
required: false
+18
View File
@@ -9,6 +9,7 @@ Chat-Historie oder einem Kopf verschwindet.
```
hosts/ ein File pro Host, benannt nach dem Hostnamen
shared/ Themen, die mehrere Hosts betreffen (DNS-Zone, Mail-Policy)
verfahren/ wiederkehrende Abläufe zwischen Personen und Hosts, plus AARs
```
Faustregel für die Einordnung: **Der Eintrag gehört dorthin, wo die Arbeit
@@ -31,6 +32,23 @@ auf CFGMON, dessen Ursache ein blockierter Port auf dem Gameserver ist, gehört
|---|---|
| DNS-Zone `axion1337.de` und Mail-Policy (IONOS) | [shared/zone-axion1337.md](shared/zone-axion1337.md) |
## Verfahren
`hosts/` und `shared/` halten **offene Punkte**, [verfahren/](verfahren/) hält
**wie wir arbeiten** — Abläufe, die zwischen Personen oder über mehrere Hosts
hinweg gelten und deshalb an kein einzelnes Projekt-Repo gehören.
| Verfahren | Inhalt |
|---|---|
| [Deploy-Übergabe](verfahren/deploy-uebergabe.md) | „einer baut, ein anderer rollt aus": Issue-Vorlage, Prüfliste, AAR |
Zur Deploy-Übergabe gehört die Issue-Vorlage
`.gitea/ISSUE_TEMPLATE/deploy-uebergabe.yaml` — sie erscheint beim Anlegen eines
Issues in diesem Repo als **Deploy-Übergabe**. Das ist die eine Ausnahme von der
Regel unter „Verhältnis zu Gitea-Issues": die Übergabe-Issues laufen hier, weil
das Verfahren repo-übergreifend gilt; der fachliche Inhalt bleibt im jeweiligen
Projekt-Repo.
## Konventionen
**IDs** sind pro Datei fortlaufend mit einem Präfix, das den Ort benennt:
+19
View File
@@ -0,0 +1,19 @@
# Verfahren
Wiederkehrende Abläufe zwischen Personen und Hosts — dort festgehalten, wo sie
nicht an einem einzelnen Projekt-Repo hängen.
| Datei | Inhalt |
|---|---|
| [deploy-uebergabe.md](deploy-uebergabe.md) | Ablauf und Prüfliste für „einer baut, ein anderer rollt aus" |
| [aar-vorlage.md](aar-vorlage.md) | Vorlage für den After Action Report nach einem Deploy |
| [aar/](aar/) | Abgelegte AARs, benannt `JJJJ-MM-TT-<vorhaben>.md` |
Die zugehörige Issue-Vorlage liegt unter
`.gitea/ISSUE_TEMPLATE/deploy-uebergabe.yaml` und erscheint beim Anlegen eines
Issues in diesem Repo als **Deploy-Übergabe**.
Abgrenzung zum Rest des Repos: `hosts/` und `shared/` halten **offene Punkte**,
dieses Verzeichnis hält **wie wir arbeiten**. Ein Verfahren wird hier nur
aufgenommen, wenn es mindestens einmal an einem echten Vorfall gescheitert
oder bewährt ist — der auslösende AAR wird jeweils verlinkt.
+45
View File
@@ -0,0 +1,45 @@
# AAR — <Vorhaben> <Issue-Referenz>
**Datum:** JJJJ-MM-TT · **Host/Stack:** … · **Auftrag:**
## 1. Ergebnis
Was ist live und verifiziert. Was ist bewusst **nicht** live, und warum.
Diese Trennung steht ganz oben — sie ist die einzige Angabe, die jemand
braucht, der nur eine Zeile liest.
## 2. Befunde
| # | Befund | Schwere | Status |
|---|---|---|---|
| 1 | … | HIGH / MEDIUM / LOW | abgefangen / behoben / notiert · Issue |
Schwere nach Auswirkung, nicht nach Aufwand. Zu jedem Befund gehört, **wie er
sichtbar wurde** — falls das nicht offensichtlich ist, siehe Abschnitt 4.
## 3. Verdachtsfälle mit Entwarnung
Was geprüft und **nicht** bestätigt wurde, mit dem Messwert. Genauso wichtig
wie die Befunde: verhindert, dass dieselbe Vermutung beim nächsten Mal erneut
Zeit kostet.
## 4. Was die Befunde ermöglicht hat
Die Methode, nicht die Chronologie. Wenn ein Befund nur durch eine bestimmte
Prüfung sichtbar wurde, gehört genau die hierher — das ist der Teil, der beim
nächsten Deploy wiederverwendbar ist.
## 5. Offen
Was bleibt, wer entscheidet, was blockiert. Mit Issue-Referenz statt
wiederholtem Inhalt.
---
Regeln:
- Nicht verifizierte Aussagen als solche kennzeichnen — eine Vermutung, die wie
ein Befund aussieht, kostet später mehr Zeit als sie spart (Konvention aus
der [README](../README.md)).
- Zahlen statt Adjektive. „Viele Alarme" ist keine Angabe, „126 CRITICAL" ist eine.
- Kurz. Ein AAR, der nicht gelesen wird, hat keinen Wert.
@@ -0,0 +1,66 @@
# AAR — CVE-Pipeline `gitops#47`
**Datum:** 2026-08-01 · **Host/Stack:** CFGMON, `/opt/threadnet-operating/monitoring`
**Auftrag:** Deploy eines gebauten, als UNGETESTET übergebenen Stands (Trivy-Scanner,
Exporter, Alert-Regeln, Grafana-Dashboard)
## 1. Ergebnis
**Live und verifiziert:** Scanner (29/29 Images gescannt), Exporter, Prometheus-Job
`cve_exporter`, Regelgruppe `axion-cve`, Grafana-Ordner *Security* mit CVE-Dashboard.
**Bewusst nicht live:** die Alarm-Zustellung nach Matrix. `room="security"` routet in
`alertmanager.yml` auf einen Null-Receiver (Commit `2b715ca` in
`sorb/threadnet-operating`). Grund siehe Befund 1.
## 2. Befunde
| # | Befund | Schwere | Status |
|---|---|---|---|
| 1 | Eine Matrix-Nachricht pro CVE. 126 CRITICAL landen in **einer** Alertmanager-Gruppe, nach 24 h kommen 1222 HIGH dazu. Dazu steht `save_state()` in `do_POST` hinter der Sende-Schleife: bricht ein Send ab (Synapse rate-limitet nach ~10 mit 429), wird kein State gespeichert, der Receiver antwortet 502, Alertmanager wiederholt die komplette Gruppe — mit leerer Deduplizierung | HIGH | abgefangen, `gitops#51` |
| 2 | `docker compose up -d` aktiviert geänderte Configs nicht. Einzeldatei-Mounts hängen am Inode, `git pull` benennt um. Prometheus lief nach dem Deploy mit alten Regeln — `promtool` fand 9, Prometheus kannte 6, kein Fehler im Log | MEDIUM | behoben via `--force-recreate`, `gitops#52` |
| 3 | `TrivyScanStale` kann ein nie erfolgreich gescanntes Image nicht melden — ohne ersten Report existiert keine Serie, an der `time() - trivy_last_scan_timestamp` hängen könnte | LOW | notiert in `gitops#51` |
| 4 | Der Exporter prunt den First-Seen-State bei **jedem** Scrape. Ein transienter Lesefehler (`except: continue`) löscht die Erstfund-Zeitstempel des Targets dauerhaft | LOW | notiert in `gitops#51` |
| 5 | Grafana-Provisioning für den Security-Ordner fehlte — das Dashboard wäre nie erschienen | — | vom Autor selbst behoben (`cdfadc0`), bevor ausgerollt wurde |
Gemessene Gesamtlage über alle 29 Images: **126 CRITICAL, 1222 HIGH**, 2710 MEDIUM,
1316 LOW. Spitzenreiter `goauthentik/server:2026.2.3` mit 369 CRITICAL+HIGH.
## 3. Verdachtsfälle mit Entwarnung
| Vermutung | Messung | Ergebnis |
|---|---|---|
| Exporter parst 29 JSONs je Scrape → Timeouts bei 15 s Intervall | `collect()` gegen echte Reports | 0,4 s für 29 Reports, ~3100 Zeilen — unkritisch |
| Private Registry `rohana.axion1337.de` braucht Credentials für Trivy | Anonymer Pull | zieht anonym, keine Credentials nötig |
| Zwei down-Targets könnten Folge des Deploys sein | `avg_over_time(up[3h])` | 0.00 — schon 3 h vorher tot, in `hosts/game.md` erfasst |
`promtool check rules`, `amtool check-config`, `amtool config routes test`,
`docker compose config` und `py_compile` liefen alle sauber.
## 4. Was die Befunde ermöglicht hat
Befund 1 wäre in keinem Lint aufgefallen — der Code ist korrekt, das Problem entsteht
erst aus der **Datenmenge**, gegen die er läuft. Sichtbar wurde er durch Messen vor dem
Deploy: Trivy lokal über drei repräsentative Images ergab 27 CRITICAL / 126 HIGH auf 3
von 29, also die richtige Größenordnung. Die spätere Realität (126 CRITICAL) bestätigte
die Entscheidung. `amtool config routes test` belegte danach, dass die Stummschaltung
nur `room=security` trifft und den normalen Alarmweg unangetastet lässt.
Befund 2 wurde nur sichtbar, weil die Config **im Container** geprüft wurde
(`docker exec prometheus grep …`) statt auf der Platte. Auf der Platte sah alles richtig
aus, `up -d` meldete `Running`, und ein SIGHUP-Reload lud klaglos den alten Inhalt.
Diese beiden Punkte sind als Verfahren festgehalten:
[../deploy-uebergabe.md](../deploy-uebergabe.md).
## 5. Offen
Richtungsentscheidung zu `gitops#51`, bevor die Alarme scharf gehen: entweder
`matrix-alerts.py` auf eine Sammelnachricht pro Webhook-Batch umbauen (die fünf
Pflichtfelder je CVE als eine Zeile, bleibt vollständig), oder die Regeln auf
`count by (target, severity)` aggregieren und die CVE-Details im Dashboard lassen.
In beiden Fällen zusätzlich: State inkrementell speichern, 429 mit `Retry-After`
behandeln. Danach die `room="security"`-Route entfernen.
Nebenbefund ohne Handlungsbedarf von hier: `coturn/coturn:latest` ist das einzige
ungepinnte Image (bereits in `gitops#47` notiert).
+78
View File
@@ -0,0 +1,78 @@
# Verfahren: Deploy-Übergabe
Für die Konstellation „einer baut, ein anderer rollt aus". Zweck ist nicht mehr
Prozess, sondern **weniger Rückfragen und weniger stille Fehlschläge**.
Eingeführt am 2026-08-01 nach dem Deploy der CVE-Pipeline (`gitops#47`), siehe
[aar/2026-08-01-cve-pipeline-gitops47.md](aar/2026-08-01-cve-pipeline-gitops47.md).
## Ablauf
1. Wer baut, öffnet ein Issue aus der Vorlage **Deploy-Übergabe**
(`.gitea/ISSUE_TEMPLATE/deploy-uebergabe.yaml`).
2. Wer ausrollt, arbeitet die Prüfliste unten ab und deployt.
3. Wer ausrollt, hängt den **AAR** als Kommentar an dasselbe Issue
(Vorlage: [aar-vorlage.md](aar-vorlage.md)). Bei Befunden ab MEDIUM
zusätzlich als Datei unter [aar/](aar/).
## Die vier Punkte, die den Unterschied machen
### 1. Mengengerüst vor dem Deploy
Bei allem, was etwas erzeugt — Nachrichten, Alarme, Zeitreihen, Requests —
gehört die erwartete Anzahl beim **ersten** Lauf in die Übergabe. Geschätzt ist
in Ordnung, gemessen ist besser; welches von beidem, muss dabeistehen.
Der Grund: „ein Alarm pro Fund" ist eine völlig unauffällige Zeile im Code und
harmlos bei 5 Funden. Bei 126 ist es ein Ausfall. Der Unterschied steht nirgends
im Diff — er ergibt sich erst aus den Daten, gegen die das Ding läuft.
Eine Stichprobe reicht: drei repräsentative Elemente von 29 messen und
hochrechnen kostet Minuten und liefert die Größenordnung.
### 2. Verifikation dort, wo der Dienst liest
Ein grüner Linter belegt, dass eine Datei **gültig** ist — nicht, dass sie
**geladen** wurde. Diese beiden Aussagen sind bei Bind-Mounts, Caches und
Reload-Semantiken regelmäßig verschieden.
Also im Container prüfen, in der laufenden API, im tatsächlich geladenen
Regelwerk. Ein Blick auf die Platte beweist nichts über den Prozess.
### 3. Deploy-Kommando vollständig übergeben
Inklusive Reload-, Restart- und Recreate-Schritten. Ein `docker compose up -d`,
das `Running` meldet und dabei nichts aktiviert, ist der häufigste stille
Fehlschlag: kein Fehler, kein Log, falscher Zustand.
Konkret auf dem Monitoring-Stack (CFGMON): Einzeldatei-Mounts hängen am Inode,
`git pull` benennt beim Schreiben um und erzeugt damit einen neuen — der
Container zeigt danach weiter auf die alte Datei. Es braucht
`--force-recreate`. Details: `gitops#52`.
### 4. Zustellwege stumm schalten statt Deploy verschieben
Datensammlung und Außenwirkung lassen sich fast immer getrennt scharf schalten.
Wenn der Zustellweg das Risiko ist, wird **er** abgeklemmt — nicht der ganze
Deploy verschoben.
So läuft die Datensammlung ab sofort, das Dashboard steht, echte Zahlen
ersetzen die Schätzung, und die Entscheidung über die Zustellung fällt auf
Basis von Messwerten statt Vermutungen. Wichtig: die Stummschaltung gehört
committet und dokumentiert, sonst ist sie in zwei Wochen ein Rätsel.
## Prüfliste für den Ausrollenden
- [ ] Diff gelesen, nicht nur die Beschreibung
- [ ] Mengengerüst plausibel? Bei Zweifel an einer Stichprobe selbst messen
- [ ] Configs mit den jeweiligen Werkzeugen validiert (`promtool`, `amtool`,
`compose config`, `py_compile` …)
- [ ] Außenwirkung identifiziert — was verlässt beim ersten Lauf das System?
- [ ] Nach dem Deploy **im Container** verifiziert, dass die neue Config aktiv ist
- [ ] Geprüft, ob vorher gesunde Dinge noch gesund sind (keine stille Regression)
- [ ] AAR geschrieben, Folge-Issues angelegt, Stummschaltungen dokumentiert
## Abgrenzung
Das Verfahren gilt für Übergaben zwischen Personen. Wer baut **und** ausrollt,
braucht kein Issue — der AAR lohnt trotzdem, sobald es Befunde ab MEDIUM gab.