platform-docs/adr/0021-agent-intervall-debug-modus-health-monitoring.md
2026-08-26 10:07:15 +02:00

105 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 030 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).