5.6 KiB
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.pys 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).