Veröffentlicht am

OpenTelemetry auf Kubernetes: Observability-Stack

Teilen:
Authors

TL;DR

OpenTelemetry standardisiert die Erfassung von Traces, Metriken und Logs auf Kubernetes. Der OTel Collector läuft als DaemonSet und leitet Telemetriedaten an beliebige Backends weiter. Mit dem OpenTelemetry Operator funktioniert Auto-Instrumentation per Annotation — ohne Code-Änderungen in deinen Anwendungen.


OpenTelemetry auf Kubernetes einrichten

Vendor Lock-in bei Observability ist teuer. Wer heute Datadog nutzt und morgen zu Grafana Cloud wechseln will, muss ohne OpenTelemetry jede Anwendung umbauen. OTel löst das Problem: Ein offener Standard für Traces, Metriken und Logs — unabhängig vom Backend.

Die Architektur auf Kubernetes sieht so aus: Anwendungen senden Telemetriedaten an den OTel Collector, der als DaemonSet auf jedem Node läuft. Der Collector verarbeitet, filtert und leitet die Daten an Backends wie Jaeger, Prometheus oder Loki weiter.

Hier ist das DaemonSet für den OTel Collector:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
  namespace: observability
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      serviceAccountName: otel-collector
      containers:
        - name: collector
          image: otel/opentelemetry-collector-contrib:0.98.0
          args: ["--config=/etc/otel/config.yaml"]
          ports:
            - containerPort: 4317  # OTLP gRPC
            - containerPort: 4318  # OTLP HTTP
            - containerPort: 8888  # Collector-Metriken
          volumeMounts:
            - name: config
              mountPath: /etc/otel
      volumes:
        - name: config
          configMap:
            name: otel-collector-config

Collector-Konfiguration: Receivers, Processors, Exporters

Der Collector folgt einem Pipeline-Modell mit drei Stufen: Receivers nehmen Daten an, Processors transformieren sie, Exporters leiten sie weiter.

apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: observability
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
      prometheus:
        config:
          scrape_configs:
            - job_name: 'kubernetes-pods'
              kubernetes_sd_configs:
                - role: pod
              relabel_configs:
                - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                  action: keep
                  regex: true

    processors:
      batch:
        timeout: 5s
        send_batch_size: 1024
      memory_limiter:
        check_interval: 1s
        limit_mib: 512
        spike_limit_mib: 128
      resource:
        attributes:
          - key: k8s.cluster.name
            value: production
            action: upsert

    exporters:
      otlp/jaeger:
        endpoint: jaeger-collector.observability:4317
        tls:
          insecure: true
      prometheusremotewrite:
        endpoint: http://prometheus.monitoring:9090/api/v1/write
      loki:
        endpoint: http://loki.logging:3100/loki/api/v1/push

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [otlp/jaeger]
        metrics:
          receivers: [otlp, prometheus]
          processors: [memory_limiter, resource, batch]
          exporters: [prometheusremotewrite]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [loki]

Jede Pipeline ist unabhängig konfigurierbar. Traces gehen an Jaeger, Metriken an Prometheus, Logs an Loki. Du kannst jederzeit ein Backend austauschen, ohne die Anwendungen anzufassen.

Auto-Instrumentation mit dem OTel Operator

Manuell jede Anwendung mit OTel-SDKs instrumentieren ist aufwändig. Der OpenTelemetry Operator löst das per Sidecar-Injection und Init-Container — gesteuert über eine Annotation.

Operator installieren

# cert-manager ist Voraussetzung
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml

# OTel Operator per Helm installieren
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update

helm install otel-operator open-telemetry/opentelemetry-operator \
  --namespace observability \
  --create-namespace \
  --set admissionWebhooks.certManager.enabled=true

Instrumentation-CR definieren

Die Instrumentation Custom Resource beschreibt, wie Auto-Instrumentation für verschiedene Sprachen funktioniert:

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: auto-instrumentation
  namespace: observability
spec:
  exporter:
    endpoint: http://otel-collector.observability:4317
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.4.0
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:0.45b0
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:0.49.1
  dotnet:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:1.2.0

Anwendungen instrumentieren

Jetzt reicht eine einzige Annotation am Pod, um Auto-Instrumentation zu aktivieren:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-java: "observability/auto-instrumentation"
    spec:
      containers:
        - name: order-service
          image: order-service:v2.1.0
SpracheAnnotationMechanismus
Javainject-javaJavaagent via Init-Container
Pythoninject-pythonSite-Packages Injection
Node.jsinject-nodejs--require Flag
.NETinject-dotnetStartup Hook

Der Operator injiziert automatisch einen Init-Container, der das passende SDK in den Anwendungs-Container kopiert. Beim nächsten Pod-Start sendet die Anwendung Traces und Metriken an den Collector — ohne eine Zeile Code zu ändern.

Sampling-Strategie

Nicht jeder Request braucht einen Trace. Bei hohem Traffic erzeugt 100%-Sampling enorme Datenmengen. Der parentbased_traceidratio-Sampler in der Instrumentation-CR steuert die Sampling-Rate.

Ein Wert von 0.25 bedeutet: 25% aller Root-Traces werden aufgezeichnet. Child-Spans folgen der Entscheidung des Parent — so bleiben Traces vollständig oder werden komplett verworfen.

Für Production-Umgebungen hat sich eine Kombination bewährt: Niedrige Basis-Rate (5-25%) plus Tail-Based Sampling im Collector für Fehler und langsame Requests. So fängst du garantiert die interessanten Traces ein.

processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-requests
        type: latency
        latency: {threshold_ms: 2000}
      - name: probabilistic
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

Resource-Anforderungen

Der OTel Collector braucht auf Kubernetes angemessene Ressourcen. Zu wenig Memory führt zu Datenverlust, zu viel verschwendet Cluster-Kapazität.

KomponenteCPU RequestMemory RequestCPU LimitMemory Limit
Collector (DaemonSet)200m256Mi500m512Mi
Operator100m128Mi250m256Mi

Der memory_limiter-Processor ist essentiell. Er verwirft Daten bevor der Collector OOM-killed wird. Setze limit_mib auf ca. 80% des Memory-Limits.

Vendor-agnostic: Backends austauschen

Der größte Vorteil von OpenTelemetry zeigt sich beim Backend-Wechsel. Deine Anwendungen senden OTLP — was damit passiert, entscheidet die Collector-Konfiguration. Von Jaeger zu Tempo wechseln? Eine Zeile im Exporter ändern. Grafana Cloud statt Self-Hosted? Endpoint und Auth anpassen, fertig.

# Collector-Status prüfen
kubectl get pods -n observability -l app=otel-collector

# Collector-Metriken abfragen
kubectl port-forward -n observability daemonset/otel-collector 8888:8888
curl localhost:8888/metrics | grep otelcol_receiver_accepted

FAQ

Brauche ich den OTel Operator oder reicht der Collector?

Der Collector allein reicht, wenn du deine Anwendungen manuell mit OTel-SDKs instrumentierst. Der Operator ist optional und bietet Auto-Instrumentation plus Lifecycle-Management für Collector-Instanzen. Für Teams mit vielen Microservices spart der Operator erheblich Aufwand.

Wie unterscheidet sich OTel Collector von Prometheus?

Prometheus scrapt Metriken aktiv per Pull. Der OTel Collector empfängt Daten per Push (OTLP) und kann zusätzlich auch scrapen. Der Collector verarbeitet außerdem Traces und Logs — Prometheus ist rein auf Metriken fokussiert. Beide ergänzen sich gut.

Was passiert, wenn der Collector ausfällt?

Als DaemonSet läuft auf jedem Node ein Collector-Pod. Fällt einer aus, startet Kubernetes ihn automatisch neu. Während des Ausfalls gehen Telemetriedaten dieses Nodes verloren. Für kritische Pipelines empfiehlt sich ein Gateway-Collector als zusätzliche Pufferinstanz.

Wie hoch ist der Performance-Overhead der Auto-Instrumentation?

Typischerweise 2-5% CPU-Overhead und 50-100 MB zusätzlicher Memory-Verbrauch pro instrumentierter Anwendung. Java-Agents haben den höchsten Overhead, Node.js den niedrigsten. Teste in Staging mit realistischer Last.


Nächster Schritt: Ergänze deine Observability-Pipeline um ein effektives Alerting. Mit Prometheus Alerting für Kubernetes reagierst du automatisch auf Anomalien in deinen Telemetriedaten.

Kubernetes-Expertise gesucht?

Managed Services, Beratung, Training oder Security – wir unterstützen deutsche Unternehmen bei allen Kubernetes-Themen.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen