From a48ef99739265ee0595b7ef95653ca9640ef809d Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Wed, 26 Aug 2026 10:07:15 +0200 Subject: [PATCH] ADR-0021: Agent-Intervall 6h+Jitter, Debug-Modus, Health-Monitoring Co-Authored-By: Claude Opus 5 --- ...intervall-debug-modus-health-monitoring.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 adr/0021-agent-intervall-debug-modus-health-monitoring.md diff --git a/adr/0021-agent-intervall-debug-modus-health-monitoring.md b/adr/0021-agent-intervall-debug-modus-health-monitoring.md new file mode 100644 index 0000000..c2191fa --- /dev/null +++ b/adr/0021-agent-intervall-debug-modus-health-monitoring.md @@ -0,0 +1,104 @@ +# ADR-0021: Agent-Standardintervall 6h+Jitter, Debug-Modus, Health-Monitoring + +**Status:** Beschlossen +**Datum:** 26.08.2026 + +## Kontext + +Der Provisioning-Agent (`provisioning-agent/agent.py`) checkte bislang alle +5 Minuten ein (`AGENT_POLL_INTERVAL_SECONDS`, fest über eine +Server-Umgebungsvariable) — sinnvoll für die Entwicklungsphase, aber bei +einer wachsenden Geräteflotte unnötig häufig für den Normalbetrieb. Es gab +außerdem keine Möglichkeit, ein einzelnes Gerät gezielt kurzfristig +engmaschiger abzufragen (z.B. um eine frisch zugewiesene Rolle zeitnah zu +beobachten), ohne das Intervall global zu ändern. Und: der Agent sammelte +nur einmalig bei der Installation eine statische Hardware-Inventur +(`hardware_snapshots`, siehe `10_hardware.sh`) — es gab keine periodische +Sicht auf den *aktuellen* Gerätezustand (Diskbelegung, Auslastung, Uptime). + +Der Boot-Checkin selbst (Agent meldet sich direkt beim Start) war entgegen +der ursprünglichen Annahme bereits vorhanden — `agent.py:main()` ruft +`run_once()` grundsätzlich vor der ersten `sleep()`-Anweisung auf, daher +hier nicht angefasst. + +## Entscheidung + +**Standardintervall:** `AGENT_POLL_INTERVAL_SECONDS`-Default von 300 auf +21600 (6 Stunden) angehoben. **Jitter** kommt bewusst aus dem Agenten +selbst, nicht vom Server (der kennt keinen sinnvollen Zufallszustand pro +Gerät): `agent.py`s `main()`-Loop addiert 0–30 Minuten zufällig auf das +zurückgelieferte Intervall, aber **nur** wenn es „groß" ist +(`JITTER_THRESHOLD_SECONDS = 3600`) — der kurze Debug-Modus (60s) und der +Fallback-Retry bei Fehlern (`FALLBACK_INTERVAL_SECONDS = 60`) bleiben +bewusst unverjittert, sonst würde Jitter genau den Zweck des schnellen +Debug-Zyklus untergraben. + +**Debug-Modus:** neue Spalte `devices.debug_mode_until` (Migration +`0022_agent_debug_mode_und_health.sql`) ist der einzige Zustand — kein +separates Enum, kein Cron-Job zum Zurücksetzen. `agent_checkin()` +vergleicht bei jeder Anfrage `debug_mode_until` mit der aktuellen Zeit und +liefert `poll_interval_seconds` entsprechend (60s im Debug-Modus, sonst +6h) — das automatische Zurückfallen nach Ablauf passiert rein durch +Zeitvergleich, ohne dass irgendetwas aktiv zurückgesetzt werden muss. +Aktivieren (`POST /api/v1/devices/{id}/debug-mode`, setzt `now()+6h`) und +Deaktivieren (`DELETE`, setzt `NULL`) sind eigene, dünne Endpunkte, gegen +`require_service_token()` wie üblich — Organisationszugehörigkeitsprüfung +bleibt Aufgabe der aufrufenden Kundenplattform +(`find_device_in_organization()`), nicht dieses Endpunkts. Bewusst **kein +zusätzliches Rechte-Gate**: jeder eingeloggte Nutzer der eigenen +Organisation darf den Debug-Modus für die eigenen Geräte umschalten, +gleiches Muster wie die bestehenden Auftragskatalog-Aktionen. + +**Health-Monitoring:** sechs neue, nullable `devices`-Spalten +(`health_disk_used_percent`, `health_ram_used_percent`, +`health_uptime_seconds`, `health_load_1m/5m/15m`, `health_collected_at`) — +halten bewusst nur den jeweils letzten Stand, keine wachsende Historie +(konsistent mit dem Plattenplatz-Bewusstsein an anderer Stelle, siehe +`iso_builds`-Aufräumung). Der Agent sammelt die Werte bei jedem Checkin +rein über Python-Stdlib (`shutil.disk_usage("/")`, `/proc/meminfo`, +`/proc/uptime`, `os.getloadavg()`) — keine neue Abhängigkeit, jeder +Einzelwert einzeln per `try/except`, damit ein fehlschlagender Wert (z.B. +Load Average auf einem ungewöhnlichen System) die übrigen nicht +verhindert. Bewusst ausgeschlossen: Temperatur/SMART-Werte — hätten +zusätzliche Pakete/Berechtigungen gebraucht, war nicht Teil der +25./26.08.-Planung. + +`agent_checkin()` schreibt `agent_last_checkin` und die Health-Felder in +einem kombinierten `UPDATE ... RETURNING debug_mode_until` statt separater +Round-Trips. Fehlt das `health`-Objekt in einem einzelnen Checkin (z.B. +Sammel-Fehler im Agenten), bleiben die zuvor gespeicherten Health-Werte +unverändert stehen statt auf `NULL` zurückzufallen — ein `CASE WHEN` +pro Spalte statt eines bedingungslosen Überschreibens. + +`fetch_devices_for_organization()`/`fetch_all_devices()` liefern +`debug_mode_until` und `health` jetzt mit aus (gemeinsame SQL-Spaltenliste +`GERAETE_HEALTH_SPALTEN_SQL`, gemeinsame Aufbereitung +`_health_felder_aus_row()`) — Kundenplattform braucht dafür keinen +zusätzlichen Request, das Device-Dict aus der ohnehin schon vorhandenen +Besitz-Validierung (`find_device_in_organization()`) trägt die Felder +direkt mit. + +**Kundenplattform-UI:** neue Sektion „Debug-Modus" auf der +Auftragskatalog-Seite (Kunden- **und** Admin-Bereich, gleiches +`zurueck_url`-Präfix-Muster wie die bestehenden Select/Deselect-Formulare +aus ADR-0015), neue Sektion „Aktueller Zustand (letzter Checkin)" auf der +Hardware-Seite (klar getrennt von der bestehenden statischen +Install-Zeit-Inventur), Debug-Badge in beiden Geräte-Listen, und „Zuletzt +gemeldet" jetzt auch auf der Log-Seite (vorher nur in der Geräteliste +sichtbar, nicht in der Detail-Historie). + +## Konsequenzen + +Migration `0022_agent_debug_mode_und_health.sql` (8 neue `devices`-Spalten, +alle nullable, kein Backfill nötig). Kein neuer Cron/Scheduler-Prozess für +den Debug-Modus-Rückfall — reine Zeitvergleichslogik im bestehenden +Checkin-Pfad. + +Live auf anode verifiziert: `agent_checkin()` mit Health-Payload +durchlaufen, `debug_mode_until` korrekt in `poll_interval_seconds` +übersetzt (Debug-Endpunkt aktiviert → nächster Checkin liefert 60s, nach +Ablauf/Deaktivieren wieder 21600s). `collect_health()` gegen ein echtes +Linux-System (enterprise) getestet, liefert plausible Werte ohne +Zusatzpaket. UI-Änderungen lokal mit gemocktem Kontext gerendert +(Debug-Toggle in beiden Zuständen, Health-Sektion mit/ohne Daten, Log-Seite +mit Zeitstempel).