- Authors

- Name
- Phillip Pham
- @ddppham
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
| Sprache | Annotation | Mechanismus |
|---|---|---|
| Java | inject-java | Javaagent via Init-Container |
| Python | inject-python | Site-Packages Injection |
| Node.js | inject-nodejs | --require Flag |
| .NET | inject-dotnet | Startup 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.
| Komponente | CPU Request | Memory Request | CPU Limit | Memory Limit |
|---|---|---|---|---|
| Collector (DaemonSet) | 200m | 256Mi | 500m | 512Mi |
| Operator | 100m | 128Mi | 250m | 256Mi |
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
OpenTelemetry auf Kubernetes: Observability DSGVO-konform
OpenTelemetry auf Kubernetes einrichten und DSGVO-konform betreiben. Metriken, Traces und Logs mit Prometheus, Jaeger und Grafana im deutschen Rechenzentrum.
Kubernetes Alerting: Prometheus-Regeln richtig setzen
Prometheus Alerting für Kubernetes richtig aufbauen: Alert-Hierarchie, symptombasierte Regeln und Routing zu Slack oder PagerDuty ohne Alert Fatigue.
Distributed Tracing: Jaeger auf Kubernetes einrichten
Jaeger für Distributed Tracing auf Kubernetes einrichten mit dem Jaeger Operator. OpenTelemetry-Instrumentation in Go und Python, Trace-Propagation zwischen Microservices und praktische Analyse von Latenz-Problemen.
Grafana Dashboards für Kubernetes: Best Practices
Effektive Grafana Dashboards für Kubernetes mit USE- und RED-Methode erstellen. Dashboard-as-Code mit ConfigMap-Provisioning und bewährte Panel-Layouts.
ELK vs Loki: Kubernetes-Logging im Vergleich
ELK Stack und Grafana Loki im direkten Vergleich für Kubernetes-Logging. Architektur, Kosten, Ressourcenverbrauch und praktische Helm-Installation mit FluentBit-Konfiguration.