Teil D — Wie man betreibt. Zurück zur Übersicht.
Lernziel
Nach diesem Kapitel kannst du Monitoring von Observability unterscheiden, kennst die drei Säulen und die vier goldenen Signale und kannst begründen, warum auf Symptome und nicht auf Ursachen alarmiert wird. Der KFZ-Rechner liefert Metriken, wird überwacht und meldet sich bei Problemen.
Die Begriffe werden oft synonym verwendet, meinen aber Unterschiedliches.
| Monitoring | Observability | |
|---|---|---|
| Beantwortet | bekannte Fragen | unvorhergesehene Fragen |
| Beispiel | „Ist die CPU über 80 Prozent?„ | „Warum sind ausgerechnet Anfragen aus Filiale 12 langsam?“ |
| Vorgehen | vorher festgelegte Prüfungen | Daten reichhaltig genug, um nachträglich zu forschen |
| Findet | die Probleme, die man erwartet hat | auch die anderen |
Monitoring ist nicht überholt — Observability ist die Erweiterung. Man braucht beides: feste Prüfungen für Bekanntes und genug Kontext, um Unbekanntes untersuchen zu können.
Aus Kapitel 2 wieder aufgegriffen: Das ist der Zweite Weg, die Rückmeldung. Ohne Messung bleibt er eine Absichtserklärung.
| Säule | Beantwortet | Beispiel |
|---|---|---|
| Metriken | Was passiert? Wie viel? Wie schnell? | 340 Anfragen/min, 1,2 % Fehler, p95 bei 180 ms |
| Logs | Was genau ist passiert? | „Berechnung für Vertrag 4711 abgebrochen: Division durch null„ |
| Traces | Wo genau hat es gehakt? | Anfrage 78 ms in der Anwendung, 1.240 ms in der Datenbank |
Metriken sind billig, aggregiert und gut für Alarme. Logs sind teuer, detailliert und gut für die Ursachensuche. Traces zeigen den Weg einer einzelnen Anfrage durch mehrere Systeme — und sind bei verteilten Anwendungen unverzichtbar.
Der übliche Ablauf einer Fehlersuche: Ein Alarm auf einer Metrik meldet, dass etwas nicht stimmt. Ein Trace zeigt, wo. Die Logs sagen, warum.
Aus dem Betriebshandbuch von Google, und als Einstieg schwer zu schlagen. Wer nur vier Dinge misst, misst diese:
| Signal | Frage | Beim KFZ-Rechner |
|---|---|---|
| Latenz | Wie lange dauert eine Anfrage? | Antwortzeit der Berechnung |
| Verkehr | Wie viel Last liegt an? | Anfragen pro Minute |
| Fehler | Wie viel geht schief? | Anteil der Antworten mit Status 5xx |
| Sättigung | Wie voll ist das System? | Speicher, Verbindungspool, Warteschlange |
Latenz getrennt nach erfolgreichen und fehlgeschlagenen Anfragen messen.
Ein Systemfehler wird oft sehr schnell zurückgegeben. Mischt man beides, sinkt die Durchschnittslatenz genau dann, wenn das System kaputtgeht — und die Kurve sieht aus, als hätte sich etwas verbessert.
Überhaupt ist der Durchschnitt bei Latenzen die falsche Kennzahl. Bei 1.000 Anfragen mit 50 ms und 10 Anfragen mit 8 Sekunden liegt der Mittelwert bei rund 130 ms — unauffällig. Zehn Anwender haben trotzdem acht Sekunden gewartet. Deshalb misst man Perzentile: p50, p95, p99. Das p99 sagt: Ein Prozent der Anwender hat es mindestens so schlecht erwischt.
Schlecht:
ERROR: something went wrong
Besser:
2026-07-29T14:32:07Z ERROR Prämienberechnung fehlgeschlagen
vertrag=4711 sf_klasse=-3 fehler=ValueError anfrage_id=a7f3c9
Am besten - strukturiert:
{"zeit":"2026-07-29T14:32:07Z","stufe":"ERROR",
"text":"Prämienberechnung fehlgeschlagen","vertrag":"4711",
"sf_klasse":-3,"fehler":"ValueError","anfrage_id":"a7f3c9"}
Strukturierte Logs sind maschinell auswertbar. Statt in Textmengen zu suchen, filtert man nach Feldern: alle Fehler des Typs ValueError der letzten Stunde, gruppiert nach Vertrag.
Der wichtigste Einzelkniff: Jede Anfrage bekommt beim Eintritt eine eindeutige Kennung, die durch alle Systeme mitwandert und in jeder Logzeile steht.
import uuid from contextvars import ContextVar anfrage_id: ContextVar[str] = ContextVar("anfrage_id", default="-") def middleware(request): # Vorhandene ID übernehmen oder neue erzeugen kennung = request.headers.get("X-Request-ID") or str(uuid.uuid4())[:8] anfrage_id.set(kennung) antwort = verarbeite(request) antwort.headers["X-Request-ID"] = kennung return antwort
Damit lässt sich der Weg einer einzelnen Anfrage über Reverse Proxy, Anwendung und Datenbank hinweg vollständig zusammensetzen. Ohne diese Kennung ist die Fehlersuche in verteilten Systemen Ratearbeit.
Keine personenbezogenen Daten in Logs.
Namen, Adressen, Geburtsdaten, Vertragsinhalte, vollständige Anfragekörper — all das gehört nicht in ein Protokoll. Logs werden zentral gesammelt, breit lesbar gemacht, lange aufbewahrt und selten gelöscht. Damit entsteht faktisch eine zweite, schlecht geschützte Datenhaltung.
Statt der Daten protokolliert man Verweise: eine Vertragsnummer statt der Vertragsdaten, eine Kundennummer statt des Namens. Wer den Zusammenhang wirklich braucht, kann ihn über das Fachsystem herstellen — protokolliert und berechtigt.
Das gilt auch für Fehlermeldungen mit vollständigem Stacktrace: Dort landen erschreckend häufig ganze Eingabedaten.
Die schwierigste Disziplin. Zu wenige Alarme, und Störungen fallen Anwendern zuerst auf. Zu viele, und niemand schaut mehr hin.
Schlecht (Ursache): Besser (Symptom): CPU über 80 % p95 der Antwortzeit über 2 s Speicher über 90 % Fehlerrate über 1 % Festplatte zu 85 % voll Anfragen schlagen fehl
Eine CPU-Auslastung von 90 Prozent ist kein Problem, solange die Anwender nichts merken — es kann auch schlicht bedeuten, dass die Maschine gut ausgelastet ist. Umgekehrt kann alles im grünen Bereich sein, während die Anwendung wegen eines Sperrproblems in der Datenbank steht.
Die Regel: Alarmiert wird auf das, was Anwender spüren. Ursachenmetriken bleiben in den Diagrammen, damit man sie bei der Analyse zur Hand hat.
Eine Ausnahme bestätigt die Regel: Vorlaufende Größen mit sicherem Ausgang. Ein Zertifikat, das in vierzehn Tagen abläuft, oder ein Dateisystem, das bei aktueller Wachstumsrate in drei Tagen voll ist — dort ist die Ursache selbst der Alarm, weil das Symptom zu spät käme.
Prüffrage für jeden Alarm: Was genau soll die Person tun, die um drei Uhr nachts geweckt wird?
Gibt es keine sinnvolle Antwort, ist es kein Alarm, sondern ein Diagramm. Diagramme wecken niemanden.
Alarmmüdigkeit ist eine ernste Betriebsgefahr: Wer in der Woche vierzig Meldungen bekommt, von denen achtunddreißig folgenlos sind, entwickelt zwangsläufig die Gewohnheit, sie wegzuklicken. Und klickt irgendwann auch die zwei echten weg.
"""Prometheus-Metriken für den KFZ-Rechner.""" from prometheus_client import Counter, Histogram, Gauge ANFRAGEN = Counter( "kfz_anfragen_gesamt", "Anzahl der Berechnungsanfragen", ["ergebnis"], # erfolg | fehler ) DAUER = Histogram( "kfz_berechnungsdauer_sekunden", "Dauer einer Prämienberechnung", buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5), ) DB_VERBINDUNGEN = Gauge( "kfz_db_verbindungen_aktiv", "Aktuell belegte Datenbankverbindungen", )
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST from src.metriken import ANFRAGEN, DAUER from src.praemie import berechne_praemie @app.route("/api/praemie") def api_praemie(): with DAUER.time(): try: betrag = berechne_praemie( int(request.args["sf"]), int(request.args["alter"]), ) ANFRAGEN.labels(ergebnis="erfolg").inc() return {"betrag": str(betrag)} except (ValueError, KeyError) as fehler: ANFRAGEN.labels(ergebnis="fehler").inc() app.logger.warning( "Berechnung fehlgeschlagen", extra={"fehler": type(fehler).__name__}, ) return {"fehler": "ungültige Eingabe"}, 400 @app.route("/metrics") def metrics(): return generate_latest(), 200, {"Content-Type": CONTENT_TYPE_LATEST} @app.route("/health") def health(): """Lebt der Prozess?""" return {"status": "ok"} @app.route("/ready") def ready(): """Kann der Dienst Anfragen bearbeiten - inklusive Abhängigkeiten?""" if not datenbank_erreichbar(): return {"status": "nicht bereit"}, 503 return {"status": "bereit"}
Die Trennung von /health und /ready ist wichtiger, als sie aussieht. Health beantwortet: Lebt der Prozess? Ist die Antwort nein, muss neu gestartet werden. Readiness beantwortet: Kann der Dienst gerade arbeiten? Ist die Antwort nein — etwa weil die Datenbank hängt — soll er keinen Verkehr bekommen, aber auch nicht neu gestartet werden.
Wer beides zusammenlegt, bekommt eine hübsche Fehlerkaskade: Die Datenbank ist kurz nicht erreichbar, sämtliche Instanzen werden für ungesund erklärt und neu gestartet, und der Neustart erzeugt einen Ansturm auf die ohnehin überlastete Datenbank.
scrape_configs: - job_name: kfz-rechner scrape_interval: 15s static_configs: - targets: ["kfz-rechner:8080"]
groups: - name: kfz-rechner rules: - alert: HoheFehlerrate expr: | sum(rate(kfz_anfragen_gesamt{ergebnis="fehler"}[5m])) / sum(rate(kfz_anfragen_gesamt[5m])) > 0.05 for: 5m labels: severity: kritisch annotations: summary: "Fehlerrate über 5 Prozent" beschreibung: "Seit 5 Minuten schlagen mehr als 5 % der Berechnungen fehl." runbook: "https://creutz.spdns.de/doku.php/edv:devops:13_incident_management" - alert: LangsameAntworten expr: | histogram_quantile(0.95, sum(rate(kfz_berechnungsdauer_sekunden_bucket[5m])) by (le) ) > 1 for: 10m labels: severity: warnung annotations: summary: "p95 der Berechnungsdauer über 1 Sekunde" - alert: DienstNichtErreichbar expr: up{job="kfz-rechner"} == 0 for: 2m labels: severity: kritisch annotations: summary: "KFZ-Rechner antwortet nicht"
Aufgaben:
for: 5m auf for: 30s. Was passiert bei kurzen Lastspitzen?| Anti-Pattern | Folge | Abhilfe |
|---|---|---|
| Alarm auf Ursachen | viele Meldungen ohne Anwenderwirkung | auf Symptome alarmieren |
| Durchschnittslatenz | Ausreißer verschwinden | Perzentile |
| Alarme ohne Handlungsanweisung | Ratlosigkeit um drei Uhr nachts | Runbook verlinken |
| Zu viele Alarme | Ermüdung, echte Meldungen gehen unter | ausdünnen, for-Zeiten nutzen |
| Dashboard mit vierzig Diagrammen | niemand liest es | ein Übersichtsbild, Details dahinter |
| Personenbezogene Daten in Logs | Datenschutzverstoß | Verweise statt Inhalte |
| Unstrukturierte Logs | nicht auswertbar | JSON, feste Felder |
/health prüft auch die Datenbank | Neustartkaskade | Health und Readiness trennen |
| Erst überwachen, wenn es brennt | Störung ohne Datenlage | Instrumentierung von Anfang an |
Wie viele Alarme habt ihr in der vergangenen Woche bekommen, und bei wie vielen davon musste tatsächlich jemand handeln?
Liegt das Verhältnis unter einem Drittel, ist die Alarmierung ein Problem für sich — unabhängig davon, wie gut die Überwachung funktioniert.
Weiter mit Kapitel 13 — Incident-Management