Metriken, Alerts und Dashboards für produktive Systeme – von der Theorie zur ersten Alarm-Pipeline
„Es läuft” ist keine Monitoring-Strategie. Systeme, die keine Metriken liefern, keine Logs strukturieren und keine Alerts konfiguriert haben, funktionieren — bis sie es nicht mehr tun. Und dann beginnt die eigentliche Arbeit: blind nach der Ursache suchen, während Nutzer auf Fehlerseiten starren.
Prometheus und Grafana sind der Standard für Metriken-basiertes Monitoring in Cloud-Native-Umgebungen. Dieser Artikel zeigt, wie man sie vernünftig aufsetzt — und welche Fehler Systeme entweder blind lassen oder in Alert-Fatigue ertränken.
Monitoring und Observability sind nicht dasselbe
Monitoring bedeutet: Ich überwache bekannte Zustände. Ich weiß, welche Metriken wichtig sind, und ich werde benachrichtigt, wenn sie Schwellenwerte über- oder unterschreiten. Monitoring beantwortet die Frage: „Ist etwas kaputt?”
Observability geht weiter: Ich kann den inneren Zustand eines Systems aus seinen Ausgaben ableiten – auch für Zustände, die ich vorher nicht kannte. Observability beantwortet die Frage: „Warum ist es kaputt?” Die drei Säulen sind Metriken (Prometheus), Logs (Loki oder Elasticsearch) und Traces (Jaeger oder Tempo).
In der Praxis beginnen die meisten Teams mit Monitoring und entwickeln schrittweise Observability — das ist der realistische Weg. Dieser Artikel konzentriert sich auf die Metriken-Säule.
Der wichtigste praktische Unterschied: Ein reines Monitoring-System kann zuverlässig melden, dass die Fehlerrate gestiegen ist. Ein Observability-Stack kann zeigen, welcher Service-Call in welchem Request zu welcher Fehlerrate geführt hat – und warum.
Prometheus: wie Scraping funktioniert und warum das wichtig ist
Prometheus dreht das klassische Monitoring-Modell um. Statt dass Systeme Metriken aktiv an einen Server senden (Push), fragt Prometheus die Ziele regelmäßig ab (Pull/Scraping). Das hat einen entscheidenden Vorteil: Prometheus weiß sofort, wenn ein Ziel nicht erreichbar ist – es fehlt im Scrape-Ergebnis.
Die Konfiguration passiert in prometheus.yml:
# prometheus.yml
global:
scrape_interval: 15s # Wie oft Prometheus Ziele abfragt
evaluation_interval: 15s # Wie oft Alerting-Regeln ausgewertet werden
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- "alerts/*.yml"
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "node-exporter"
static_configs:
- targets: ["node-exporter:9100"]
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: "([^:]+):.*"
replacement: "$1"
- job_name: "my-app"
metrics_path: "/metrics"
static_configs:
- targets: ["myapp:8080"]
labels:
environment: "production"
team: "backend"
Jeder Service, der überwacht werden soll, muss einen /metrics-Endpunkt exponieren. Prometheus-Client-Libraries gibt es für alle gängigen Sprachen (Go, Python, Java, Node.js). Der Node Exporter liefert System-Metriken (CPU, Memory, Disk, Network) direkt vom Host.
Die vier Metriken-Typen
Prometheus kennt vier Typen, und der Unterschied zwischen Counter und Gauge ist in der Praxis der häufigste Stolperstein:
- Counter: Nur steigend, nie sinkend. Requests, Fehler, Bytes – alles, was sich nur erhöht. Für sinnvolle Abfragen braucht man immer
rate(). - Gauge: Aktueller Wert, kann steigen und fallen. CPU-Auslastung, aktive Verbindungen, Queue-Länge.
- Histogram: Verteilt Werte in Buckets. HTTP-Response-Zeiten, Request-Größen. Erlaubt Perzentil-Berechnungen.
- Summary: Ähnlich wie Histogram, aber Perzentile werden clientseitig berechnet. Weniger flexibel für nachträgliche Abfragen.
PromQL: die vier wichtigsten Abfragen
PromQL ist eine eigene Abfragesprache, die anfangs ungewohnt wirkt. Vier Muster decken 80 Prozent der praktischen Fälle ab.
Rate einer Counter-Metrik – wie viele HTTP-Requests kommen pro Sekunde an?
rate(http_requests_total[5m])
[5m] ist das Lookback-Fenster. Zu klein gewählt entstehen Spikes, zu groß gewählt werden kurze Lastspitzen geglättet. 5 Minuten ist ein guter Ausgangspunkt.
Fehlerrate in Prozent – wie viel Prozent der Requests schlagen fehl?
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])
* 100
Verfügbarkeit prüfen – welche Ziele sind gerade nicht erreichbar?
up == 0
up ist eine spezielle Metrik, die Prometheus für jedes Scrape-Ziel setzt: 1 wenn erreichbar, 0 wenn nicht. Diese Abfrage ist der einfachste Health-Check überhaupt.
Perzentil-Latenzen – wie lange brauchen 95 Prozent der Requests?
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile berechnet Perzentile aus Histogram-Buckets. Der p95-Wert ist aussagekräftiger als der Durchschnitt: Ein Durchschnitt von 200 ms kann ein p99 von 10 Sekunden verbergen.
Grafana: von der Rohmetrik zum lesbaren Dashboard
Grafana verbindet sich mit Prometheus als Datenquelle und visualisiert Metriken in Panels. Ein Dashboard ist eine Sammlung von Panels.
Praktischer Einstieg:
# Prometheus + Grafana via Docker Compose starten
docker compose up -d
# docker-compose.yml
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=15d'
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
volumes:
prometheus_data:
grafana_data:
Ein sinnvolles erstes Dashboard
Statt ein leeres Dashboard von Grund auf zu bauen, empfiehlt sich ein bewährter Import. Grafana Labs hostet Tausende vorgefertigter Dashboards. Dashboard-ID 1860 (Node Exporter Full) liefert sofort System-Metriken für jeden überwachten Host.
Für anwendungsspezifische Dashboards gilt ein Aufbauprinzip: von oben (Gesundheit des Systems) nach unten (Details einer Komponente).
Ein einfaches Panel-Beispiel als JSON-Konfiguration:
{
"title": "HTTP Request Rate",
"type": "timeseries",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(rate(http_requests_total[5m])) by (status)",
"legendFormat": "Status {{status}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 100 },
{ "color": "red", "value": 500 }
]
}
}
}
}
Farb-Thresholds direkt im Panel sind nützlich für Stat-Panels, wo ein einziger Wert sofort den Status signalisieren soll.
Alerting ohne Alert-Fatigue
Das ist der Bereich, an dem die meisten Setups scheitern. Nicht weil das Alerting nicht konfiguriert wurde – sondern weil zu viele Alerts konfiguriert wurden, die zu oft feuern und zu wenig Relevanz haben.
Alertmanager konfigurieren
Der Alertmanager nimmt Alerts von Prometheus entgegen, dedupliciert, gruppiert und routet sie an Empfänger:
# alertmanager.yml
global:
resolve_timeout: 5m
slack_api_url: "https://hooks.slack.com/services/..."
route:
group_by: ['alertname', 'environment']
group_wait: 30s # Warten auf weitere Alerts vor dem Senden
group_interval: 5m # Wie oft gruppierte Alerts gesendet werden
repeat_interval: 4h # Wie oft ein aktiver Alert erneut gesendet wird
receiver: "slack-ops"
routes:
- match:
severity: critical
receiver: "pagerduty"
repeat_interval: 1h
- match:
severity: warning
receiver: "slack-ops"
receivers:
- name: "slack-ops"
slack_configs:
- channel: "#ops-alerts"
title: "{{ .GroupLabels.alertname }}"
text: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}"
- name: "pagerduty"
pagerduty_configs:
- routing_key: "your-pagerduty-routing-key"
description: "{{ .GroupLabels.alertname }}: {{ .CommonAnnotations.summary }}"
inhibit_rules:
- source_match:
severity: "critical"
target_match:
severity: "warning"
equal: ["alertname", "instance"]
Alert-Regeln mit sinnvollen Schwellenwerten
# alerts/service.yml
groups:
- name: service_alerts
rules:
- alert: HighErrorRate
expr: |
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])
> 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Fehlerrate über 5% für {{ $labels.job }}"
description: "Aktuelle Fehlerrate: {{ $value | humanizePercentage }}"
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service nicht erreichbar: {{ $labels.instance }}"
- alert: HighMemoryUsage
expr: |
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/
node_memory_MemTotal_bytes
> 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "Hohe Speicherauslastung auf {{ $labels.instance }}"
description: "Speicherauslastung: {{ $value | humanizePercentage }}"
- alert: SlowResponseTime
expr: |
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P95-Latenz über 2 Sekunden für {{ $labels.job }}"
Das for-Feld ist entscheidend gegen False Positives: Ein Alert feuert erst, wenn die Bedingung für die angegebene Dauer konstant gilt. Ohne for: 2m würde ein kurzer Spike sofort eine PagerDuty-Benachrichtigung auslösen.
Runbooks: Der Unterschied zwischen Alarm und Handlung
Ein Alert ohne Runbook ist oft nur Lärm. Wer nachts geweckt wird, braucht nicht nur die Information, dass etwas kaputt ist, sondern den ersten sinnvollen Prüfpfad.
Ein guter Alert enthält:
| Feld | Beispiel |
|---|---|
| Was ist kaputt? | checkout-api hat mehr als 5 Prozent 5xx-Fehler |
| Seit wann? | for: 2m, Startzeit im Alertmanager |
| Wer ist zuständig? | Label team="backend" |
| Wo sieht man Details? | Grafana-Dashboard-Link mit Service-Filter |
| Was ist der erste Schritt? | Runbook-URL mit Diagnosebefehlen |
Ein kurzes Runbook für hohe Fehlerrate könnte so aussehen:
# HighErrorRate checkout-api
1. Grafana Dashboard "Checkout API" öffnen.
2. Prüfen: betrifft es alle Routen oder nur `/api/orders`?
3. Logs in Loki nach `status=500` und `trace_id` filtern.
4. Letztes Deployment prüfen.
5. Wenn Fehler nach Rollout starteten: Rollback ausführen.
6. Wenn externe Payment-API betroffen: Statusseite prüfen und Fallback aktivieren.
Das Runbook muss nicht perfekt sein. Es muss die ersten zehn Minuten strukturieren. Genau dort entscheidet sich, ob ein Alert hilft oder nur Stress erzeugt.
Die drei häufigsten Fehler in der Praxis
Fehler 1: Cardinality-Explosion
Cardinality bezeichnet die Anzahl eindeutiger Label-Kombinationen in Prometheus. Jede einzigartige Kombination ist eine eigene Zeitreihe, die Speicher und CPU verbraucht.
Das Problem entsteht, wenn Labels mit zu hoher Varianz verwendet werden:
# SCHLECHT: user_id als Label – jeder Nutzer eine eigene Zeitreihe
http_requests_total{user_id="12345", path="/api/data"}
# GUT: nur stabile, niedrig-kardinalitäts Labels
http_requests_total{status="200", path="/api/data", method="GET"}
User-IDs, Session-IDs, Request-IDs – alles, was sich ständig ändert – gehört in Logs, nicht in Metriken. Eine Prometheus-Instanz, die plötzlich Millionen Zeitreihen verwaltet, verlangsamt sich stark und kann abstürzen.
Diagnose:
# Wie viele Zeitreihen hat Prometheus aktuell?
prometheus_tsdb_head_series
# Welche Metriken haben die meisten Label-Kombinationen?
topk(10, count by (__name__)({__name__=~".+"}))
Fehler 2: Fehlende Labels machen Alerts nutzlos
Ein Alert ohne aussagekräftige Labels ist schwer zu routen und schwer zu debuggen:
# SCHLECHT
- alert: HighErrorRate
expr: rate(http_errors_total[5m]) > 0.1
annotations:
summary: "Hohe Fehlerrate" # Welcher Service? Welche Umgebung?
# GUT
- alert: HighErrorRate
expr: rate(http_errors_total[5m]) > 0.1
labels:
severity: critical
team: "{{ $labels.team }}"
annotations:
summary: "Hohe Fehlerrate in {{ $labels.job }} ({{ $labels.environment }})"
description: "{{ $value | humanize }} Fehler/s – Runbook: https://wiki/runbook/high-error-rate"
runbook_url: "https://wiki/runbook/high-error-rate"
Labels ermöglichen präzises Routing im Alertmanager: Critical-Alerts an PagerDuty, Warning-Alerts ans Slack-Kanal des zuständigen Teams. Ohne strukturierte Labels landet alles in einem globalen Kanal, den irgendwann niemand mehr liest.
Fehler 3: Monitoring zeigt, dass es läuft – aber nicht wie
Das klassische Monitoring überwacht, ob ein Service erreichbar ist (up == 1). Das ist notwendig, aber nicht hinreichend. Ein Service kann erreichbar sein und trotzdem langsam, fehlerreich oder falsch verhalten.
Die vier Golden Signals (aus dem Google SRE-Buch) sind der Rahmen für sinnvolles Application-Monitoring:
- Latenz – wie lange dauern Requests? (unterscheiden: erfolgreiche vs. fehlerhafte)
- Traffic – wie viele Requests kommen an?
- Fehlerrate – wie viel Prozent der Requests schlagen fehl?
- Saturation – wie ausgelastet ist das System? (CPU, Memory, Queue-Länge)
Diese vier Metriken pro Service zu haben reicht für ein funktionierendes Basis-Monitoring.
Telegraf als ergänzende Metriken-Quelle
Nicht jeder Service lässt sich direkt mit einem Prometheus-Endpunkt ausstatten – Legacy-Systeme, externe Dienste, Datenbanken. Hier ist Telegraf als Metriken-Agent eine sinnvolle Ergänzung: Es kann hunderte Quellen abfragen und die Daten direkt in das Prometheus-Exposition-Format umwandeln oder in InfluxDB schreiben.
Telegraf ergänzt Prometheus dort, wo direktes Scraping nicht möglich oder sinnvoll ist – ohne die Prometheus-Architektur zu ersetzen.
Alerting-Disziplin: weniger ist mehr
Alert-Fatigue entsteht, wenn zu viele Alerts feuern, die zu unspezifisch sind oder keine klare Aktion erfordern. Das Ergebnis: Das Team gewöhnt sich ans Ignorieren von Alerts – und verpasst den Alert, der tatsächlich wichtig ist.
Ein sinnvoller Test für jeden Alert: Erfordert dieser Alert eine menschliche Aktion innerhalb der nächsten 30 Minuten? Wenn nein, ist es kein Alert – es ist eine Metrik für ein Dashboard.
Drei Kategorien helfen bei der Einordnung:
- Page (PagerDuty, sofortige Reaktion): Produktionssystem down, Daten verloren, kritische Fehlerrate
- Ticket (Slack, nächster Werktag): Speicher über 80%, Zertifikat läuft in 14 Tagen ab
- Dashboard (kein Alert): Latenz leicht erhöht, Cache-Hit-Rate gesunken
Severity-Labels (critical, warning, info) im Alertmanager ermöglichen dieses Routing ohne manuelle Eingriffe.
Was auf ein erstes Dashboard gehört
Ein gutes erstes Dashboard beantwortet nicht „was kann Grafana alles anzeigen?”, sondern „ist der Dienst gesund?”. Für einen Web-Service reichen am Anfang sechs Panels:
| Panel | PromQL-Idee |
|---|---|
| Request Rate | sum(rate(http_requests_total[5m])) |
| Error Rate | 5xx-Requests geteilt durch alle Requests |
| P95 Latency | histogram_quantile(0.95, ...) |
| CPU und Memory | Node-Exporter-Metriken pro Instanz |
| Queue Length | Gauge aus Worker- oder Queue-System |
| Deploy Marker | Annotationen aus CI/CD oder Release-Events |
Der letzte Punkt wird oft vergessen. Ohne Deploy Marker sieht man zwar, dass die Fehlerrate ab 14:32 steigt, aber nicht, dass um 14:29 ein Release ausgerollt wurde. Gute Observability verbindet Metriken mit Ereignissen.
Einstiegspfad für kleine Teams: Was zuerst, was später
Ein vollständiger Observability-Stack überfordert kleine Teams, wenn er auf einmal eingeführt wird. Der praktische Einstieg folgt einem klaren Stufenmodell — jede Stufe liefert sofort Mehrwert, ohne die nächste zu erzwingen.
Stufe 1 — Tag 1 (2–4 Stunden): Node Exporter und Prometheus via Docker Compose starten, einen einzigen Alert für up == 0 konfigurieren. Das ist keine vollständige Lösung, aber der erste echte Sicherheitsnetz: Man erfährt, wenn ein Service nicht mehr erreichbar ist.
Stufe 2 — erste Woche: Grafana anbinden, Dashboard 1860 (Node Exporter Full) importieren. Die vier Golden Signals (Latenz, Traffic, Fehlerrate, Auslastung) für den wichtigsten produktiven Service als eigene Panels ergänzen. Ziel: ein gemeinsames Bild der Systemgesundheit, das das gesamte Team lesen kann.
Stufe 3 — erster Monat: Alert-Regeln für die zwei bis drei kritischsten Szenarien konfigurieren (hohe Fehlerrate, ServiceDown, voller Disk). Für jeden Alert ein minimales Runbook anlegen. Keine perfekten Schwellenwerte nötig — anpassen, wenn Alerts feuern und sich als Fehlalarm herausstellen.
Was später kommt, wenn der Bedarf entsteht: Loki für Logs, Tempo oder Jaeger für Distributed Tracing, Alertmanager-Routing nach Teams, kube-prometheus-stack für Kubernetes. Diese Schichten bauen aufeinander auf — aber nur wer Stufe 1 bis 3 wirklich nutzt, profitiert von der Tiefe.
Von hier in die Tiefe
Prometheus und Grafana sind ein solides Fundament, aber kein vollständiger Observability-Stack. Logs fehlen (Loki oder ELK), Distributed Tracing fehlt (Tempo oder Jaeger), und für Kubernetes-Cluster empfiehlt sich das kube-prometheus-stack-Helm-Chart, das Prometheus, Grafana, Alertmanager und 20 Standard-Dashboards mit einem einzigen Befehl installiert.
Das Wichtigste ist aber ein anderes: Ein Monitoring-System, das niemand pflegt, verrottet. Alert-Regeln veralten, Schwellenwerte stimmen nicht mehr, Dashboards zeigen irrelevante Metriken. Monitoring ist kein einmaliges Setup – es ist ein kontinuierlicher Prozess, der mit dem System wächst.
Der erste Schritt ist oft kleiner, als es scheint: Node Exporter und Prometheus starten, die up-Metrik prüfen, einen einzigen Alert für up == 0 konfigurieren. Von dort wächst der Stack organisch mit den Anforderungen.