OpenTelemetry Collector in Produktion: Best Practices für SREs

Deployment-Topologie, Speicherlimits, persistente Queues, Self-Monitoring und Rollouts: Was es braucht, um den Collector als kritische Infrastruktur zu betreiben.

Marco RossiHead of Developer Relations, Obseria
Zusammenfassung
  • Betreiben Sie den OpenTelemetry Collector zweistufig: ein schlanker Agent pro Node und ein horizontal skaliertes Gateway, das Sampling, Anreicherung und Export übernimmt.
  • Der memory_limiter-Prozessor, persistente Sending Queues und durchdachte Retry-Einstellungen entscheiden darüber, ob bei einem Backend-Ausfall oder Lastspitzen Telemetrie verloren geht.
  • Behandeln Sie den Collector wie jeden anderen Produktivdienst: Überwachen Sie seine eigenen Metriken, versionieren Sie die Konfiguration und rollen Sie Änderungen schrittweise aus.

Der OpenTelemetry Collector ist der flexibelste Baustein eines OpenTelemetry-Setups und in vielen Organisationen zugleich der am wenigsten betriebene. Er beginnt als Sidecar, das jemand aus einem Tutorial kopiert hat, und ein Jahr später transportiert er jeden Span, jede Logzeile und jede Metrik des Unternehmens. Fällt er aus, wird es in der Observability genau dann dunkel, wenn man sie am dringendsten braucht.

In diesem Beitrag fassen wir die OpenTelemetry Collector Best Practices zusammen, die wir in Produktion anwenden: in unserer eigenen Plattform und wenn wir Teams dabei unterstützen, bestehende OTel-Setups zu Obseria zu migrieren. Nichts davon ist an ein bestimmtes Backend gebunden. Die Konfigurationsbeispiele verwenden ausschließlich Upstream-Komponenten aus den Core- und Contrib-Distributionen des Collectors.

Zweistufige Topologie: Agent und Gateway

Ein einzelnes Collector-Deployment, das alles erledigt, ist schnell aufgesetzt und schwer zu betreiben. Bewährt hat sich unter Last ein Muster, das zwei Rollen klar trennt:

  • Agent-Ebene: ein Collector pro Node (als DaemonSet in Kubernetes, als Systemdienst auf VMs). Er empfängt OTLP von lokalen Anwendungen, liest Host-Metriken, folgt Logdateien und ergänzt Ressourcenattribute wie Host, Pod und Namespace. Er bleibt klein und verarbeitet so wenig wie möglich.
  • Gateway-Ebene: ein horizontal skaliertes Deployment hinter einem Load Balancer. Hier liegt alles, was eine globale Sicht oder mehr Speicher braucht: Tail-based Sampling, PII-Maskierung, Routing an mehrere Ziele, Authentifizierung und Retries in Richtung Backend.

Diese Trennung hat handfeste Vorteile. Agents lassen sich im Zuge der Node-Wartung aktualisieren, ohne die Sampling-Logik anzufassen. Gateways skalieren auf Basis der CPU, ohne dass jeder Node neu ausgerollt werden muss. Und Zugangsdaten für das Backend liegen nur in der Gateway-Ebene, nicht auf jedem einzelnen Host.

Wenn Sie Tail-based Sampling einsetzen, denken Sie daran: Alle Spans eines Traces müssen bei derselben Gateway-Instanz ankommen. Nutzen Sie dafür in der Agent-Ebene den loadbalancing-Exporter mit Routing nach Trace-ID oder ergänzen Sie eine eigene Sampling-Ebene hinter dem Gateway.

memory_limiter immer an erster Stelle

Der häufigste Collector-Ausfall ist kein Bug, sondern ein Out-of-Memory-Kill. Eine Lastspitze, ein langsames Backend oder ein fehlerhafter Client füllt die internen Puffer, bis Kernel oder Kubelet den Prozess beenden. Alles, was gepuffert war, ist dann verloren.

Der memory_limiter-Prozessor verhindert das, indem er Daten ablehnt, bevor der Speicher ausgeht. Die Receiver liefern dann einen Fehler zurück, der einen erneuten Versuch erlaubt, und sauber implementierte OTLP-Clients fahren ihre Sendefrequenz zurück. Er muss in jeder Pipeline der erste Prozessor sein:

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

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, k8sattributes, batch]
      exporters: [otlp/obseria]

Drei Details sind entscheidend. Erstens beziehen sich die Prozentwerte auf den für den Prozess verfügbaren Speicher. Setzen Sie deshalb ein Memory-Limit für den Container, sonst fehlt dem Limiter eine sinnvolle Bezugsgröße. Zweitens sollten Sie die Umgebungsvariable GOMEMLIMIT auf etwa 80 bis 90 Prozent des Container-Limits setzen, damit der Garbage Collector von Go früher und intensiver arbeitet. Drittens gehört batch ans Ende der Pipeline, hinter alle Prozessoren, die Daten verwerfen oder verändern. So enthalten die Batches nur, was tatsächlich exportiert wird.

Exporte, die Backend-Ausfälle überstehen

Jeder Exporter, der auf dem Exporter-Helper aufbaut, besitzt eine Sending Queue und eine Retry-Logik. Standardmäßig liegt die Queue im Arbeitsspeicher. Ein Neustart des Collectors während einer Störung im Backend verwirft also alles, was gerade wartet. Für die Gateway-Ebene empfehlen wir eine persistente Queue auf Basis der file_storage-Extension:

extensions:
  file_storage/queue:
    directory: /var/lib/otelcol/queue
    timeout: 10s
    compaction:
      on_start: true
      on_rebound: true
      directory: /var/lib/otelcol/queue

exporters:
  otlp/obseria:
    endpoint: ingest.eu.obseria.example:4317
    headers:
      authorization: "Bearer ${env:OBSERIA_TOKEN}"
    compression: zstd
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 50000
      storage: file_storage/queue
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 10m

In Kubernetes braucht dafür jeder Gateway-Pod ein eigenes Volume, typischerweise über ein StatefulSet mit PersistentVolumeClaim. Dimensionieren Sie die Queue anhand des tatsächlichen Durchsatzes: Empfängt ein Gateway 2.000 Batches pro Minute und soll eine zehnminütige Störung im Backend überbrücken, braucht die Queue Platz für mindestens 20.000 Batches. Beachten Sie, dass queue_size Batches zählt und nicht einzelne Spans oder Log-Records, sofern Sie keine größenbasierte Queue konfigurieren.

Setzen Sie außerdem max_elapsed_time bewusst. Endlose Retries wirken sicher, halten aber veraltete Daten in der Queue und verzögern frische Daten dahinter. Für die meisten Teams sind zehn bis fünfzehn Minuten eine sinnvolle Obergrenze.

Früh anreichern, früh filtern

Ressourcenattribute machen Telemetrie überhaupt erst navigierbar. Ohne k8s.namespace.name, k8s.deployment.name oder cloud.region beginnt jede Abfrage mit Rätselraten. Ergänzen Sie diese Attribute in der Agent-Ebene, wo der Collector die lokalen Metadaten sieht:

processors:
  resourcedetection:
    detectors: [env, system, ec2, eks]
    timeout: 2s
    override: false
  k8sattributes:
    auth_type: serviceAccount
    passthrough: false
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.deployment.name
        - k8s.pod.name
        - k8s.node.name
      labels:
        - tag_name: team
          key: app.kubernetes.io/team
          from: pod

Beim Filtern gilt das Umgekehrte: Verwerfen Sie Daten, die Sie nie abfragen werden, so früh wie möglich. Health-Check-Spans, Debug-Logs geschwätziger Bibliotheken und Metrik-Labels mit hoher Kardinalität verursachen an jeder Station Kosten. Der filter-Prozessor mit OTTL-Bedingungen deckt die meisten Fälle ab:

processors:
  filter/noise:
    error_mode: ignore
    traces:
      span:
        - attributes["http.route"] == "/healthz"
        - attributes["http.route"] == "/readyz"
    logs:
      log_record:
        - severity_number < SEVERITY_NUMBER_INFO

Setzen Sie in Produktion error_mode: ignore. Mit dem Standardwert propagate kann ein einziger fehlerhafter Datensatz dazu führen, dass der gesamte Batch abgelehnt wird.

Den Collector selbst überwachen

Ein Collector, der still Daten verwirft, ist schlimmer als einer, der abstürzt, denn niemand bemerkt es. Der Collector stellt eigene Telemetrie bereit, und die verdient dieselbe Sorgfalt wie Anwendungsmetriken:

service:
  telemetry:
    metrics:
      level: detailed
      readers:
        - pull:
            exporter:
              prometheus:
                host: 0.0.0.0
                port: 8888
  extensions: [health_check, file_storage/queue]

Diese Metriken sind im Tagesbetrieb am wichtigsten:

MetrikAussageAlarm, wenn
otelcol_receiver_refused_*Am Receiver abgelehnte Daten, oft durch den memory_limiterLänger als 5 Minuten ungleich null
otelcol_exporter_send_failed_*Exporte, die trotz Retries fehlgeschlagen sindAnhaltender Anstieg
otelcol_exporter_queue_sizeAktueller Füllstand der QueueÜber 70 % der Kapazität
otelcol_processor_dropped_*In einem Prozessor verworfene DatenUnerwartete Abweichung von der Baseline
otelcol_process_memory_rssResidenter Speicher des ProzessesNahe am Container-Limit

Die genauen Metriknamen tragen Suffixe wie _spans, _log_records oder _metric_points, und Prometheus hängt unter Umständen _total an. Sobald diese Metriken in Obseria ankommen, zeigt eine einfache OQL-Abfrage, wo über alle Gateways hinweg Daten verloren gehen:

fetch metrics
| filter metric.name == "otelcol_exporter_send_failed_spans"
| summarize failed = sum(value), by:{k8s.pod.name, exporter}
| sort failed desc

Schicken Sie die Eigentelemetrie des Collectors über eine separate, minimale Pipeline. Ist die Hauptpipeline überlastet, wollen Sie trotzdem sehen, warum.

Konfiguration versionieren und schrittweise ausrollen

Collector-Konfiguration ist Produktionscode. Ein Tippfehler im Namen eines Prozessors verhindert den Start des gesamten Collectors, eine falsche OTTL-Bedingung kann jede Logzeile verwerfen. Diese Praktiken verhindern die meisten Vorfälle:

  1. Konfiguration in Git halten und über dieselbe Pipeline ausrollen wie Anwendungscode, inklusive Review.
  2. Vor dem Deployment validieren mit otelcol validate --config=config.yaml in der CI, und zwar mit genau der Distribution und Version, die auch in Produktion läuft.
  3. Eigene Distribution bauen mit dem OpenTelemetry Collector Builder (ocb). Sie enthält nur die Komponenten, die Sie wirklich nutzen. Das verkleinert Angriffsfläche und Binary.
  4. Versionen pinnen und Release Notes lesen. Contrib-Komponenten entwickeln sich schnell, Konfigurationsschlüssel werden umbenannt oder als veraltet markiert.
  5. Stufenweise ausrollen: zuerst eine Gateway-Replica oder ein Node-Pool, dann 30 Minuten lang die Self-Monitoring-Metriken vergleichen, erst danach weiter.

Skalierung und Sicherheit

Gateways skalieren horizontal gut über die CPU. Nutzen Sie einen HorizontalPodAutoscaler auf CPU-Auslastung, setzen Sie ein PodDisruptionBudget, damit ein Node-Drain nie alle Replicas gleichzeitig entfernt, und verteilen Sie die Replicas über mehrere Zonen. Bei OTLP über gRPC hebeln langlebige Verbindungen ein einfaches L4-Load-Balancing aus. Verwenden Sie einen gRPC-fähigen Load Balancer oder setzen Sie max_connection_age am Receiver, damit sich Clients regelmäßig neu verbinden.

Auf der Sicherheitsseite gilt: TLS an allen Receivern terminieren, die Verkehr von außerhalb des Nodes annehmen, per Network Policy einschränken, welche Namespaces an das Gateway senden dürfen, und Backend-Tokens in Secrets ablegen, die über ${env:...} referenziert werden. Maskieren Sie personenbezogene Daten so nah an der Quelle wie möglich. Die Prozessoren transform und redaction können Attribute wie E-Mail-Adressen hashen oder entfernen, bevor die Daten Ihren Cluster verlassen.

Wie das zu Obseria passt

Obseria nimmt OTLP nativ entgegen. Das hier beschriebene Collector-Setup funktioniert deshalb unverändert: Richten Sie den Gateway-Exporter auf Ihren Obseria-Endpunkt in der EU- oder UK-Region, und bestehende SDKs liefern innerhalb weniger Minuten die ersten Traces. Teams, die weniger bewegliche Teile bevorzugen, überlassen die Erfassung auf Host-Ebene dem Obseria Agent und nutzen Obseria Pipeline für Filterung, Anreicherung und PII-Maskierung beim Ingest. Beide Ansätze lassen sich kombinieren, und weil alle Daten im OpenTelemetry-Format bleiben, können Sie sie jederzeit per OTLP exportieren.

Welchen Weg Sie auch wählen, die Prinzipien bleiben dieselben: Speicher schützen, Queues persistieren, früh anreichern, früh filtern und den Collector so genau beobachten wie die Services, die er überwacht.

Teilen

Marco Rossi

Head of Developer Relations, Obseria

Observability, die Antworten liefert.

Starten Sie kostenlos mit OpenTelemetry, ohne Kreditkarte.