Observability-Integration-Fallstricke: Was schiefgeht, wenn du Prometheus, Grafana, OTel und Sentry verdrahtest

#observability integration fallstricke
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

Prometheus, Grafana, OpenTelemetry und Sentry zu einem Stack zu verdrahten sieht nach einem gelösten Problem aus. Jedes Tool hat exzellente Dokumentation, jedes hat einen Quickstart, und jedes funktioniert für sich allein einwandfrei. Die Observability-Integration-Fallstricke lauern in den Nahtstellen: dort, wo die Defaults des einen Tools mit den Annahmen des anderen kollidieren. Ein Metrik-Label, das Prometheus klaglos akzeptiert, sprengt deinen Storage. Eine Batch-Processor-Einstellung, die im Staging funktioniert, verwirft in Produktion stillschweigend Spans. Zwei Sampling-Entscheidungen von zwei SDKs erzeugen Traces ohne Eltern.

Wir haben eine Schritt-für-Schritt-Integrations-Checkliste für den Aufbau dieses Stacks von Grund auf geschrieben. Dieser Beitrag ist die andere Hälfte der Geschichte: die sechs Fehlermuster, die wir beim Audit von Observability-Setups am häufigsten sehen, und die konkrete Konfiguration, die jedes davon behebt.

Die sechs Observability-Integration-Fallstricke, sortiert nach Schadenspotenzial

Geordnet danach, wie teuer es ist, sie erst in Produktion zu entdecken:

  1. Prometheus-Kardinalitätsexplosion durch unbedachte Labels
  2. OTel Collector verwirft Spans unter Backpressure
  3. Grafana-Clock-Skew erzeugt Phantom-Lücken in Dashboards
  4. Sampling-Konflikte zwischen Sentry und OTel erzeugen verwaiste Traces
  5. Alert-Müdigkeit durch doppelte Alerting-Regeln
  6. Die Performance-Kosten von vier Agents auf einem Server

Fallstrick 1: Kardinalitätsexplosion durch naive Labels

Prometheus speichert eine Zeitreihe pro eindeutiger Kombination aus Metrik-Name und Label-Werten. Füge einem Request-Counter ein user_id-Label hinzu und du hast nicht mehr eine Serie, sondern eine Serie pro Nutzer. Füge path ohne Normalisierung hinzu und jede URL mit einer ID darin wird zu ihrer eigenen Serie. Der Speicherverbrauch wächst linear mit den aktiven Serien, und eine Prometheus-Instanz, die bei 200k Serien bequem lief, fängt bei 5 Millionen an, sich selbst per OOM-Kill zu beenden.

Das Heimtückische ist die Verzögerung. Kardinalität wächst mit Traffic und Feature-Launches, nicht mit Deploys. Die Explosion schlägt also Wochen nach dem Hinzufügen des Labels ein, und niemand verbindet die beiden Ereignisse miteinander.

Der Fix. Verwende niemals unbegrenzte Werte (User-IDs, Session-IDs, rohe URLs, Container-Hashes) als Label-Werte. Normalisiere Routen zu Templates (/api/orders/{id}) und erzwinge die Regel zur Scrape-Zeit mit metric_relabel_configs, damit ein einzelner fehlerhafter Exporter nicht den Server lahmlegen kann:

metric_relabel_configs:
  - source_labels: [user_id]
    regex: '.+'
    action: labeldrop

Beobachte prometheus_tsdb_head_series und alarmiere, wenn der Wert Woche über Woche um mehr als 20 Prozent wächst. Dieser eine Alert hat sich in jedem Audit, das wir durchgeführt haben, bezahlt gemacht.

Fallstrick 2: Der OTel Collector verwirft Spans unter Backpressure

Das ist der Fallstrick, nach dem Leute erst im Nachhinein suchen, meist formuliert als "opentelemetry collector missing spans". Die Default-Konfiguration des Batch-Processors ist für Demos ausgelegt. Unter echter Last nimmt der Collector Spans schneller an, als er sie exportiert, Queues laufen voll, und Spans werden verworfen, ohne dass mehr davon zeugt als ein hochzählender Counter.

Die beiden entscheidenden Einstellungen sind send_batch_size und timeout am Batch-Processor, kombiniert mit der Exporter-Queue:

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 20
  batch:
    send_batch_size: 8192
    send_batch_max_size: 10000
    timeout: 5s

exporters:
  otlp:
    endpoint: tempo:4317
    sending_queue:
      enabled: true
      queue_size: 5000
    retry_on_failure:
      enabled: true
      max_elapsed_time: 300s

Eine zu kleine send_batch_size (der Default ist 8192, aber viele Tutorials setzen 512) bedeutet mehr Export-Calls pro Sekunde und früheren Backpressure. Ein zu aggressives timeout flusht halbleere Batches. Der memory_limiter muss als Erstes in der Processor-Kette stehen, und die Exporter-Queue ist das, was einen Backend-Aussetzer ohne Datenverlust abfedert.

Du erkennst diesen Fallstrick daran, dass die Metrik otelcol_processor_dropped_spans nicht null ist. Alarmiere darauf. Die vollständige Produktions-Referenz findest du in unserem OTel-Collector-Konfigurationsguide.

Fallstrick 3: Grafana-Clock-Skew und Phantom-Lücken in Dashboards

Ein Dashboard zeigt eine zweiminütige Lücke in den Metriken. Der Bereitschaftsdienst vermutet einen Ausfall, wühlt sich durch Logs, findet nichts. Die echte Ursache: Der Grafana-Host, der Prometheus-Host und der Applikations-Host sind sich um 40 Sekunden uneinig darüber, wie spät es ist.

Prometheus versieht Samples zur Scrape-Zeit mit Zeitstempeln seiner eigenen Uhr. Grafana rendert mit seiner eigenen Uhr und der des Browsers. Driftet eine davon, scheinen aktuelle Daten zu fehlen, weil Grafana nach Samples fragt, die "neuer" sind als das, was es laut Prometheus überhaupt schon gibt. Die Lücken erscheinen immer am rechten Rand des Dashboards und lösen sich beim Refresh von selbst auf, und genau dieses Verhalten untergräbt das Vertrauen in den ganzen Stack.

Der Fix hat drei Teile. Erstens: Lass chrony oder systemd-timesyncd auf jedem Host laufen und alarmiere bei Drift: node_timex_offset_seconds über 0.05 ist ein Problem. Zweitens: Setze ein sinnvolles Mindestintervall in der Grafana-Datasource (das Scrape interval in den Einstellungen der Prometheus-Datasource sollte deinem tatsächlichen Scrape-Intervall entsprechen, typischerweise 15s oder 30s), damit Grafana keine Punkte interpoliert, die es nicht geben kann. Drittens: Verwende in Panels, die über Zeit aggregieren, $__rate_interval statt fest codierter Zeiträume. Das hält Queries an der Scrape-Auflösung ausgerichtet.

Fallstrick 4: Sampling-Konflikte zwischen Sentry und OTel, die Traces verwaisen lassen

Sentrys SDKs implementieren ihr eigenes Tracing mit eigenem Sampler. OpenTelemetry hat seinen Sampler. Lass beide unkoordiniert in einer Applikation laufen und jeder trifft eine unabhängige Behalten-oder-Verwerfen-Entscheidung. Das Ergebnis sind Traces, bei denen Sentry den Kind-Span behalten hat, OTel aber den Eltern-Span verworfen hat: verwaiste Traces, die als Fragmente gerendert werden, mit einem Trace-Wasserfall ohne Wurzel.

Das Symptom ist unverkennbar: Tempo oder Jaeger zeigen Traces, die mitten im Request beginnen, und die Sentry-Performance-Daten widersprechen den OTel-Daten beim Traffic-Volumen desselben Endpoints.

Der Fix besteht darin, genau ein System die Sampling-Entscheidung treffen zu lassen und das andere sie respektieren zu lassen. Da Sentry OpenTelemetry direkt unterstützt, routet das saubere Setup Sentry durch OTel, statt zwei Tracer zu betreiben:

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  skipOpenTelemetrySetup: true,
  tracesSampleRate: undefined,
});

Konfiguriere Sampling dann genau einmal, im OTel SDK (parentbased_traceidratio hält Entscheidungen über Services hinweg konsistent), und hänge Sentrys Span-Processor an den OTel Tracer Provider. In PHP- und Python-Setups, wo diese Verdrahtung nicht verfügbar ist, ist der Fallback, beide Sampler auf dieselbe Rate zu setzen und einen traces_sampler-Callback zu verwenden, der die eingehende Eltern-Entscheidung respektiert. Die Regel bleibt in beiden Fällen: Einer entscheidet, alle anderen erben.

Fallstrick 5: Doppeltes Alerting über Alertmanager und Sentry

Prometheus Alertmanager alarmiert auf Symptome: Fehlerrate über 2 Prozent, p95-Latenz über 800ms. Sentry alarmiert auf Issues: neuer Exception-Typ, Regression nach einem Release. Konfiguriere beide ohne Plan und derselbe Vorfall pagt dich zweimal, über zwei Kanäle, mit zwei verschiedenen Links. Nach dem dritten doppelten 2-Uhr-nachts-Page fangen Entwickler an, Kanäle stummzuschalten, und dann stirbt ein echter Alert in einem stummgeschalteten Kanal.

Der Fix ist eine explizite, schriftlich festgehaltene Aufgabenteilung:

  • Alertmanager besitzt Symptom-Alerts. Alles Raten-, Latenz- oder Sättigungsbasierte. Das sind die Pages.
  • Sentry besitzt Regressions-Alerts. Neue Issue-Typen, Issues, die nach dem Schließen wieder auftauchen, Release-Health-Schwellwerte. Die gehen in einen Triage-Kanal, nicht an den Pager.

Dann setze es durch: Lösche Sentry-Alert-Regeln, die auf Volumen feuern (das deckt Alertmanager bereits ab), und nutze Alertmanager-Inhibition-Regeln, damit ein feuernder Symptom-Alert sein nachgelagertes Rauschen unterdrückt. Der Test für ein gesundes Setup: Jeder einzelne Vorfall erzeugt genau einen Page und höchstens eine Triage-Benachrichtigung.

Fallstrick 6: Vier Agents auf einem Server

Auf einem Single-Server-Deployment wird der Observability-Stack selbst zum Mieter. Prometheus, der OTel Collector, Grafana und ein Sentry-Relay verbrauchen zusammen 1,5 bis 2,5 GB RAM und einen konstanten Anteil CPU, bevor deine Applikation auch nur einen Request bedient. Wir haben Setups auditiert, in denen Observability 30 Prozent der Maschine verbrauchte und das Team ein Server-Upgrade evaluierte, um Platz dafür zu schaffen.

Der Fix ist bewusstes Budgetieren. Begrenze jede Komponente (--storage.tsdb.retention.time=15d und --storage.tsdb.retention.size=8GB für Prometheus, den memory_limiter aus Fallstrick 2 für den Collector, Container-Memory-Limits für Grafana). Weite Scrape-Intervalle auf 30s oder 60s aus, wo 15s nichts bringt. Sample Traces mit 10 Prozent statt 100. Passt das Budget dann immer noch nicht, ist Hosted Grafana Cloud oder Sentry SaaS für die schweren Komponenten meist günstiger als die nächste Server-Stufe.

Das Muster hinter allen sechs

Jeder dieser Fallstricke folgt derselben Form: Jedes Tool verhält sich nach seinen eigenen Defaults korrekt, und der Fehler lebt in der Naht dazwischen. Deshalb widersetzen sich Observability-Probleme dem Debugging Tool für Tool, und deshalb ist der Fix immer eine Entscheidung über die Naht: Wer samplet, wer alarmiert, wer das Label-Budget besitzt, wessen Uhr gewinnt.

Zeigt dein Stack eines dieser Symptome, ist ein Blick von außen oft schneller als die nächste interne Debugging-Runde. Wolf-Tech führt Performance- und Code-Quality-Audits durch, die Observability-Konfiguration abdecken, und hilft Teams bei Tech-Stack-Entscheidungen wie Self-Hosted versus SaaS für die Monitoring-Ebene. Schreib an hello@wolf-tech.io oder besuche wolf-tech.io, wenn du ein zweites Paar Augen auf deinem Setup haben willst.