Der erste von zwei Spickzetteln zum Seminar Observability mit OpenTelemetry, Prometheus und Grafana: alles zwischen Anwendung und Backend. Was danach kommt — PromQL, Dashboards, Alarme, SLOs — steht auf Prometheus und Grafana.
Maßgeblich ist die Spezifikation auf opentelemetry.io. Dieses Blatt trifft eine Auswahl: die Entscheidungen, die man beim Instrumentieren tatsächlich trifft, und die Fallen, die still bleiben statt Fehler zu werfen.
Welches Signal beantwortet welche Frage
| Signal | Stark bei | Schwach bei |
|---|---|---|
| Metrik | Trend, Schwellwert, Alarm | Einzelfall und Ursache |
| Trace | Ursachensuche über Dienstgrenzen | Aussage über die Gesamtheit |
| Log | Detail zu einem Ereignis | Aggregation und Vergleich |
Die Schwäche jedes Signals ist die Stärke eines anderen — deshalb lohnt die Korrelation. Als Faustregel: Fragen mit „welche” und „wie viele” führen auf Metriken, Fragen mit „warum” auf Traces, Fragen nach dem Wortlaut auf Logs.
| Fehlerbild | Was hilft |
|---|---|
| bekannt und erwartet | Monitoring mit Schwellwert |
| bekannt, Ursache offen | Trace über alle Dienste |
| unbekannt | freie Abfrage über Attribute |
Die dritte Zeile ist der Grund für Observability — sie lässt sich nicht vorab als Alarm formulieren.
Module: Von Monitoring zu Observability · Die Observability-Signale
OTLP: Ports, Protokolle, Pfade
| Transport | Port | Merkmale |
|---|---|---|
| OTLP/gRPC | 4317 | unärer Austausch, Statuscodes des gRPC-Protokolls |
| OTLP/HTTP binär | 4318 | Protobuf, Content-Type application/x-protobuf |
| OTLP/HTTP JSON | 4318 | Trace- und Span-IDs als Hex-Zeichenkette |
Die Pfade lauten /v1/traces, /v1/metrics und /v1/logs; Profiles liegen
unter /v1development/profiles. Häufigster Fehlgriff: gRPC-Port mit
HTTP-Protokoll kombiniert — der Export läuft dann still ins Leere. Ebenso
häufig: den Endpunkt mit Pfad angeben, obwohl das SDK ihn selbst anhängt.
| Betriebsform | Stärke | Grenze |
|---|---|---|
| Agent | Host-Attribute, kurzer Weg, kein Verlust im Netz | viele Instanzen zu pflegen |
| Sidecar | klare Zuordnung je Dienst | Ressourcen je Pod |
| Gateway | Sampling und Routing über alle Dienste | Ausfallpunkt ohne Redundanz |
Agent und Gateway schließen sich nicht aus — die Kette Agent → Gateway ist der Regelfall. Entscheidungen über alle Dienste hinweg, etwa Tail Sampling, brauchen zwingend ein Gateway.
Module: Referenzarchitektur · OpenTelemetry Collector
Resource-Attribute und Semantic Conventions
| Attribut | Stabilität | Beispiel |
|---|---|---|
service.name | stabil, vom SDK verlangt | pfandwerk-annahme |
service.version | stabil | 2.4.0 |
service.instance.id | stabil | annahme-7c4f |
deployment.environment.name | stabil | production |
telemetry.sdk.name | stabil, vom SDK gesetzt | opentelemetry |
Das ältere deployment.environment ist abgekündigt und durch
deployment.environment.name ersetzt. Ohne Codeänderung gesetzt:
OTEL_SERVICE_NAME=pfandwerk-annahme
OTEL_RESOURCE_ATTRIBUTES=\
service.version=2.4.0,\
service.instance.id=annahme-7c4f,\
deployment.environment.name=production
Eigene Attribute benennen:
| Fall | Schreibweise | Beispiel |
|---|---|---|
| firmenweit | Domain rückwärts | de.pfandwerk.filiale |
| nur eine Anwendung | Anwendungsname voran | pfandwerk.materialart |
| zu vermeiden | fremder Namensraum | http.filiale |
Bestehende OpenTelemetry-Namensräume nie als Präfix verwenden — künftige Versionen kollidieren sonst damit. Vor jedem eigenen Attribut prüfen, ob die Konventionen es bereits kennen; das ist der häufigste vermeidbare Fehler.
Span Kind steuert, wie Backends Dienstgrenzen erkennen — SERVER,
CLIENT, PRODUCER, CONSUMER, INTERNAL. Es ist keine Schmuckangabe:
Ist alles INTERNAL, sieht das Backend keine Dienstgrenze.
Modul: Datenmodell und Semantic Conventions
Java Agent: Konfiguration und Reichweite
java -javaagent:/opt/otel/opentelemetry-javaagent.jar \
-Dotel.service.name=pfandwerk-annahme \
-Dotel.exporter.otlp.protocol=grpc \
-Dotel.exporter.otlp.endpoint=http://collector:4317 \
-jar pfandwerk-annahme.jar
| Umgebungsvariable | Setzt | Übliche Wahl |
|---|---|---|
OTEL_SERVICE_NAME | service.name | Name des Dienstes |
OTEL_EXPORTER_OTLP_ENDPOINT | Ziel des Exports | Adresse des Collectors |
OTEL_EXPORTER_OTLP_PROTOCOL | Transportform | grpc oder http/protobuf |
OTEL_TRACES_EXPORTER | Trace-Export | otlp |
OTEL_METRICS_EXPORTER | Metrik-Export | otlp |
OTEL_LOGS_EXPORTER | Log-Export | otlp |
Jede Systemeigenschaft hat eine Entsprechung als Umgebungsvariable. In
Containern lässt sich der Agent auch über JAVA_TOOL_OPTIONS anhängen, ohne den
Startbefehl zu ändern. Eine einzelne Instrumentierung abschalten — besser als
pauschal filtern, weil die Daten gar nicht erst entstehen:
-Dotel.instrumentation.jdbc.enabled=false
Was der Agent von allein erfasst: HTTP-Server und -Client (Latenz und Status je Endpunkt, Latenz externer Abhängigkeiten), JDBC (langsame Abfragen), JVM (Heap, Threads, Garbage Collection).
| Automatisch | Manuell |
|---|---|
| technische Grenzen und Aufrufe | fachliche Vorgänge |
| ohne Codeänderung | im Anwendungscode |
| konventionsgerechte Namen | eigene Namensräume nötig |
| sofort verfügbar | Aufwand je Vorgang |
Das Eine ersetzt das Andere nicht: Der Agent sieht, dass ein Endpunkt langsam war, nicht welcher Schritt darin. Fachliche Fehler ohne technischen Fehlerstatus bleiben unsichtbar.
Modul: Automatische Java-Instrumentierung
Eigene Spans und Metriken
Span span = tracer.spanBuilder("leergut.pruefen")
.setSpanKind(SpanKind.INTERNAL).startSpan();
try (Scope scope = span.makeCurrent()) {
span.setAttribute("pfandwerk.materialart", art);
pruefe(behaelter);
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "abgelehnt");
} finally { span.end(); }
Das finally ist nicht optional — ein nicht beendeter Span erscheint in keinem
Backend. Attribute nach span.end() zu setzen wirkt nirgends.
LongCounter behaelter = meter
.counterBuilder("pfandwerk.behaelter.angenommen")
.setDescription("Angenommene Behaelter")
.setUnit("{behaelter}")
.build();
behaelter.add(1, Attributes.of(
stringKey("pfandwerk.materialart"), "glas"));
| Instrument | Erfasst | Beispiel |
|---|---|---|
| Counter | monoton steigende Summe | angenommene Behälter |
| Histogram | Verteilung von Messwerten | Dauer der Materialprüfung |
| beobachtet | Momentanwert bei Abfrage | Füllstand des Automaten |
Ein Wert, der auch fallen kann, gehört nie in einen Counter — sonst rechnet jede
Rate falsch. Einheiten ohne physikalisches Maß stehen in geschweiften Klammern,
Zeiten in s oder ms. Instrumente einmal beim Start bauen, nicht in der
Schleife.
Modul: Manuelle Instrumentierung
Context Propagation: der traceparent und seine Brüche
traceparent: 00-a0892f3577b34da6a3ce929d0e0e4736
-f03067aa0ba902b7-01
Aufbau: Version – Trace-ID – Span-ID – Flags, zusammen 55 Zeichen. Neben
traceparent steht tracestate für herstellerspezifische Zusatzangaben.
| Bruchstelle | Ursache | Behandlung |
|---|---|---|
| Thread-Wechsel | Context ist threadgebunden | Context ausdrücklich übergeben |
| Warteschlange | Anfrage endet vor Verarbeitung | Context in die Nachricht legen |
| Stapelverarbeitung | viele Ursachen, ein Lauf | Span Link je Ursache |
| Wiederholung | neuer Versuch, alter Vorgang | Link auf den ersten Versuch |
Span Links verbinden, ohne eine Eltern-Kind-Beziehung zu behaupten, die es nicht gibt. Ein Bruch erzeugt keinen Fehler, nur einen zweiten, unauffälligen Trace — deshalb gehört die Prüfung in die Abnahme, nicht in die Störungssuche.
Baggage transportiert Werte über Dienstgrenzen. Es ist kein bequemer Transportweg für Anwendungsdaten: Was im Baggage steht, landet potenziell im Log eines Fremdsystems. Und Baggage, das nie als Attribut übernommen wird, bleibt wirkungslos.
Modul: Context Propagation und Baggage
Collector: Abschnitte, Pipeline, Processor-Reihenfolge
| Abschnitt | Aufgabe | Beispiel |
|---|---|---|
receivers | Telemetrie entgegennehmen | otlp |
processors | verändern, begrenzen, anreichern | batch |
exporters | an ein Ziel weitergeben | otlphttp |
connectors | Pipelines verbinden | spanmetrics |
extensions | Zusatzfunktionen ohne Datenbezug | health_check |
service | aktivieren und verdrahten | pipelines |
Nur was in service steht, ist aktiv. Eine definierte, aber nicht
verdrahtete Komponente tut nichts — und meldet das auch nicht.
receivers:
otlp: { protocols: { grpc: {} } }
processors:
batch: { timeout: 10s }
exporters:
debug: {}
service:
pipelines:
traces: { receivers: [otlp],
processors: [batch], exporters: [debug] }
| Processor | Aufgabe | Beispiel |
|---|---|---|
resourcedetection | Host- und Plattformangaben | Knoten und Region ergänzen |
attributes | einzelne Attribute ändern | Filiale vereinheitlichen |
transform | Regeln mit Bedingung (OTTL) | Status je Ablehnungsgrund |
filter | verwerfen | Health-Aufrufe entfernen |
redaction | Werte unkenntlich machen | Kontonummer entfernen |
processors:
attributes/filiale:
actions:
- key: pfandwerk.filiale
value: unbekannt
action: insert
insert setzt nur, wenn das Attribut fehlt; update ändert nur Vorhandenes,
upsert tut beides. Eine OTTL-Anweisung mit Bedingung:
set(span.status.code, STATUS_CODE_OK)
where span.attributes["pfandwerk.ablehnung"]
== "material"
Die Reihenfolge der Processors ist die Reihenfolge der Wirkung: Memory-Limiter an den Anfang, sonst schützt er nichts · gefiltert wird vor dem Anreichern, sonst wird Verworfenes zuvor veredelt · Batch-Processor ans Ende, direkt vor den Export. Eine vertauschte Reihenfolge erzeugt keine Fehlermeldung, nur andere Daten.
Module: OpenTelemetry Collector · Telemetriedaten verarbeiten und kontrollieren
Sampling und Volumen
OTEL_TRACES_SAMPLER | Verhalten | Einsatz |
|---|---|---|
always_on | erfasst alles | Entwicklung |
always_off | erfasst nichts | gezielt abschalten |
traceidratio | Anteil nach Trace-ID | selten allein sinnvoll |
parentbased_traceidratio | folgt der Entscheidung des Aufrufers | Regelfall |
OTEL_TRACES_SAMPLER_ARG trägt bei den Ratio-Samplern die Wahrscheinlichkeit
zwischen 0 und 1. Die Entscheidung muss über alle Dienste hinweg dieselbe sein —
entscheidet jeder Dienst neu, entstehen Traces mit Löchern. Ein abweichend
konfigurierter Dienst genügt.
| Stellschraube | Wirkung | Risiko |
|---|---|---|
| Sampling | weniger Traces | seltene Fälle verschwinden |
| Filterung | weniger Rauschen | später fehlt der Beleg |
| Aggregation | Metrik statt Einzelfall | Ursachensuche wird schwerer |
| Aufbewahrung | weniger Speicher | kein Vorjahresvergleich |
Fehler- und Latenzausreißer gehören in jeder Variante vollständig erhalten — sie tragen den Diagnosewert. Tail Sampling entscheidet erst, wenn der Trace vollständig ist, und braucht deshalb ein Gateway: Landen die Spans eines Trace auf verschiedenen Instanzen, greift die Regel ins Leere.
Modul: Sampling, Volumen und Kosten
Typische Fallen
- Der Endpunkt passt nicht zum Protokoll. gRPC auf 4317, HTTP auf 4318 — die Verwechslung erzeugt keinen Fehler, nur fehlende Daten.
- Alle Dienste heißen gleich. Der Agent läuft, aber
OTEL_SERVICE_NAMEfehlt. - Der Span-Name trägt eine Kennung. Bon-Nummer im Namen ergibt Millionen verschiedener Namen; Kennungen gehören in Attribute.
- Alles ist
INTERNAL. Ohne Span Kind erkennt kein Backend eine Dienstgrenze. - Der Span wird bei einer Ausnahme nie beendet. Ohne
finallyerscheint er in keinem Backend. - Ein Fehler wird geloggt, der Span-Status bleibt
Unset. Umgekehrt: eine fachliche Ablehnung alsERRORtreibt jede Fehlerquote grundlos hoch. - JDBC-Spans tragen die Abfrage mit eingesetzten Werten — und damit Personenbezug ins Backend.
- Health-Endpunkte erzeugen die Mehrzahl aller Spans und verdecken den Rest.
- Der asynchrone Teil erzeugt eigene Traces, und niemand bemerkt es monatelang — es entsteht kein Fehler, nur ein zweiter Trace.
- Der Receiver steht in keiner Pipeline und empfängt nichts, obwohl er sauber konfiguriert ist.
- Ohne Memory-Limiter reißt der Collector unter Last die ganze Instanz mit.
- Ohne Batch-Processor erzeugt jeder einzelne Span einen Netzaufruf.
- Die Redaction sitzt erst im Gateway, und der Agent hat die Daten schon geloggt.
- Die Warteschlange liegt im Speicher, und ein Neustart verwirft alles.
- Sampling wirkt doppelt — in der Anwendung und im Collector.
- Metriken werden mitgesampelt und liefern danach falsche Absolutwerte.
- Das Volumen wird nie gerechnet, und die erste Rechnung entscheidet die Architektur.
Zum Seminar Observability mit OpenTelemetry, Prometheus und Grafana