Wenn der Server sich im Chat meldet: Alerts, die man wirklich liest
Im Beitrag über Logging, Monitoring und Alerts stand am Ende ein Satz, der mir seither nicht mehr aus dem Kopf geht: Am wirkungsvollsten laufen Alerts dorthin, wo Sie ohnehin hinschauen. Dieser Beitrag ist die Fortsetzung. Er handelt davon, wie das konkret aussieht, wenn ein Server nicht mehr nur E-Mails schreibt, die niemand öffnet, sondern sich im Chat meldet. Und er handelt von der Lektion, die ich in diesem Sommer beim Umbau meines eigenen Mailservers noch einmal gründlich gelernt habe: Die gefährlichste Meldung ist die, die ausbleibt. Und die zweitgefährlichste ist die, der man glaubt, ohne zu fragen, wer sie geschrieben hat.
Alles, was hier steht, läuft so bei mir. Nicht als Konzept, sondern als Cronjob. Hostnamen und Uhrzeiten in den Beispielen sind allerdings verfremdet, aus einem Grund, der weiter unten steht.
TL;DR - Die Kurzfassung
- E-Mail allein ist für zeitkritische Alerts meist der falsche Kanal: Sie geht im Postfach unter, und niemand merkt, wenn sie ausbleibt. Als zweiter, unabhängiger Weg ist sie trotzdem wertvoll.
- Ein Chat-Kanal (Mattermost, Matrix, XMPP) ist der Ort, an dem Sie ohnehin sind. Ein Webhook und ein kleines Skript reichen, damit der Server dort sprechen kann. Das Skript muss aber selbst merken, wenn es scheitert.
- Zwei Sorten Nachrichten: der Alarm (selten, sofort handeln) und der Tagesdigest (immer zur gleichen Zeit, auch wenn alles grün ist).
- Das Herzschlag-Prinzip: Jeder Melder meldet sich regelmäßig. Bleibt die Meldung aus, ist das selbst der Alarm. Ein zweiter Rechner prüft das, über einen Weg, der den ersten nicht braucht.
- Die Grenze: Der Herzschlag beweist, dass der Server lebt, nicht, dass er die Wahrheit sagt. Ein übernommener Server meldet weiter grün.
- Die häufigste Todesursache eines Melders ist nicht der Server, sondern die Umgebung: ein fehlender Pfad im Cron, ein Programm, das nie installiert wurde.
Warum das Postfach allein nicht reicht
Fast jedes Werkzeug auf einem Linux-Server kann E-Mails schicken. Der SMART-Dienst für die Festplatten, der RAID-Verbund, der Paketmanager, der tägliche Logbericht. Das ist historisch gewachsen und funktioniert technisch. Praktisch scheitert es an drei Stellen, und keine davon ist die Schuld der E-Mail.
Erstens: Die Mail wird nicht gelesen. Ein Postfach ist ein Ort für Menschen, die mir etwas wollen. Eine Systemmeldung zwischen Kundenanfragen und Newslettern hat dort die Aufmerksamkeit einer Werbebeilage. Nach zwei Wochen mit „alles in Ordnung"-Mails filtert man sie weg, und die eine, die anders ist, gleich mit.
Zweitens: Der Mailweg selbst ist ein System, das kaputtgehen kann. Auf einem frisch umgebauten Server fehlte bei mir das Kommandozeilenprogramm mail. Der Festplatten-Wächter smartd hätte einen Fehler gemeldet, er hätte nur niemanden erreicht. Ich habe das nicht durch einen Fehler bemerkt, sondern durch Nachprüfen. Das ist Glück, keine Methode.
Drittens: Niemand merkt, wenn nichts kommt. Wenn ein Wächter stirbt, schickt er keine Abschiedsmail. Die Stille sieht genauso aus wie „alles gut". Genau darum geht es weiter unten beim Herzschlag.
Das eigentliche Problem ist also nicht der Kanal, sondern dass der Kanal nicht gelesen wird oder am selben Ausfall hängt wie das, was er melden soll. Der Chat löst das erste. Für das zweite braucht es mehr, dazu unten.
Ein Alert, den niemand sieht, ist teurer als gar keiner. Er erzeugt das Gefühl von Sicherheit, ohne die Sicherheit.
Der Chat als Ort für den Server
Der Chat, den Sie den ganzen Tag offen haben, ist der natürliche Ort für Systemmeldungen. Bei mir ist das ein selbst betriebenes Mattermost, davor war es lange XMPP, das im Prinzip genauso funktioniert. Der Server bekommt einen eigenen Kanal, meinetwegen #infra, und dort schreiben die Maschinen. Nicht ich, die Maschinen.
Der Weg dorthin ist kurz. Mattermost, Matrix, Rocket.Chat und die meisten anderen Systeme bieten eingehende Webhooks: eine Adresse, an die man ein kleines JSON schickt, und der Text erscheint im Kanal. Aber das Skript, das den Webhook aufruft, ist selbst ein Melder, und für Melder gilt alles, was dieser Beitrag später fordert. Deshalb sieht meines nicht nach drei Zeilen aus, sondern nach fünfzehn:
#!/bin/bash
# /usr/local/bin/chat-notify: eine Zeile in den Infra-Kanal, mit Rückkanal bei Fehlschlag
# Die Webhook-Adresse ist ein Geheimnis (wer sie kennt, kann posten): nur root-lesbar ablegen,
# nicht im Skript, und in Mattermost auf genau diesen Kanal festnageln.
WEBHOOK=$(cat /etc/chat-notify/webhook) || { logger -t chat-notify "webhook-datei fehlt"; exit 1; }
command -v jq >/dev/null || { logger -t chat-notify "jq fehlt"; exit 1; }
TEXT="$*"
PAYLOAD=$(jq -n --arg text "$TEXT" '{text:$text}')
if ! curl -sS --fail --max-time 15 -o /dev/null \
-H 'Content-Type: application/json' -d "$PAYLOAD" "$WEBHOOK"; then
# Chat nicht erreichbar oder Webhook widerrufen: Spur hinterlassen, die der Digest findet
logger -t chat-notify "ZUSTELLUNG FEHLGESCHLAGEN: $TEXT"
echo "$(date -Is) $TEXT" >> /var/log/chat-notify-failed.log
exit 1
fi
Vier Dinge stecken darin, die in den üblichen Drei-Zeilen-Beispielen fehlen. --fail: curl behandelt HTTP-Fehler wie 404 oder 500 sonst nicht als Fehler, ein widerrufener Webhook scheitert lautlos, und das ist exakt das Problem, um das es hier geht. --max-time: Ein hängender Chat-Server darf den Aufrufer nicht ewig blockieren. Die Abhängigkeit von jq wird geprüft, denn ein Melder, der an einem fehlenden Programm stirbt, steht weiter unten auf meiner Totenliste, und der Ersatz sollte nicht dieselbe Krankheit haben. Und bei Fehlschlag bleibt eine Spur zurück, in einer Datei und im Systemlog, die der Digest am nächsten Morgen findet und meldet. Ein Alarm, der nicht zugestellt werden konnte, ist damit nicht weg, sondern verspätet.
Das jq in der Mitte sorgt übrigens dafür, dass Anführungszeichen oder Umlaute im Text das JSON nicht zerlegen. Wer das per String-Bastelei versucht, bekommt genau dann eine kaputte Nachricht, wenn die Fehlermeldung ein Sonderzeichen enthält, also im Ernstfall.
Für XMPP ist es dasselbe Muster mit einem anderen Werkzeug: sendxmpp oder ein kleines Python-Skript, das sich mit einem eigenen Bot-Konto anmeldet und eine Nachricht an Ihre Adresse schickt. Der Vorteil von XMPP: Es läuft auf jedem Gerät, braucht keine App eines Anbieters und ist seit zwanzig Jahren stabil.
Was aus einer Webapp heraus melden darf
Der Webhook ist nicht nur für den Server. Auch die Anwendung selbst kann ihn nutzen, mit Augenmaß. Ein Zahlungsvorgang, der dreimal hintereinander fehlschlägt. Ein nächtlicher Import, der mit null Datensätzen endet, obwohl gestern zwölftausend kamen. Ein Hintergrundjob, der seit zwei Stunden nicht fertig ist. Das sind Ereignisse, bei denen ich lieber jetzt Bescheid weiß als morgen früh.
Nur: nie synchron aus der Geschäftstransaktion heraus. Wenn Mattermost hängt, darf deshalb nicht der Zahlungsvorgang hängen. Entweder der Aufruf hat ein kurzes Timeout und ein Fehlschlag wird geloggt und ignoriert, oder die Meldung wandert in eine Queue und ein Hintergrundjob stellt sie zu. Und in die Meldung gehört, was der Empfänger zum Handeln braucht, nicht mehr: welcher Host, was ist kaputt, seit wann, was ist der nächste Schritt. Keine Tokens, keine Kundendaten, keine vollständigen Stacktraces. Chat-Verläufe werden lange aufbewahrt und von mehr Leuten gelesen als ein Logfile.
Was nicht in den Chat gehört: einzelne Fehler, die ein Nutzer ausgelöst hat. Dafür gibt es das Fehler-Tracking mit Sentry, das gleiche Fehler bündelt. Wenn jede Exception einzeln im Chat landet, ist der Kanal nach einer Woche stumm geschaltet, und dann ist er wertlos.
Zwei Sorten Nachrichten: Alarm und Digest
Der wichtigste Gedanke in diesem Beitrag ist eine Trennung. Es gibt Nachrichten, die sofort eine Handlung verlangen, und es gibt Nachrichten, die sagen: Ich lebe noch, und hier ist mein Befund. Beides in denselben Topf zu werfen, ist der Fehler, der Alarm-Müdigkeit erzeugt.
Der Alarm
Ein Alarm ist selten, eindeutig und umsetzbar. Bei mir lösen ihn aus: Ein RAID-Verbund, dem ein Mitglied fehlt. Eine Festplatte, deren SMART-Selbsttest nicht sauber war. Ein Rootkit-Scanner mit Fund. Ein Backup, das älter als ein Tag ist. Eine Zertifikatserneuerung, die fehlschlägt. Nicht mehr als eine Handvoll Bedingungen, und jede davon hat einen klaren nächsten Schritt, der in der Meldung steht.
Der Alarm bekommt ein eigenes Zeichen am Anfang der Nachricht, damit das Auge ihn im Kanal sofort vom Rest unterscheidet. Bei mir ist das ein rotes Warnlicht, der Digest bekommt einen grünen Haken. Das klingt nach Spielerei, ist aber der Unterschied zwischen „ich lese jede Meldung" und „ich sehe auf einen Blick, ob heute etwas ist".
Der Tagesdigest
Der Digest kommt jeden Morgen zur selben Minute, egal ob etwas ist oder nicht. Er fasst zusammen, was die nächtlichen Prüfungen ergeben haben: der Härtungs-Score des Systems, das Ergebnis der beiden Rootkit-Scanner, ob sich der Score gegenüber dem Vortag verändert hat, welche Prüfung neu bestanden oder verloren wurde, und ob ein Alarm in der Nacht nicht zugestellt werden konnte. Ein Beispiel, so wie er ankommt:
beispielhost security digest: alles grün (Mo 07.09.)
[OK] lynis: Score 85/100, 0 Warnungen, 3 Suggestions
Änderung zum Vortag: 84 -> 85, weggefallen: LOGG-2154
[OK] rkhunter: 0 Warnungen
[OK] chkrootkit: identisch mit Baseline
[OK] chat-notify: keine fehlgeschlagenen Zustellungen
Das liest sich in fünf Sekunden. Zwei Einordnungen dazu. „0 Warnungen" beim Rootkit-Scanner heißt: Das Werkzeug hat nichts gefunden. Es heißt nicht: Es gibt kein Rootkit. Die Scanner sind zusätzliche Signale, keine Unbedenklichkeitsbescheinigung. Und der eigentliche Wert des Digests liegt nicht im Inhalt, sondern in der Regelmäßigkeit: Wenn er morgens fehlt, stimmt etwas nicht. Dass ich das selbst bemerke, ist ein Symptom, kein Mechanismus. Der Mechanismus kommt jetzt.
Das Herzschlag-Prinzip: Die ausbleibende Meldung ist der Alarm
Ein Melder, der nur im Fehlerfall spricht, ist nicht von einem toten Melder zu unterscheiden. Beide schweigen. Deshalb gilt bei mir für jede Prüfung: Sie meldet sich regelmäßig, auch wenn alles in Ordnung ist. Und es gibt eine zweite Instanz, die nachsieht, ob die Meldung gekommen ist.
Konkret sieht das dreistufig aus:
- Jede Prüfung hinterlässt eine Spur. Der Rootkit-Scanner schreibt ein Log mit Zeitstempel, der Härtungs-Check einen Report, das Backup eine Marke, das Notify-Skript seine Fehlschläge. Nicht nur bei Fehlern, immer.
- Der Digest prüft das Alter dieser Spuren. Ist der letzte Scanner-Lauf älter als ein Tag, meldet der Digest nicht „alles grün", sondern „Scanner hat heute nicht gelaufen". Der Digest ist damit nicht nur Berichterstatter, sondern Wächter über die anderen Wächter.
- Ein zweiter Rechner prüft den Server. Der Server schreibt regelmäßig eine kleine Datei mit einem Zeitstempel auf ein Verzeichnis, das ein anderer Rechner im Netz sehen kann. Dort läuft ein Skript, das nur eine Frage stellt: Ist der Stempel älter als eine Stunde? Wenn ja, meldet dieser Rechner den Ausfall. Das fängt den Fall ab, in dem der Server so tot ist, dass er nicht einmal mehr seinen eigenen Ausfall melden kann.
Man nennt das im Englischen einen Dead Man's Switch. Es ist nicht die einzige Lösung für das Problem, externe Verfügbarkeitsprüfungen, Watchdogs und fertige Heartbeat-Dienste lösen es auf ihre Art ebenfalls, aber es ist ein sehr robustes Prinzip mit sehr wenig Technik: ein zweiter Rechner und ein Cronjob mit drei Zeilen.
Zwei Tote überwachen sich nicht gegenseitig
Mit einer Bedingung, die leicht übersehen wird: Der Alarmweg des Zweitrechners darf das überwachte System nicht voraussetzen. Wenn der Chat-Server auf derselben Maschine, im selben Netz oder hinter demselben Reverse Proxy läuft wie die überwachten Systeme, meldet beim Totalausfall niemand mehr irgendwas. Der Wächter sieht den Server sterben und schreibt es in einen Chat, der mitgestorben ist. Der Zweitrechner braucht deshalb einen eigenen Weg nach draußen: XMPP auf einem anderen Server, eine E-Mail über einen fremden Anbieter, im Zweifel eine SMS. Hier ist die E-Mail aus dem ersten Abschnitt wieder wertvoll, als zweiter Kanal, der am ersten Ausfall nicht hängt.
Die Grenze: Der Herzschlag beweist Leben, nicht Ehrlichkeit
Bis hierher meldet alles vom überwachten System aus über sich selbst. Der Digest, der „rkhunter: 0 Warnungen" schreibt, läuft auf derselben Maschine, die der Rootkit-Scanner prüft. Das ist eine Vertrauensgrenze, und sie muss benannt werden: Ein Angreifer, der den Server übernommen hat, übernimmt damit auch den Meldeweg. Er lässt den Herzschlag weiterlaufen und schickt grüne Digests. Schweigen wäre verdächtig, „alles grün" ist es nicht.
Das ist exakt dieselbe Grenze wie beim Push-Backup im Backup-Beitrag: Wer die Maschine hat, hat ihre Zugänge. Dort den Backup-Schlüssel, hier die Webhook-Adresse. Und die Antwort ist dieselbe: Pull. Der Zweitrechner fängt den toten Server. Wer auch den lügenden fangen will, lässt den Zweitrechner die Spuren selbst holen, Logs, Prüfsummen, Paketlisten, und selbst vergleichen, statt dem Bericht des Beobachteten zu glauben. Das ist der Schritt von Monitoring zu Integritätsprüfung, und er ist deutlich mehr Arbeit. Man muss ihn nicht bauen. Man muss wissen, dass die Konstruktion bis hierher ihn nicht ersetzt.
Aus derselben Überlegung kommt die Verfremdung in diesem Beitrag: Wer Hostnamen, Werkzeuge und den exakten Zeitplan seiner Überwachung veröffentlicht, sagt einem Angreifer, wann niemand hinsieht und wann der Scanner läuft. Für einen Demonstrationsaufbau ist das egal. Für die eigene Infrastruktur ist es ein Geschenk, das nichts kostet, es nicht zu machen.
Wie Melder wirklich sterben
Beim Umbau meines Mailservers in diesem Sommer habe ich fünf Melder gefunden, die tot waren, ohne dass ich es wusste. Keiner davon starb an einem Serverfehler. Alle starben an der Umgebung. Die Liste ist lehrreich, weil sie so banal ist:
- Der Pfad fehlte. Cron startet Skripte mit einem sehr kurzen Suchpfad, in dem Verwaltungsprogramme wie
smartctlodermdadmnicht liegen. Das Skript lief, fand die Programme nicht, und meldete leere Felder. In der Shell von Hand ausgeführt funktionierte alles, weil dort der Pfad stimmt. Die Lösung ist eine einzige Zeile am Anfang der Crontab, die den Pfad setzt. - Das Mailprogramm fehlte. Zwei Wächter wollten per Mail alarmieren und hätten es nie gekonnt. Deshalb prüft das Notify-Skript oben seine eigene Abhängigkeit.
- Der Zeitplan stimmte nicht. Ein RAID-Check war auf „zweiter Sonntag im Monat" gedacht und stand in der Crontab so, dass er an jedem Tag zwischen dem 8. und 14. lief, der auf einen Sonntag oder den passenden Wochentag fiel. Ein klassischer Cron-Fehler, weil Tag-des-Monats und Wochentag mit ODER verknüpft werden, nicht mit UND.
- Die Prüfung meldete „sauber", ohne zu prüfen. Ein Skript rief einen Scanner auf, filterte dessen Ausgabe nach Funden, und meldete bei null Funden „clean". Als der Scanner wegen des Pfad-Problems gar nicht startete, war die Ausgabe leer, null Funde, „clean". Zwei Monate lang.
Der letzte Punkt ist der wichtigste. Ein Prüfskript muss unterscheiden zwischen „ich habe geprüft und nichts gefunden" und „ich konnte nicht prüfen". Das erreicht man, indem man den Rückgabewert des Scanners auswertet, nicht nur seine Ausgabe. Als zusätzliche Plausibilisierung hilft die Laufzeit: Ein Rootkit-Scanner, der in null Sekunden fertig ist, hat wahrscheinlich nicht gescannt. Das ist ein Indiz, kein Beweis, bei kleinem Prüfumfang kann etwas auch schnell fertig sein.
Testen Sie Ihre Melder so, wie sie im Ernstfall laufen: aus dem Cron heraus, nicht aus Ihrer Shell. Die beiden Welten sind verschieden.
Ein Aufbau, der sich bewährt hat
So sieht der Wochenplan auf meinem Server heute aus, gekürzt und mit verschobenen Uhrzeiten. Er ist nicht groß, aber jeder Eintrag hat einen Herzschlag:
| Wann (illustrativ) | Was | Kanal |
|---|---|---|
| täglich nachts | Rootkit-Scanner, Vergleich mit Baseline | Spur für den Digest; Mail als zweiter Kanal bei Abweichung, weil sie am Chat nicht hängt |
| täglich nachts | Härtungs-Check (lynis) | Spur für den Digest |
| täglich morgens | Digest: fasst alles zusammen, prüft das Alter der Spuren und fehlgeschlagene Zustellungen | Chat, immer |
| wöchentlich | RAID-Status und SMART je Festplatte | Chat, immer, Alarm bei Auffälligkeit |
| viertelstündlich | Herzschlag-Datei für den Zweitrechner | Datei im Netz; der Zweitrechner alarmiert über einen eigenen Weg |
| wöchentlich | Erinnerung an den Wochen-Check von Hand (15 Minuten) | Chat |
Der letzte Eintrag ist Absicht. Automatik ersetzt den Blick nicht, sie macht ihn nur billig. Fünfzehn Minuten pro Woche: den Logbericht querlesen, nachsehen, ob alle Reports gekommen sind, die Sperrliste der Firewall plausibilisieren, den Plattenplatz ansehen. Der Chat erinnert mich daran, damit es nicht am Vorsatz scheitert.
Ein pragmatischer Einstieg
- Einen Kanal anlegen und einen Webhook erzeugen, auf den Kanal festgenagelt, Adresse root-lesbar abgelegt. Zehn Minuten.
- Das Notify-Skript von oben ablegen und einmal von Hand testen, auch den Fehlerfall mit einer falschen Adresse. Zehn Minuten.
- Den ersten Digest bauen: ein Skript, das morgens Plattenplatz, Backup-Alter, fehlgeschlagene Zustellungen und die Ausgabe der vorhandenen Prüfungen in fünf Zeilen zusammenfasst und immer sendet. Eine Stunde.
- Alarme definieren, höchstens fünf, jeder mit klarer Handlung in der Meldung.
- Den Herzschlag einrichten: Zeitstempel schreiben, zweiter Rechner prüft, mit eigenem Alarmweg. Eine halbe Stunde.
- Alles einmal aus dem Cron heraus testen, nicht aus der Shell. Und dann in den Kalender: Vierteljährlich einen Melder absichtlich abschalten und nachsehen, ob der Ausfall gemeldet wird. Das ist der Restore-Test für die Überwachung.
Checkliste: Spricht Ihr Server mit Ihnen?
- Kommen Systemmeldungen dort an, wo Sie täglich hinschauen, oder in einem Postfach-Ordner?
- Merkt Ihr Melde-Skript, wenn die Zustellung scheitert, und hinterlässt es eine Spur?
- Gibt es eine regelmäßige Meldung, die auch bei „alles gut" kommt?
- Würden Sie merken, wenn diese Meldung ausbleibt? Wer oder was merkt es, und über welchen Weg?
- Läuft der Chat auf derselben Infrastruktur wie das, was er melden soll?
- Unterscheiden Ihre Prüfskripte zwischen „nichts gefunden" und „nicht geprüft"?
- Haben Sie jeden Melder einmal aus dem Cron heraus getestet, nicht nur von Hand?
- Wissen Sie, welche Ihrer Meldungen ein übernommener Server fälschen könnte?
- Wann haben Sie zuletzt absichtlich etwas kaputtgemacht, um zu sehen, ob der Alarm kommt?
Fazit: Stille ist nur dann gut, wenn sie beobachtet wird
Ein Server, der sich im Chat meldet, ist kein Luxus und keine Spielerei. Es ist die günstigste Art, aus „ich hoffe, es läuft" ein „ich weiß, dass es läuft" zu machen. Der Webhook ist in einer Viertelstunde eingerichtet. Die eigentliche Arbeit steckt in der Disziplin: wenige Alarme, ein verlässlicher Digest, ein Melde-Skript, das seine eigenen Fehlschläge kennt, und ein Herzschlag, der die Stille überwacht, über einen Weg, der den Server nicht braucht. Und im Wissen, wo das aufhört: Der Herzschlag sagt, dass der Server lebt. Ob er die Wahrheit sagt, muss jemand anderes prüfen.
Ich habe in diesem Sommer fünf tote Melder gefunden, weil ich nachgesehen habe, nicht weil sie sich gemeldet hätten. Seitdem meldet sich jeder von ihnen jeden Tag, und wenn einer schweigt, sagt es mir ein anderer. Das ist der ganze Trick.
Sie betreiben einen Server oder eine Webapp und erfahren von Problemen immer noch zuerst von Nutzern? Schreiben Sie mir kurz, was bei Ihnen läuft. Ich sehe mir an, welche Melder Sie haben, welche davon wirklich leben, und wie Sie mit wenig Aufwand einen Kanal bekommen, der Ihnen morgens in fünf Sekunden sagt, ob alles in Ordnung ist.
Weitere interessante Artikel
Logging, Monitoring & Alerts: Webapps im Betrieb sichtbar machen
Strukturiertes Logging, Monitoring und sinnvolle Alerts: wie Ihre Webapp im Betrieb sichtbar wird und Sie Fehler sehen, bevor Nutzer …
Background-Jobs in PHP: Queues, Cron & lange Prozesse
Background-Jobs in PHP: Queue-Systeme (Redis, RabbitMQ, Database), Cron-Jobs, Worker-Prozesse, Supervisord.
Debian Server Setup: Von der Installation bis zum Production-Ready
Debian-Server aufsetzen: von der Installation bis zum Production-Ready-System mit Härtung, Updates, Monitoring und Backup, Schritt für …