---
title: "OpenTelemetry erklärt: Traces, Metriken, Logs und der Collector"
description: "Was OpenTelemetry ist und was nicht, wie Traces, Metriken und Logs zusammenhängen, wie API, SDK, OTLP und Collector Daten bewegen, Agent oder Gateway, Zero-Code-Instrumentierung auf Kubernetes, Sampling und die Fehler, die Observability in der Produktion teuer machen."
author: Aleksei Aleinikov
date: 2026-10-14
lang: de
tags: [opentelemetry, observability, distributed-tracing, otel-collector, otlp, kubernetes, prometheus]
canonical: https://www.alekseialeinikov.com/de/blog/topics/devops/opentelemetry-erklaert-collector-otlp-tracing
source: alekseialeinikov.com
---

# OpenTelemetry erklärt: Traces, Metriken, Logs und der Collector

Ihr Pager geht um drei Uhr nachts: Die Checkout-Latenz hat sich verdoppelt. Das Dashboard zeigt die Spitze, die Logs aus zwölf Services zeigen nichts Auffälliges, und niemand kann sagen, welcher der vierzig Aufrufe hinter einem Checkout langsam ist. Diese Lücke – zwischen *irgendetwas stimmt nicht* und *genau das stimmt nicht* – schließt Observability, und OpenTelemetry ist das Werkzeug, mit dem die meisten Teams sie heute schließen.

Gleichzeitig gehört es zu den am häufigsten missverstandenen Projekten der Cloud-native-Welt. Viele installieren es in der Erwartung eines Monitoring-Produkts und finden eine Spezifikation, ein Dutzend SDKs und einen Collector mit Hunderten Komponenten. Dieser Leitfaden zeigt, wie die Teile zusammenpassen, wie Daten tatsächlich vom Code ins Backend gelangen, wie Sie das Ganze auf Kubernetes betreiben, ohne in Konfiguration zu ertrinken, und welche Entscheidungen es in der Produktion günstig oder teuer machen. Alle Konfigurationen und Codebeispiele wurden gegen OpenTelemetry Collector Contrib v0.162.0 ausgeführt.

![OpenTelemetry erklärt: Ein Standard macht aus Aufrufen zwischen Services Traces, Metriken und Logs, die jedes Backend lesen kann.](https://www.alekseialeinikov.com/blog/opentelemetry-explained.webp)

## Was ist OpenTelemetry?

**OpenTelemetry** (oft kurz **OTel**) ist ein Open-Source-Framework für Observability, mit dem Sie **Telemetrie erzeugen, sammeln und exportieren** – Traces, Metriken und Logs. Es ist herstellerneutral: Dieselbe Instrumentierung kann Daten an Jaeger, Prometheus, Grafana, Google Cloud, Datadog oder jedes andere Backend senden, das sein Protokoll versteht.

Genauso wichtig ist, was es *nicht* ist. Nach eigener Aussage des Projekts ist OpenTelemetry **kein Observability-Backend**: Speicherung und Visualisierung überlässt es bewusst anderen Werkzeugen. Wer Dashboards sucht, sucht in der falschen Schicht.

Das Projekt beruht auf zwei Versprechen: **Die Daten, die Sie erzeugen, gehören Ihnen** – ohne Vendor-Lock-in – und **Sie lernen einen Satz APIs und Konventionen** statt einen pro Anbieter. Es entstand 2019 aus der Fusion von OpenTracing und OpenCensus, zwei älteren Projekten, die dasselbe Problem getrennt lösen wollten. Im Mai 2019 kam es in die CNCF, im August 2021 auf die Stufe Incubating und am **11. Mai 2026** auf **Graduated** – dieselbe Reifestufe wie Kubernetes und Prometheus.

OpenTelemetry besteht aus:

- **Einer Spezifikation** für alle Komponenten und einem **Protokoll**, OTLP, für den Datentransport.
- **Semantic Conventions** – Standardnamen wie `http.request.method` oder `k8s.pod.name`, damit jede Bibliothek dasselbe gleich benennt.
- **APIs und SDKs** für C++, .NET, Erlang, Go, Java, JavaScript, PHP, Python, Ruby, Rust, Swift und Kotlin.
- **Instrumentierungsbibliotheken** und **Zero-Code-Instrumentierung**, die Telemetrie aus gängigen Frameworks erzeugen, ohne dass Sie Code ändern.
- **Dem Collector**, einem Proxy, der Telemetrie empfängt, verarbeitet und exportiert.
- **Dem Kubernetes Operator** und **Helm Charts**, um all das im Cluster zu betreiben.

## Monitoring vs. Observability

Beide Begriffe werden oft synonym verwendet, beantworten aber unterschiedliche Fragen.

**Monitoring** sagt Ihnen, *ob* etwas nicht stimmt. Sie legen vorher fest, was Sie beobachten – CPU, Fehlerrate, Queue-Länge – und alarmieren, wenn ein Wert eine Schwelle überschreitet. Das funktioniert gut für Ausfälle, die man vorhersehen kann.

**Observability** lässt Sie herausfinden, *warum* etwas nicht stimmt, auch bei Ausfällen, die niemand vorhergesehen hat. Die Definition von OpenTelemetry ist kurz: Observability ist die Fähigkeit, den inneren Zustand eines Systems anhand seiner Ausgaben zu verstehen. In Software sind diese Ausgaben Telemetrie, und das System muss **instrumentiert** sein, um sie zu erzeugen.

| | Monitoring | Observability |
| --- | --- | --- |
| Frage | Ist das System gesund? | Warum verhält es sich so? |
| Vorab bekannt | Die Fehlerbilder | Nur die zu sammelnden Ausgaben |
| Typische Daten | Dashboards und Schwellwert-Alerts auf Metriken | Traces, Metriken und Logs, filterbar nach jedem Attribut |
| Stark bei | Vorhersehbaren Ausfällen: Platte voll, Pod im Crash-Loop | Neuen Ausfällen: ein Mandant langsam auf einem Endpunkt nach einem Deploy |

Sie brauchen beides. Monitoring weckt Sie auf, Observability zeigt Ihnen, wo Sie suchen müssen. OpenTelemetry kümmert sich um den zweiten Teil – reichhaltige, zusammenhängende Daten in einer Standardform aus dem System zu holen.

## Traces, Metriken und Logs: die drei Signale

OpenTelemetry nennt jede Art von Telemetrie ein **Signal**. Drei sind heute stabil; ein viertes, **Profile**, ist noch in Entwicklung.

![Die drei OpenTelemetry-Signale: ein Trace aus Spans, Metriken als Zähler und Histogramme und Logs mit derselben Trace-ID.](https://www.alekseialeinikov.com/blog/opentelemetry-explained-signals.webp "Eine Anfrage, drei Sichten – und die Trace-ID verbindet sie.")

### Traces und Spans

Ein **Trace** ist der Weg einer Anfrage durch Ihr System. Er besteht aus **Spans**: ein Span pro Arbeitseinheit – ein HTTP-Handler, eine Datenbankabfrage, ein Aufruf beim Zahlungsdienstleister. Jeder Span hat einen Namen, Start- und Endzeit, eine **Span-Art** (Server, Client, Producer, Consumer oder intern), Attribute, optionale Events und einen Status.

Spans bilden einen Baum. Der erste Span ist der **Root Span**; jeder andere verweist über eine Parent-Span-ID auf seinen **Parent**, und alle teilen sich eine **Trace-ID**. So zeichnet ein Tracing-Backend den Wasserfall mit den 40 Aufrufen hinter einem Checkout – und zeigt, welcher davon 1,8 Sekunden gedauert hat.

### Context Propagation

Ein Trace funktioniert über Service-Grenzen nur, wenn die Trace-ID mit der Anfrage mitreist. Das nennt man **Context Propagation**. OpenTelemetry nutzt standardmäßig den **W3C-Trace-Context**-Standard: Jede ausgehende HTTP-Anfrage trägt einen `traceparent`-Header.

```http title="traceparent header"
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
```

Die vier Felder sind die Version (`00`), die **Trace-ID** mit 32 Hex-Zeichen, die **Parent-Span-ID** mit 16 Hex-Zeichen und die **Trace Flags** (`01` bedeutet *gesampelt*). Ein zweiter Header, `baggage`, transportiert Schlüssel-Wert-Paare, die jeder nachgelagerte Service sehen soll, etwa eine Mandanten-ID. Für unterstützte HTTP- und gRPC-Bibliotheken setzen und lesen die SDKs beide Header automatisch.

> [!WARNING]
> Über eine Message Queue wandert der Kontext nicht von selbst. Veröffentlicht ein Service nach Kafka oder Pub/Sub, muss der Trace-Kontext in den Headern oder Attributen der Nachricht mitreisen, und der Consumer muss ihn auslesen. Instrumentierungsbibliotheken für gängige Clients erledigen das; selbst geschriebene Producer meist nicht – und genau dort zerbrechen die meisten Traces in zwei Teile.

### Metriken

**Metriken** sind über die Zeit aggregierte Zahlen: Anzahl der Anfragen, Latenzverteilungen, Queue-Längen. OpenTelemetry bietet **Counter** (steigen nur), **Up-Down-Counter**, **Histogramme** (Verteilungen wie die Antwortzeit) und **Gauges** (aktueller Wert), jeweils synchron zum Aufruf aus dem Code und asynchron, vom SDK abgefragt.

Metriken sind günstig zu speichern, weil sie aggregiert sind – ein Histogramm fasst Millionen Anfragen zusammen. Ihre Schwäche ist die Kardinalität, dazu unten ein eigener Abschnitt. **Exemplars** verbinden beide Welten: Ein Latenz-Histogramm kann die Trace-ID einer Beispielanfrage mitführen, sodass Sie von einem langsamen Bucket direkt zu einem echten Trace springen.

### Logs

OpenTelemetry verlangt nicht, dass Sie Ihr Logging umschreiben. Eine **Logs Bridge** verbindet stattdessen vorhandene Bibliotheken – `logging` in Python, Log4j oder Logback in Java und so weiter – mit dem SDK. Jeder Eintrag wird als OTLP-Log mit **Trace-ID und Span-ID des aktiven Spans** exportiert, sodass Sie in einem Trace genau die Logzeilen sehen, die währenddessen geschrieben wurden.

Der Collector kann außerdem Logs lesen, die nie durch ein SDK gingen, etwa Container-Logdateien auf einem Node, und sie ins selbe Format bringen.

## Wie OpenTelemetry funktioniert: API, SDK, OTLP und Collector

Die Architektur hat vier Schichten, und wer sie verwechselt, wird schnell frustriert.

- **Die API** ist das, was Ihr Code und Ihre Bibliotheken aufrufen: einen Tracer holen, einen Span starten, eine Metrik erfassen. Allein bewirkt sie nichts – Aufrufe sind No-ops, bis ein SDK installiert ist. Das ist Absicht: Eine Bibliothek kann mit der API instrumentiert werden, ohne jeden Nutzer zu Telemetrie zu zwingen.
- **Das SDK** implementiert die API. Es sampelt Spans, aggregiert Metriken, ergänzt **Resource-Attribute**, die den Prozess beschreiben (`service.name`, `service.version`, `k8s.pod.name`), bündelt die Daten und übergibt sie an einen **Exporter**.
- **OTLP** ist das Protokoll, das der Exporter spricht.
- **Der Collector** ist ein optionaler, aber fast immer vorhandener Zwischenschritt zwischen Anwendungen und Backends.

### OTLP, das Übertragungsprotokoll

OTLP überträgt alle drei stabilen Signale über zwei Transportwege:

| | OTLP/gRPC | OTLP/HTTP |
| --- | --- | --- |
| Standardport | **4317** | **4318** |
| Kodierung | Protobuf | Binäres Protobuf oder JSON |
| Pfade | gRPC-Services | `/v1/traces`, `/v1/metrics`, `/v1/logs` |
| Gut für | Service zu Collector, Collector zu Collector | Browser, Proxys, Firewalls, die nur HTTP erlauben |

Das Protokoll legt fest, wie man sich bei Fehlern verhält – wichtig, wenn Telemetrie während eines Vorfalls sprunghaft ansteigt. Über HTTP sind `429`, `502`, `503` und `504` wiederholbar, und ein `Retry-After`-Header muss beachtet werden; ein `400` heißt, die Daten sind fehlerhaft und werden verworfen, nicht wiederholt. Ein Server kann auch nur einen Teil eines Batches annehmen und melden, wie viele Einträge er abgelehnt hat. Die Spezifikation empfiehlt eine Grenze von **64 MiB** pro Anfrage und nimmt in Kauf, dass ein Retry nach einem Netzwerkfehler **Duplikate** erzeugen kann – ein bewusster Kompromiss für Telemetrie.

OTLP lässt sich mit nichts außer `curl` ausprobieren. Mit einem lokalen Collector auf Port 4318 erzeugt diese Anfrage einen Span:

```bash title="send-span.sh"
curl -X POST http://127.0.0.1:4318/v1/traces \
  -H 'Content-Type: application/json' \
  -d '{"resourceSpans":[{"resource":{"attributes":[
        {"key":"service.name","value":{"stringValue":"checkout"}}]},
      "scopeSpans":[{"scope":{"name":"manual-test"},"spans":[{
        "traceId":"5b8efff798038103d269b633813fc60c",
        "spanId":"eee19b7ec3c1b174",
        "name":"POST /checkout","kind":2,
        "startTimeUnixNano":"1760425200000000000",
        "endTimeUnixNano":"1760425200250000000"}]}]}]}'
# → {"partialSuccess":{}}   HTTP 200
```

Beachten Sie die Details der JSON-Kodierung: Trace- und Span-IDs sind **Hex-Strings**, nicht Base64, und `kind` ist die **Zahl** `2` (Server), kein Name.

## Der OpenTelemetry Collector

Der **Collector** ist ein einzelnes Go-Binary, konfiguriert mit YAML. Er ist der Teil von OpenTelemetry, den Betriebsteams tatsächlich betreiben, und es lohnt sich, ihn richtig zu verstehen.

![Im OpenTelemetry Collector: Receiver speisen Prozessoren in fester Reihenfolge, Exporter senden an Backends, Connectoren verbinden Pipelines.](https://www.alekseialeinikov.com/blog/opentelemetry-explained-collector.webp "Die Reihenfolge zählt: zuerst begrenzen und verwerfen, in der Mitte anreichern, zuletzt bündeln.")

### Receiver, Prozessoren, Exporter und Connectoren

Eine Konfiguration kennt vier Arten von Pipeline-Komponenten:

- **Receiver** holen Daten herein. `otlp` ist der wichtigste; andere scrapen Prometheus-Endpunkte, lesen Kafka-Topics, folgen Logdateien oder sammeln Host-Metriken.
- **Prozessoren** verändern Daten unterwegs: Speicher begrenzen, filtern, Attribute ergänzen oder löschen, sampeln, bündeln.
- **Exporter** senden Daten hinaus – an einen anderen Collector, Prometheus, Google Cloud, Datadog, eine Datei oder an den `debug`-Exporter, der auf die Konsole schreibt.
- **Connectoren** verbinden zwei Pipelines und sind auf der einen Seite Exporter, auf der anderen Receiver. Der `spanmetrics`-Connector etwa macht aus Spans Metriken für Anfragerate, Fehler und Dauer.

Eine Komponente zu definieren, bewirkt allein nichts. Sie läuft erst, wenn Sie sie unter `service.pipelines` in einer Pipeline aufführen, getrennt für `traces`, `metrics` und `logs`.

### Die Reihenfolge der Prozessoren ist keine Kosmetik

Prozessoren laufen in der Reihenfolge, in der Sie sie auflisten, und das Projekt dokumentiert eine empfohlene Reihenfolge:

1. **`memory_limiter` zuerst**, damit der Collector Daten ablehnen kann, bevor ihm der Speicher ausgeht.
2. **Sampling und Filter als Nächstes**, damit keine CPU für Daten verloren geht, die gleich verworfen werden.
3. **Prozessoren, die den Request-Kontext brauchen**, etwa `k8sattributes`, vor dem Batching – das Batching löscht diesen Kontext.
4. **Anreicherung und Transformation**: `attributes`, `transform`, `resource`.
5. **Batching zuletzt**, am besten im Exporter selbst über seine Sending Queue statt über einen eigenen `batch`-Prozessor.

Hier eine vollständige Agent-Konfiguration, wie sie auf jedem Kubernetes-Node laufen würde:

```yaml title="otel-agent.yaml"
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 20
  k8sattributes:
    extract:
      metadata: [k8s.namespace.name, k8s.deployment.name, k8s.pod.name, k8s.node.name]
  resourcedetection:
    detectors: [env, gcp]
    timeout: 5s
  attributes/scrub:
    actions:
      - key: http.request.header.authorization
        action: delete
      - key: user.email
        action: hash

exporters:
  otlp_grpc/gateway:
    endpoint: otel-gateway.observability.svc.cluster.local:4317
    sending_queue:
      batch:

extensions:
  health_check:
    endpoint: 0.0.0.0:13133

service:
  extensions: [health_check]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, k8sattributes, resourcedetection, attributes/scrub]
      exporters: [otlp_grpc/gateway]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, k8sattributes, resourcedetection]
      exporters: [otlp_grpc/gateway]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, k8sattributes, resourcedetection, attributes/scrub]
      exporters: [otlp_grpc/gateway]
```

> [!TIP]
> Führen Sie `otelcol-contrib validate --config=otel-agent.yaml` in der CI aus. Der Befehl lädt jede Komponente und lehnt unbekannte Schlüssel ab, sodass ein Tippfehler die Pipeline scheitern lässt statt den Rollout. Neuere Collector-Versionen nennen die Exporter `otlp_grpc` und `otlp_http`; die älteren Namen `otlp` und `otlphttp`, die Sie noch in vielen Beispielen und Hersteller-Manifesten finden, werden ebenfalls akzeptiert.

In dieser Datei stecken zwei Sicherheitsdetails. Der Collector bindet standardmäßig an `localhost`; `0.0.0.0` ist in einem Pod nötig, damit andere Pods ihn erreichen, und sollte dann per NetworkPolicy abgesichert werden. Und der Prozessor `attributes/scrub` ist die günstigste Stelle in Ihrem gesamten Stack, um zu verhindern, dass ein Authorization-Header oder eine E-Mail-Adresse in einem fremden Backend landet.

### Welche Collector-Distribution?

Der Collector wird aus Komponenten gebaut, und mehrere **Distributionen** bündeln unterschiedliche Sätze:

- **`otelcol`** (Core) – eine kleine Auswahl an Upstream-Komponenten.
- **`otelcol-contrib`** – fast alle Community-Komponenten; bequem zum Lernen, für die Produktion größer als nötig.
- **`otelcol-k8s`** – für Kubernetes ausgewählte Komponenten.
- **Hersteller-Distributionen** wie der Google-Built OpenTelemetry Collector oder Grafana Alloy, getestet gegen eine Plattform.
- **Ihr eigener Build** mit dem OpenTelemetry Collector Builder (`ocb`), der genau die Komponenten enthält, die Sie nutzen – die kleinste Angriffsfläche.

## Agent oder Gateway: wie Sie den Collector betreiben

Es gibt zwei Bausteine, und die meisten Produktionsumgebungen nutzen beide.

![Betriebsmodelle für den OpenTelemetry Collector: direkter Export, ein Agent auf jedem Node und Agent plus Gateway mit Tail Sampling.](https://www.alekseialeinikov.com/blog/opentelemetry-explained-deploy.webp "Mit Agents beginnen; ein Gateway ergänzen, wenn Sie eine zentrale Stelle für Regeln brauchen.")

Ein **Agent** läuft nah an der Anwendung – als **DaemonSet** auf jedem Kubernetes-Node oder als Sidecar. Anwendungen senden an einen lokalen Endpunkt, was ihre Konfiguration trivial hält, und der Agent ergänzt Host- und Pod-Metadaten günstig, weil er auf demselben Node läuft.

Ein **Gateway** ist ein zentraler, horizontal skalierter Pool von Collectoren hinter einem OTLP-Endpunkt – meist einer pro Cluster oder Region. Die offizielle Deployment-Anleitung nennt als Vorteile zentral verwaltete Credentials und zentrale Regeln wie Filter und Sampling, als Kosten eine weitere Komponente, die gewartet werden muss und ausfallen kann, zusätzliche Latenz und mehr Ressourcenverbrauch.

| | Direkter Export | Nur Agent | Agent + Gateway |
| --- | --- | --- | --- |
| Bewegliche Teile | Keine | Ein DaemonSet | DaemonSet + Deployment |
| Backend-Credentials | In jeder Anwendung | Auf jedem Node | An einer Stelle |
| Kubernetes-Metadaten | Vom SDK | Ja, günstig | Ja, vom Agent ergänzt |
| Tail Sampling | Nein | Nein | **Ja** |
| Fan-out an mehrere Backends | Pro Anwendung | Pro Node | Zentral |
| Geeignet für | Prototypen, wenige Services | Die meisten Cluster | Plattformen mit vielen Teams, regulierte Daten, hohes Volumen |

### Tail Sampling braucht alle Spans an einer Stelle

Tail Sampling ist der Hauptgrund für ein Gateway, und es hat einen Haken: Ein Sampler kann einen Trace nur beurteilen, wenn er *alle* seine Spans sieht, während diese Spans von vielen Pods kommen. Die dokumentierte Lösung sind **zwei Schichten**. Die erste nutzt den **`load_balancing`-Exporter** mit `routing_key: traceID`, um alle Spans eines Traces an dieselbe Instanz der zweiten Schicht zu senden, die den Prozessor `tail_sampling` ausführt.

Die folgende Konfiguration der zweiten Schicht behält jeden Fehler, jeden Trace über zwei Sekunden und 10 % vom Rest – und berechnet Metriken für Anfragerate, Fehler und Dauer aus **allen** Spans *vor* dem Sampling, sodass Dashboards korrekt bleiben, obwohl nur ein Teil der Traces gespeichert wird:

```yaml title="otel-gateway.yaml"
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

connectors:
  spanmetrics:          # RED metrics from 100% of spans, before sampling
  forward/sample:       # hands the same spans to the sampling pipeline

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 20
  tail_sampling:
    decision_wait: 10s
    num_traces: 100000
    policies:
      - name: keep-errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: keep-slow
        type: latency
        latency: { threshold_ms: 2000 }
      - name: sample-the-rest
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }
  resource/gcp:
    attributes:
      - key: gcp.project_id
        value: ${env:GOOGLE_CLOUD_PROJECT}
        action: insert

exporters:
  otlp_http/gcp:
    endpoint: https://telemetry.googleapis.com
    encoding: proto
    auth: { authenticator: googleclientauth }
    sending_queue:
      batch:

extensions:
  googleclientauth:

service:
  extensions: [googleclientauth]
  pipelines:
    traces/in:
      receivers: [otlp]
      processors: [memory_limiter]
      exporters: [spanmetrics, forward/sample]
    traces/sampled:
      receivers: [forward/sample]
      processors: [tail_sampling, resource/gcp]
      exporters: [otlp_http/gcp]
    metrics/red:
      receivers: [spanmetrics]
      processors: [resource/gcp]
      exporters: [otlp_http/gcp]
```

Die Regeln werden gemeinsam ausgewertet: Ein Trace bleibt erhalten, wenn *irgendeine* von ihnen zustimmt. `decision_wait` legt fest, wie lange das Gateway einen Trace puffert, bevor es entscheidet, und `num_traces` begrenzt, wie viele es im Speicher hält – dimensionieren Sie beides nach Ihrem Verkehr, denn diese Schicht ist zustandsbehaftet.

### Eine Regel für Metriken: ein einziger Writer

Exportieren mehrere Collectoren denselben Metrik-Stream, können sie sich gegenseitig überschreiben und in Prometheus Lücken, Sprünge und Fehler wie „out-of-order sample" erzeugen. Das OpenTelemetry-Datenmodell verlangt für jeden Metrik-Stream einen **einzigen Writer**. In der Praxis heißt das: Jeder Stream braucht Attribute, die seine Herkunft eindeutig machen – `k8sattributes` und `resourcedetection` liefern sie –, und zwei Collectoren dürfen nicht dasselbe Ziel scrapen.

## Code instrumentieren

Es gibt zwei Wege, eine Anwendung Telemetrie erzeugen zu lassen, und Sie werden beide nutzen.

### Zero-Code-Instrumentierung

**Zero-Code**-Instrumentierung (auch automatische Instrumentierung) hängt sich zur Laufzeit in gängige Frameworks und Clients – Webserver, HTTP-Clients, Datenbanktreiber, Message Queues – und erzeugt dafür Spans und Metriken, ohne dass Sie den Quellcode anfassen.

```bash title="Python"
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install   # adds instrumentation for installed libraries
OTEL_SERVICE_NAME=checkout \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317 \
opentelemetry-instrument python app.py
```

```bash title="Node.js"
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node
export OTEL_SERVICE_NAME=checkout
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node app.js
```

Für Java ist das Gegenstück der OpenTelemetry-Java-Agent, eingebunden mit `-javaagent:opentelemetry-javaagent.jar`. Python arbeitet per Monkey Patching der Bibliotheken zur Laufzeit, der Java-Agent per Bytecode-Umschreibung beim Laden der Klassen.

> [!NOTE]
> Verwenden Sie `127.0.0.1` statt `localhost`, wenn Sie ein SDK auf einen lokalen Collector zeigen lassen. Auf manchen Rechnern löst `localhost` zuerst zur IPv6-Adresse `::1` auf, der Collector lauscht nur auf IPv4, und es antwortet ein anderer Prozess – in unserem Test ein verwirrendes `403 Forbidden` von einem fremden Dienst.

### Manuelle Spans dort, wo die Geschäftslogik liegt

Automatische Instrumentierung sieht HTTP und SQL. Sie sieht nicht, dass eine Anfrage eine *Erstattung* war, zu welchem *Mandanten* sie gehörte oder dass der Zahlungsdienstleister beim dritten Versuch in ein Timeout lief. Das ergänzen Sie mit wenigen Zeilen API:

```python title="payments.py"
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

provider = TracerProvider(resource=Resource.create({"service.name": "checkout"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("checkout.payments")


def charge(order_id: str, amount_cents: int) -> None:
    with tracer.start_as_current_span("charge card") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("payment.amount_cents", amount_cents)
        try:
            call_payment_provider(order_id, amount_cents)
        except TimeoutError as exc:
            span.record_exception(exc)
            span.set_status(trace.Status(trace.StatusCode.ERROR, "provider timeout"))
            raise
```

Läuft der Zahlungsdienstleister in ein Timeout, erhält der Collector einen Span namens `charge card` mit Status `Error`, der Meldung `provider timeout`, beiden Attributen und einem Exception-Event mit dem Stacktrace. Nutzen Sie zusätzlich Zero-Code-Instrumentierung, lassen Sie die Provider-Zeilen weg – der Agent konfiguriert das SDK, und Ihr Code braucht nur `trace.get_tracer`.

Halten Sie sich bei allem, was bereits einen Standardnamen hat (`http.request.method`, `db.system.name`, `messaging.system`), an die **Semantic Conventions**, und nutzen Sie für den Rest einen eigenen Namensraum wie `payment.amount_cents`.

### Reifegrad nach Sprache

Traces sind in fast allen Sprachen stabil, Metriken in den meistgenutzten, Logs nur in einigen. Die offizielle Statustabelle:

| Sprache | Traces | Metriken | Logs |
| --- | --- | --- | --- |
| Java, .NET, C++, PHP | Stabil | Stabil | Stabil |
| Go | Stabil | Stabil | Release Candidate |
| Python, JavaScript | Stabil | Stabil | Development |
| Rust | Beta | Stabil | Stabil |
| Ruby, Swift, Erlang/Elixir | Stabil | Development | Development |
| Kotlin | Development | Development | Development |

Development heißt nicht unbrauchbar – viele Teams verschicken Python-Logs über OpenTelemetry –, aber APIs können sich zwischen Releases noch ändern. Pinnen Sie die Versionen.

## OpenTelemetry auf Kubernetes betreiben

Der **OpenTelemetry Operator** verwaltet im Cluster zweierlei: Collectoren über eine `OpenTelemetryCollector`-Ressource, die als Deployment, DaemonSet, StatefulSet oder Sidecar laufen kann, und die **automatische Instrumentierung** über eine `Instrumentation`-Ressource. Für seinen Admission Webhook braucht er meist **cert-manager**.

Die `Instrumentation`-Ressource beschreibt, wohin Daten gehen und wie gesampelt wird:

```yaml title="instrumentation.yaml"
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: default
  namespace: shop
spec:
  exporter:
    endpoint: http://otel-agent.observability.svc.cluster.local:4318
  propagators: [tracecontext, baggage]
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"
```

Anwendungen melden sich mit einer Annotation am **Pod-Template** an:

```yaml title="deployment.yaml (excerpt)"
spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-python: "true"
```

Läuft der Agent als DaemonSet, geben Sie seinem Service `internalTrafficPolicy: Local`, damit jeder Pod an den Agent auf seinem eigenen Node sendet statt an einen beliebigen im Cluster.

Beim Start des Pods ergänzt der Operator einen Init-Container, der die Instrumentierung in den Pod kopiert und die Umgebungsvariablen setzt. Drei Details verursachen die meisten gescheiterten Rollouts:

- **Die Reihenfolge zählt.** Die `Instrumentation`-Ressource muss existieren, *bevor* die Pods starten; vorher erzeugte Pods werden erst nach einem Neustart instrumentiert.
- **Die Annotation gehört nach `spec.template.metadata.annotations`**, nicht in die Metadaten des Deployments selbst.
- **Die Protokolle unterscheiden sich je Sprache.** Die Python-Injektion unterstützt nur OTLP über HTTP, der Endpunkt braucht also Port **4318**; Node.js nutzt standardmäßig gRPC auf **4317**. Go wird über einen privilegierten eBPF-Sidecar instrumentiert und ist hinter einem Feature Gate standardmäßig deaktiviert.

Auf GKE können Sie auf eigene Collectoren verzichten: Google bietet **Managed OpenTelemetry for GKE** an und veröffentlicht außerdem den **Google-Built OpenTelemetry Collector** mit einer geprüften Konfiguration, falls Sie lieber selbst betreiben. Der Rest des Cluster-Designs – Workload Identity, NetworkPolicies, Platz des Gateways – fügt sich in dieselbe Struktur wie die [Secure-by-default-Referenzarchitektur für GKE](https://www.alekseialeinikov.com/de/blog/topics/architecture/secure-by-default-gke-referenzarchitektur-2026).

## Wohin die Daten gehen: ein Backend wählen

OpenTelemetry endet beim Exporter. Alles danach ist eine Backend-Entscheidung, und weil jedes ernsthafte Backend inzwischen OTLP annimmt, können Sie es sich später anders überlegen.

| Backend | Signale | Wie es OpenTelemetry empfängt | Gut zu wissen |
| --- | --- | --- | --- |
| **Prometheus** | Metriken | Nativer OTLP-Receiver unter `/api/v1/otlp/v1/metrics`, aktiviert mit `--web.enable-otlp-receiver`; oder per Scraping über den Collector | Nur HTTP; Out-of-order-Ingestion aktivieren (30 Minuten sind ein guter Start), wenn mehrere Collectoren schreiben |
| **Grafana Tempo / Loki / Mimir** | Traces / Logs / Metriken | OTLP | Loki speichert die meisten Attribute als Structured Metadata und erlaubt standardmäßig 15 Index-Labels – `k8s.pod.name` gehört nicht in die Labels |
| **Jaeger v2** | Traces | OTLP; Jaeger v2 baut selbst auf dem Collector-Framework auf | All-in-one mit In-Memory-Speicher nur für Entwicklung |
| **Google Cloud Observability** | Alle drei | Telemetry (OTLP) API unter `telemetry.googleapis.com`, gRPC oder HTTP | Verlangt `gcp.project_id` an der Resource; Metriken landen als Prometheus-Zeitreihen |
| **Datadog, New Relic, Dynatrace, Honeycomb** | Alle drei | OTLP-Endpunkte oder Collector-Exporter | Ihre Analyse bleibt, der proprietäre Agent fällt weg |

Speziell auf Google Cloud nimmt die Telemetry API alle drei Signale über einen Endpunkt an. Der Schreibende braucht die Rolle `roles/telemetry.writer` plus Service Usage Consumer im Quota-Projekt, und die Authentifizierung im Collector läuft über die Extension `googleclientauth` – idealerweise mit Workload Identity statt einer Schlüsseldatei, aus den Gründen, die in [Service-Account-Schlüssel abschaffen mit Workload Identity Federation](https://www.alekseialeinikov.com/de/blog/topics/security/service-account-schluessel-abschaffen-workload-identity-federation-2026) stehen. Das Standardkontingent für Metriken liegt bei 60.000 Anfragen pro Minute und Region mit bis zu 200 Punkten pro Anfrage – rund 200.000 Samples pro Sekunde –, und die Metrik-Ingestion darüber braucht Collector 0.140.0 oder neuer.

## OpenTelemetry vs. Prometheus vs. Hersteller-Agents

Nach diesem Vergleich wird oft gesucht, und die ehrliche Antwort lautet: Die drei konkurrieren nicht auf derselben Ebene.

| | OpenTelemetry | Prometheus | Hersteller-Agent (Datadog, Dynatrace …) |
| --- | --- | --- | --- |
| Was es ist | Standard + SDKs + Collector | Metrik-Datenbank, Abfragesprache und Alerting | Proprietäre Erfassung für ein Backend |
| Signale | Traces, Metriken, Logs (Profile folgen) | Metriken | Meist alle, plus Extras des Herstellers |
| Modell | Meist Push über OTLP | Meist Pull (Scraping) | Push zum Hersteller |
| Speicherung und UI | Keine – Ihre Wahl | Ja | Die des Herstellers |
| Lock-in | Keiner | Gering | Hoch |
| Ersetzt | Hersteller-SDKs und -Agents | Nichts in OpenTelemetry; es ist ein Backend | — |

Die praktische Antwort auf *„OpenTelemetry oder Prometheus?"* lautet daher meist **beides**: mit OpenTelemetry instrumentieren und Metriken in Prometheus oder einem Managed-Prometheus-Dienst speichern. Und die Antwort auf *„OpenTelemetry oder Datadog?"* lautet: OpenTelemetry ersetzt den Datadog-*Agent*, nicht Datadog – Sie behalten das Produkt und gewinnen die Freiheit, es zu verlassen.

## Sampling: die Traces behalten, die zählen

Im großen Maßstab können Sie sich nicht leisten, jeden Trace zu speichern, und Sie müssen es auch nicht. Die OpenTelemetry-Dokumentation empfiehlt, Sampling ab etwa **1.000 Traces pro Sekunde** in Betracht zu ziehen, und weist darauf hin, dass bei hohem Volumen eine Stichprobe von **1 % oder weniger** den Rest zuverlässig abbilden kann.

- **Head Sampling** entscheidet, wenn der Root Span beginnt. Die übliche Wahl ist `parentbased_traceidratio`: Der Root-Service behält einen festen Anteil der Traces, ausgewählt nach Trace-ID, und jeder nachgelagerte Service folgt der Entscheidung des Parents – so entstehen nie halbe Traces. Das ist günstig und zustandslos, weiß aber nicht, ob der Trace mit einem Fehler endet.
- **Tail Sampling** entscheidet nach Abschluss des Traces und kann daher jeden Fehler und jede langsame Anfrage behalten. Es braucht die oben beschriebene zustandsbehaftete Gateway-Schicht und ist schwerer zu dimensionieren und zu betreiben.

Viele Teams kombinieren beides: ein großzügiges Head Sampling, um die Pipeline vor Überlast zu schützen, und danach Tail Sampling im Gateway, um auszuwählen, welche Traces das Geld wert sind. Eine Regel macht beide Wege sicher – **berechnen Sie Metriken, bevor Sie Traces sampeln**, wie es der `spanmetrics`-Connector in der Gateway-Konfiguration tut, damit Dashboards und SLOs auf 100 % der Anfragen beruhen.

## Fehler in der Produktion, die OpenTelemetry teuer machen

Fast jedes Kostenproblem mit OpenTelemetry geht auf eine Handvoll Fehler zurück.

**Metrik-Kardinalität.** Jede eindeutige Kombination von Attributwerten ist eine eigene Zeitreihe. Ein Latenz-Histogramm mit `http.route` und `http.response.status_code` ist in Ordnung; fügen Sie `user.id` oder `order.id` hinzu, entsteht eine Zeitreihe pro Kunde. Halten Sie Kennungen auf Spans und Logs – dort sind sie nützlich – und aus Metriken heraus. Entfernen Sie problematische Attribute im Collector mit dem Prozessor `attributes` oder `transform`.

**Jeden gesunden Trace behalten.** Ein erfolgreicher `GET /health`, der zu 100 % getraced wird, ist reine Kosten. Filtern Sie Health Checks ganz heraus und sampeln Sie den Rest.

**Personenbezogene Daten in Attributen.** Instrumentierung kann URLs mit Query-Strings, Header und SQL-Statements erfassen. Bereinigen oder hashen Sie alles Personenbezogene im Agent, bevor es den Node verlässt – die günstigste DSGVO-Maßnahme, die Sie je einführen werden.

**Logs, die in jeden Eintrag kopiert werden.** Manche Backends kopieren Resource- und Scope-Attribute in jeden gespeicherten Logeintrag, sodass eine große Resource das Logvolumen vervielfacht. Die Dokumentation von Google Cloud weist ausdrücklich darauf hin; kürzen Sie Resource-Attribute vor dem Export.

**Ein Collector ohne Grenzen.** Ohne `memory_limiter` kann ein Vorfall, der den Verkehr verdoppelt, den Collector genau dann zum Absturz bringen, wenn Sie ihn brauchen. Beobachten Sie seine eigenen Metriken – abgelehnte, verworfene und wartende Einträge – und dimensionieren Sie das Gateway für Verkehr wie im Vorfall, nicht für einen durchschnittlichen Tag.

**Abgerissener Kontext an asynchronen Grenzen.** Traces, die an einer Queue enden, bedeuten meist einen Producer, der keinen Kontext mitgibt. Für Batch-Consumer, die viele Nachrichten auf einmal verarbeiten, verbinden Sie einen Consumer-Span über **Span Links** mit vielen Producer-Spans, statt einen einzigen Parent zu erzwingen.

> [!KEY]
> Telemetrie ist eine Datenpipeline wie jede andere. Geben Sie ihr ein Budget, begrenzen Sie die Rate, bereinigen Sie sie und überwachen Sie sie – sonst wird sie unbemerkt zum teuersten und am wenigsten vertrauenswürdigen System, das Sie betreiben.

## Checkliste für die Produktion

**Instrumentierung:**

- `service.name`, `service.version` und `deployment.environment.name` an jedem Service gesetzt
- Zero-Code-Instrumentierung aktiv, manuelle Spans um Geschäftsvorgänge
- Semantic Conventions für Standardattribute; eigener Namensraum für den Rest
- Kontext über HTTP, gRPC **und** Message Queues weitergegeben
- Versionen von SDK und Instrumentierung gepinnt

**Collector:**

- Agent als DaemonSet; Gateway nur dort, wo zentrale Regeln oder Tail Sampling nötig sind
- `memory_limiter` zuerst, Batching zuletzt, `k8sattributes` vor dem Batching
- PII im Agent bereinigt; Credentials nur im Gateway oder über Workload Identity
- Konfigurationen in der CI mit `otelcol validate` geprüft; eigener schlanker Build in der Produktion
- Eigene Metriken und Health Check des Collectors überwacht und alarmiert

**Daten und Kosten:**

- Keine Nutzer- oder Anfrage-IDs in Metrik-Attributen
- Health Checks gefiltert; Head und/oder Tail Sampling eingerichtet
- Span-Metriken vor dem Sampling berechnet
- Ein Writer pro Metrik-Stream
- Backend pro Signal gewählt; überall OTLP, damit es später wechseln kann

## Fazit

OpenTelemetry ist kein Produkt, das Sie kaufen, und kein Dashboard, das Sie installieren. Es ist die **Verrohrung**: eine gemeinsame Art, wie jeder Service beschreibt, was er tut, ein gemeinsames Protokoll, um diese Beschreibung zu transportieren, und ein Collector, der entscheidet, was bleibt, was bereinigt wird und wohin es geht.

Der Gewinn ist Hebelwirkung. Einmal instrumentiert, können Sie das Backend wechseln, während einer Migration an zwei gleichzeitig senden oder Tail Sampling für das ganze Unternehmen einführen, indem Sie eine Gateway-Konfiguration ändern – ohne den Anwendungscode anzufassen. Machen Sie die langweiligen Teile richtig – Namen, Kardinalität, Sampling und einen Collector, der nicht umfallen kann –, und die Frage um drei Uhr nachts ändert sich von *„Wo fangen wir überhaupt an?"* zu *„Klick auf den langsamen Span."*

> Observability ist die Datenseite der Arbeit eines Plattformteams – [Platform Engineering auf Kubernetes](https://www.alekseialeinikov.com/de/blog/topics/devops/platform-engineering-auf-kubernetes-2026) beschreibt den Rest des Paved Road.
