Zum Inhalt springen
CASOON

Monitoring und Observability mit Prometheus und Grafana

Metriken, Alerts und Dashboards für produktive Systeme – von der Theorie zur ersten Alarm-Pipeline

15 Minuten
Monitoring und Observability mit Prometheus und Grafana
#Monitoring #DevOps #Cloud Native #Infrastruktur

„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:

FeldBeispiel
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:

PanelPromQL-Idee
Request Ratesum(rate(http_requests_total[5m]))
Error Rate5xx-Requests geteilt durch alle Requests
P95 Latencyhistogram_quantile(0.95, ...)
CPU und MemoryNode-Exporter-Metriken pro Instanz
Queue LengthGauge aus Worker- oder Queue-System
Deploy MarkerAnnotationen 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.