Prometheus-Alerts: Lektionen aus sechs Monaten Fehlalarmen
Ein Workshop über Regeln, die nur wecken, wenn es zählt: Metriken prüfen, bevor man Schwellwerte schreibt, predict_linear statt starrer Limits, Fallen bei Log-Alerts und eine zweistufige KI-Pipeline, die JSON in einen menschlichen Satz übersetzt.

Drei Uhr nachts, das Handy vibriert, nichts brennt
Die erste Version meiner Alert-Regeln hat mich regelmäßig geweckt. Nicht weil der Server ausfiel — weil die Regeln naiv waren. Ein Speicher-Ausschlag beim nächtlichen Scan der Medienbibliothek sah für sie wie ein Leck aus. Ein Neustart-Zähler, der einmal zuckte, alarmierte für immer weiter. Und eine Metrik, der ich vertraute, maß etwas anderes, als ich dachte.
Heute laufen auf dem Server über siebzig Regeln, und in den meisten Wochen meldet sich keine davon ohne Grund. Dieser Beitrag ist ein Workshop: Was genau hat sich zwischen der Version geändert, die mich weckte, und der Version, die nur noch aus echtem Anlass weckt. Ein Fehlalarm ist nicht harmlos — jeder einzelne lehrt dich, Benachrichtigungen zu ignorieren, und ein Monitoring, das niemand liest, existiert nicht.
Bevor du eine Regel schreibst, schau dir die Metrik an
Die teuerste Lektion der ganzen sechs Monate: Eine Metrik misst nicht immer das, was ihr Name suggeriert. Bevor du einen Schwellwert schreibst, tipp die Metrik in die -Konsole und prüfe drei Dinge: ob sie unter diesem Namen überhaupt existiert, welche Labels sie trägt und ob ihre Werte zu der Realität passen, die du anderweitig kennst.
Der Klassiker dieser Falle: container_memory_usage_bytes enthält den Dateisystem-Cache und bläht den „Verbrauch“ um Dutzende Prozent auf — ein Container sah aus, als fräße er 433 MB, während er real mit 326 MB arbeitete. Die richtige Metrik ist container_memory_working_set_bytes. Die ganze Geschichte steht im Case Study zum Monitoring-Stack; hier bleibt nur die Workshop-Regel: Ein Schwellwert auf einer Metrik, die du dir nicht angesehen hast, ist Raten mit ernster Miene.
for: — ein Alert ist ein Zustand, kein Ausschlag
Die zweite Korrektur mit dem größten Effekt war die einfachste. Ein Alert ohne for:-Klausel feuert bei der ersten passenden Probe — also bei jedem Ausschlag, jedem Scrape-Neustart und jedem kurzen Netzwerk-Schluckauf.
Die Verteilung über meine siebzig-plus Regeln spricht für sich: Die meisten Regeln warten 5 Minuten, kritische Verfügbarkeitsregeln 2 Minuten, langsame Trends eine Stunde (die drei häufigsten Einstellungen sind 25 Regeln bei fünf Minuten, 14 bei zwei und 9 bei einer Stunde; der Rest verteilt sich von einer halben Minute bis zu sechs Stunden). Es gibt auch eine Ausnahme mit Geschichte: Der Alert für hohen Container-Speicher wartet eine halbe Stunde, weil der nächtliche Scan der Medienbibliothek den Verbrauch legitim für Dutzende Minuten über die Schwelle heben kann — und die erste Version dieser Regel alarmierte jede Nacht zur selben Zeit.
- alert: ServiceDown
expr: up == 0
for: 2m # ein Scrape-Neustart ist kein Ausfall
- alert: ContainerHighMemory
expr: ... # working_set > 85% des Limits
for: 30m # der nächtliche Bibliotheks-Scan hebt den Speicher legitim an
Die Wahl von for: ist immer derselbe Kompromiss: zu kurz — Ausschläge wecken dich; zu lang — du erfährst es als Letzter. Eine praktische Heuristik: Wie lange kann dieses Problem dauern, bevor es wirklich etwas kaputt macht? Für einen toten Reverse Proxy: Minuten. Für einen Speichertrend — Stunden.
predict_linear: alarmiere, bevor es passiert, nicht wenn es zuckt
Ein starrer Speicher-Schwellwert hat ein eingebautes Dilemma: zu niedrig — er alarmiert im Normalbetrieb; zu hoch — er weckt dich, wenn es schon zu spät ist. Für Speicherlecks funktioniert ein Trend besser: predict_linear() extrapoliert die letzten Stunden und fragt, ob der Container beim aktuellen Tempo sein Limit erreicht.
Meine Leck-Erkennungsregel hat drei Versionen durchlaufen, und jede Korrektur war eine Lektion:
- alert: ContainerMemoryLeak
expr: |
predict_linear(container_memory_working_set_bytes{...}[6h], 6*3600)
> container_spec_memory_limit_bytes * 0.95
and container_memory_working_set_bytes{...}
/ container_spec_memory_limit_bytes > 0.5
and delta(container_memory_working_set_bytes{...}[1h]) > 50 * 1024 * 1024
Drei Bedingungen mit and verknüpft, weil jede für sich allein lügt. Die Vorhersage allein, auf einem Drei-Stunden-Fenster, reagierte auf natürliche Oszillationen — ein Container, der zwischen 50 und 65% seines Limits pendelte, sah zeitweise wie eine Rakete aus. Das auf sechs Stunden erweiterte Fenster beruhigte den Trend, die Bedingung „nutzt bereits mehr als die Hälfte des Limits“ siebte frisch gestartete Container aus, und die Anforderung echten Wachstums (über 50 MB pro Stunde) — jene, die einfach hoch stehen und nichts Böses tun.
Eine Alert-Regel ist kein einmaliger Eintrag, sondern Code, der getunt wird. Jeder Fehlalarm sollte eine Spur hinterlassen: einen Kommentar in der Regel, was korrigiert wurde und warum. Meine besten Regeln tragen drei datierte Korrekturen in den Kommentaren — und genau deshalb sind sie die besten.
Log-Alerts: drei Fallen, die in den Tutorials fehlen
Metriken sehen nicht alles — ein vom OOM-Killer getöteter Prozess hinterlässt seine Spur in den Logs, nicht auf einem Diagramm. Dafür gibt es Regeln in . Und hier warteten Fallen ganz anderer Art als in Prometheus, denn Musterabgleich auf Text hat seine eigenen Arten von Boshaftigkeit:
Wortgrenzen. Das Muster oom matcht auch „Room“ und „Zoom“ — jeder Chat oder Gerätename in den Logs kann einen Speicher-Alert auslösen. Die Rettung: \boom\b. Ähnlich ist „killed“ zu breit — es fängt harmlose Sätze in Anwendungslogs; man muss konkrete Phrasen wie „OOM-killer“ oder „was killed“ matchen.
Die Rückkopplungsschleife. Meine Regel, die Container-Logs nach OOM-Mustern durchsuchte, fand sie… in Lokis eigenen Logs, die diese Muster ja verarbeiten. Der Alert feuerte sich selbst, endlos. Seitdem trägt jede Log-Regel den Ausschluss container_name!="loki".
Kenne deine Labels. Eine Regel ist nur so gut wie ihr Selektor. Bei mir heißt der Job containerlogs, und Containernamen kommen ohne führenden Schrägstrich — aber mit einer anderen promtail-Konfiguration würden dieselben Regeln nichts matchen und für immer schweigen. Bevor du einer Log-Regel vertraust, prüfe in der Konsole, dass ihr Selektor überhaupt etwas zurückgibt. Stille kann ein Konfigurationsfehler sein, keine Gesundheit.
Der Validator winkt durch, die Semantik lügt: was ein Audit fand
Nach einem halben Jahr Feintuning habe ich alle Regeln komplett auditiert — nicht weil etwas kaputt war, sondern weil alles funktionierend aussah. Gefunden habe ich Fehler, die kein Syntax-Validator je fangen wird, weil syntaktisch alles stimmte.
Eine Unterdrückungsregel, tot vom ersten Tag an. Alertmanager kann verwandte Alerts unterdrücken: Wenn ein ganzer Dienst ausfällt, will ich keinen separaten Alarm über seine hohe CPU-Last. Meine Regel nutzte target_match mit dem Wert 'HighCPUUsage|HighMemoryUsage' — und target_match vergleicht exakt, Zeichen für Zeichen. Die Regex-Variante heißt target_match_re. Ein Literal mit | ist ein völlig gültiger String, also schwieg der Validator, und die Regel unterdrückte ein halbes Jahr lang nichts. Obendrein hieß der Alert, den sie treffen sollte, in Wirklichkeit anders. Zwei Fehler in vier Zeilen Konfiguration, null Warnungen.
Ein Ausfall, drei kritische Benachrichtigungen. Alertmanager gruppiert Benachrichtigungen unter anderem nach Alert-Namen — jeder Name ist eine eigene E-Mail. Über sechs Monate hinweg habe ich Verfügbarkeitsregeln ergänzt und nie bemerkt, dass ein einziger ausgefallener Metrik-Exporter die Bedingungen von drei verschiedenen Regeln mit drei verschiedenen Namen erfüllte. Ein Ausfall, drei kritische Benachrichtigungen über dasselbe Ereignis. Die Korrektur hat zwei Teile: 1:1-Duplikate flogen schlicht raus, und wo ein dedizierter Alert eine bessere Beschreibung trägt als der generische, unterdrückt der dedizierte jetzt den generischen (Inhibition mit equal auf einem gemeinsamen Job- oder Instanz-Label).
Der Ausschluss von etwas, das nicht existiert. Die generische Verfügbarkeitsregel schloss den Job blackbox-http aus — den es nicht gab; er war irgendwann umbenannt worden, der Ausschluss blieb zurück. Ein toter Ausschluss ist schlimmer als keiner: Er suggeriert, der Fall sei behandelt, also schaut dort niemand mehr hin.
Operative Lehren aus dieser Runde: Routing lässt sich verhaltensbasiert testen — amtool config routes test mit den Labels eines Beispiel-Alerts zeigt, welche Empfänger er erreicht. Unterdrückungen brauchen eine eigene Prüfung: ob die Alert-Namen und Labels in source_match, target_match und equal tatsächlich in den Regeln vorkommen — und in einer Testumgebung zusätzlich an einem Paar synthetischer Alerts. Dazu ein Deployment-Detail, das mich eine Viertelstunde kostete: Nach dem Editieren einer Datei, die als einzelner Bind-Mount in einen Container eingehängt ist, sieht der Container weiter die alte Version, weil der Editor den Inode austauscht — es hilft nur ein Container-Neustart, kein Reload.
Ein Validator prüft Syntax, nicht Absicht. Der Alert-Name in einer Unterdrückungsregel, der Job in einem Ausschluss, das Label in einem Selektor — das sind alles Strings, die mit dem Rest der Konfiguration übereinstimmen müssen, und kein Werkzeug wacht über diese Übereinstimmung. Ein vierteljährliches Semantik-Audit (existieren die Namen noch, unterdrücken die Unterdrückungen wirklich) findet Fehler, die man sonst nicht sieht, weil sie sich als zu viele Benachrichtigungen zeigen, nicht als zu wenige.
Das Audit brachte auch einen Fund jenseits des Alertings selbst: Loki schrieb seinen Index von Anfang an in einem Format, das inzwischen als veraltet markiert wurde (boltdb-shipper). Die Migration auf den Nachfolger (TSDB) erwies sich als sicherer, als sie klingt — ein neuer Eintrag in schema_config mit einem Datum in der Zukunft, alte Daten liest bis zum Ende der Retention die alte Engine, und der Rollback besteht darin, den Eintrag vor diesem Datum wieder zu entfernen.
Die letzte Meile: von JSON zum menschlichen Satz
Selbst ein gut getunter Alert erreicht das Handy als Nachricht von Roboter zu Roboter: Labels, Werte, Bezeichner. Das letzte Stück der Pipeline übernehmen bei mir zwei lokale Sprachmodelle über — und die Arbeitsteilung zwischen ihnen ist eine eigene Lektion, gemessen statt vermutet. Wobei nicht jeder Alert die Modelle überhaupt weckt: Bekannte, wiederkehrende Muster tragen eine fest zugewiesene Klasse und eine fertige Nachricht, und nur die nicht offensichtlichen erreichen die KI — auf einer CPU ohne GPU eine vernünftige Sparsamkeit.
Der Plan war einfach: Ein Modell klassifiziert die Priorität und schreibt die Nachricht. Die Tests sagten etwas anderes. phi4-mini klassifizierte fehlerfrei und schnell, aber sein Polnisch war verkrüppelt. Bielik — das polnische Modell von SpeakLeash — schrieb natürlich, drückte aber die Klassifikation platt: Alles landete in der Mitte der Skala, eine kritische Festplatte eingeschlossen. Keins konnte beides zugleich. Die Lösung: eine zweistufige Pipeline — phi4-mini klassifiziert (Stufe 1), Bielik schreibt die endgültige Nachricht auf Polnisch (Stufe 2).
Das Rollout brachte eigene Fallen mit. Über den rohen Generate-Endpoint warf Bielik den Prompt mitunter als Echo zurück, statt zu antworten — geholfen haben erst Chat-Aufrufe mit Beispielantworten im System-Prompt (few-shot); ohne sie war die Zuverlässigkeit eine Lotterie, mit ihnen bestanden alle Tests. Ein einzelnes Beispiel wiederum verankerte den Stil — das Modell kopierte dessen Schlusssatz in jede Nachricht; zwei Beispiele mit unterschiedlichen Enden lösten das. Und die oberste Regel der ganzen Stufe: fail-open. Wenn Bielik versagt oder ein Echo liefert (ein einfacher Inhalts-Guard fängt das), geht der Alert mit einer Ersatznachricht raus. Schöne Sprache ist optional — Zustellung nicht.
Die KI steht nicht ohne Grund am Ende der Pipeline: Sie übersetzt gute Alerts in menschliche Sprache, aber schlechte Regeln repariert sie nicht. Die Investitionsreihenfolge ist eindeutig — zuerst Metriken und Regeln, dann Zustellung, Sprachkosmetik zuletzt. In umgekehrter Reihenfolge bekommst du wunderschön formulierte Fehlalarme.
Der Zustellweg selbst — wer benachrichtigt wird, über welchen Kanal, was in Ruhezeiten passiert — steht im Beitrag über n8n-Muster, die Modellwahl für CPU ohne GPU im Beitrag über das lokale LLM.
Checkliste vor dem Ausrollen einer Regel
- Sieh dir die Metrik in der Konsole an — sie existiert, sie trägt diese Labels, sie misst, was du denkst.
- Zähler? Vergleiche den Zuwachs (
increase(...[5m]) > 0), nie den Rohwert — sonst erlischt der Alert nie. - Füge ein
for:hinzu, passend zur Frage „wie lange darf dieses Problem dauern, bevor es wehtut“. - Trend statt Schwellwert, wo Probleme langsam anwachsen —
predict_linearmit Bedingungen, die Oszillationen abschneiden. - Log-Regeln: Wortgrenzen in Mustern, die eigenen Logs des Logging-Systems ausschließen, ein geprüfter Selektor.
- Die Regelbeschreibung = der erste Diagnoseschritt, keine Wiederholung des Namens. Um drei Uhr nachts dankst du dir für ein fertiges Kommando.
- Jeder Fehlalarm korrigiert die Regel und hinterlässt einen datierten Kommentar. Stummschalten ist keine Korrektur.
- Einmal im Quartal ein Semantik-Audit — existieren die Alert-Namen in Unterdrückungsregeln und die Jobs in Ausschlüssen noch; Routing mit
amtool config routes testan einer Stichprobe von Labels prüfen.
Sechs Monate später weckt mich das Monitoring ein paarmal pro Quartal — und jedes Mal zu Recht. Das ist das richtige Maß: nicht die Zahl der Regeln oder Dashboards, sondern das Vertrauen in das vibrierende Handy mitten in der Nacht.
Häufige Fragen
Womit sollte ich beim Alerting im Homelab anfangen?
Mit drei Regeln, die echte Ausfälle fangen: ein Dienst antwortet nicht, der Speicherplatz geht zur Neige, ein Container startet in einer Schleife neu. Jede mit einer vernünftigen Verzögerung (for: 2-5 Minuten) und einer Beschreibung, was zu tun ist. Erst wenn diese drei keine Fehlalarme mehr produzieren, lohnt es sich, weitere hinzuzufügen.
Warum erlischt mein Alert nicht, obwohl das Problem vorbei ist?
Meistens, weil die Regel auf einen Zähler schaut statt auf dessen Zuwachs. Prometheus-Zähler wachsen nur — eine Bedingung wie Neustarts > 0 bleibt für immer wahr. Vergleiche den Zuwachs in einem Zeitfenster (increase über die letzten 5 Minuten), dann erlischt der Alert zusammen mit dem Problem.
Wie reduziere ich Fehlalarme, ohne alles stummzuschalten?
Drei Hebel: for (ein Alert ist ein anhaltender Zustand, kein Ausschlag), die richtige Metrik (prüfe, was sie wirklich misst, bevor du einen Schwellwert schreibst) und Trendvorhersage statt starrer Schwelle, wo Probleme langsam anwachsen. Stummschalten ist der letzte Ausweg — eine Regel, die du regelmäßig stummschaltest, ist schlicht falsch geschrieben.
Ergibt KI im Alert-Pfad ohne GPU Sinn?
Ja, denn es ist eine Hintergrundaufgabe — niemand wartet interaktiv auf die Antwort. Ein kleines Modell klassifiziert die Priorität in gut zehn Sekunden, ein zweites schreibt eine lesbare Nachricht. Aber die KI steht am Ende der Pipeline: Sie übersetzt gute Alerts in menschliche Sprache, schlechte Regeln repariert sie nicht.