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.
Dieses Thema als Schulung für Ihr Team
Dieser Beitrag erklärt das Thema. Damit Ihr Team es danach auch anwendet, gibt es Observability mit OpenTelemetry, Prometheus und Grafana als Schulung — an Ihrem eigenen Code, mit den Fragen, die ein Text nicht beantwortet. Sie wählen die Module, wir bauen daraus ein Programm.
3 Tage·ab 900 EUR netto pro Tag (bis 3 Teilnehmende) ·Termin nach Vereinbarung
Als Team-Schulung anfragenZum Seminar Observability mit OpenTelemetry, Prometheus und Grafana →