Zum Inhalt springen

Tracing für KI-Anwendungen

Ein ManagedTracing ist ein OTLP-Endpunkt für die Aufrufe, die deine App an ein LLM macht: Richte den OpenTelemetry-Exporter deines Modell-Clients darauf, und jeder Prompt, jede Antwort, jede Token-Zahl und jede Latenz landet an einer Stelle, an der du sie siehst — OpenLIT (Apache-2.0) über ClickHouse, direkt gerendert, ohne eigenen Operator zu installieren.

Tracing ist eine Capability, standardmäßig aus, pro Shoot in der Konfiguration der paasbox-paas-Extension eingeschaltet — derselbe Block, den paasbox garden shoot create in dein Shoot-Manifest schreibt:

apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
spec:
extensions:
- type: paasbox-paas
providerConfig:
apiVersion: paas.paasbox.com/v1alpha1
kind: PaasConfig
capabilities: [apps, postgres, valkey, tracing]

Füge tracing der Liste hinzu und wende den Shoot erneut an. Das Einschalten selbst kostet nichts — kein Operator wird installiert, nur das Objekt und ein Reconciler, der ohnehin schon läuft. Die Kosten fangen erst mit deinem ersten ManagedTracing an.

apiVersion: paas.paasbox.com/v1alpha1
kind: ManagedTracing
metadata: { name: traces, namespace: shop }
spec:
plan: s
store: { mode: managed }
status:
conditions: [Reconciled, Ready, Degraded, StoreHealthy, SpansDropping]
phase: Ready
endpoint:
otlp: http://traces.shop.svc:4318
ui: http://traces.shop.svc:3000
secretName: traces-otlp

kubectl apply -f traces.yaml gibt dir eine Ready-Instanz in etwa einer Minute — gemessen 52 Sekunden auf einem bezahlten Hetzner-Shoot bei Plan s (2026-09-16). plan bemisst den OpenLIT-Pod; store.mode: managed rendert eine einzelne ClickHouse-Instanz daneben, im selben Namespace, die sonst niemand liest. retention (Standard 30d) ist die einzige Schranke dafür, wie viel ein ManagedTracing behält: ClickHouse hat kein Kontingent für gespeicherte Bytes pro Instanz.

# auf der App
uses: [{ name: traces, kind: ManagedTracing }]

injiziert fünf Umgebungsvariablen: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL (http/protobuf) und OTEL_EXPORTER_OTLP_HEADERS (ein x-api-key) als Referenzen auf traces-otlp, dazu zwei feste Werte: OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512 und, die entscheidende, OTEL_BSP_MAX_QUEUE_SIZE=32768. Jedes OpenTelemetry-SDK, das die Standard-Variablen OTEL_EXPORTER_OTLP_TRACES_* liest — dazu gehören OpenLITs eigenes und die meisten LLM-Tracing-Bibliotheken —, braucht sonst nichts. Ein prefix auf dieser Abhängigkeit wird akzeptiert, tut aber nichts: Die Namen sind fest.

Ein OTel-SDK sammelt Spans im Speicher, bevor es sie verschickt, in einer Queue fester Größe; ist sie voll, verwirft das SDK neue Spans selbst, bevor sie je das Netzwerk erreichen. Die meisten SDKs setzen diese Queue standardmäßig auf 2048. Gemessen auf einem bezahlten Shoot (2026-09-16): Ein Burst von 10.002 Spans gegen ein ManagedTracing mit der Standard-Queue des SDKs schickte nur 2.688 an den Store — 7.314 Spans, 73 %, wurden im eigenen Prozess der App verworfen, während der eigene Ablehnungszähler des Stores die ganze Zeit bei null stand. Auf der Empfangsseite sah nichts nach einem Problem aus; die Spans waren verschwunden, bevor sie den Pod verließen.

Deshalb setzt uses OTEL_BSP_MAX_QUEUE_SIZE=32768 automatisch für dich. Unter sauberer Last ohne Bursts verlor derselbe Aufbau nichts (1.002 geschickt, 1.002 gespeichert). Die Falle betrifft gezielt einen Burst gegen die SDK-Vorgabe — und sie ist still, weil status.drops.storeRefused die ganze Zeit über unauffällig bleibt.

status.drops trägt beide Zähler, denn nur einer davon ist echter Gegendruck:

  • storeRefused — Spans, die ClickHouse tatsächlich abgelehnt hat. Ein Wert über null hier heißt: der Store ist überlastet.
  • clientDropped — Spans, die das eigene SDK deiner App verworfen hat, bevor sie verschickt wurden, über denselben OTLP-Endpunkt zurückgelesen. Das ist die Zahl, in der sich die Queue-Größen-Falle zeigt.

Die Condition SpansDropping wird True, sobald einer der beiden Zähler steigt — du musst also nicht beide Zahlen von Hand abfragen, um es zu bemerken.

plan bemisst OpenLIT; ClickHouse (Modus managed) wird nicht nach Plan bemessen — es fordert immer 600Mi mit einem Limit von 2Gi an, weil dieser Spielraum für Abfragen gedacht ist, nicht für eine größere Instanz:

PlanOpenLIT-CPUOpenLIT-SpeicherClickHouse-Speicherboden
xs100m256Mi5Gi
s250m512Mi10Gi
m500m1Gi20Gi
l12Gi50Gi

Addiere ClickHouses feste 600Mi-Anforderung zum eigenen Speicher des Plans, um zu sehen, was eine Instanz reserviert: 1.112Mi bei Plan s, 856Mi bei xs — beide zweimal unabhängig gemessen, auf einem bezahlten Shoot (2026-09-16). Das ist mehr, als die elf eigenen Operatoren der Plattform zusammen reservieren. Das Portal soll diese Zahl vor dem Schalter zeigen; bis es das tut, ist diese Seite diese Zahl.

Beim Speicher gibt es dieselbe Art Überraschung. OpenLITs eigenes Datenvolume ist eine feste 2Gi-Anforderung, unabhängig vom Plan. Hetzners Blockspeicher stellt nie weniger als 10Gi bereit, also wird aus dieser 2Gi-Anforderung ein 10Gi-Volume — und bei Plan s, wo ClickHouses eigene 10Gi-Anforderung schon genau auf diesem Boden liegt, kostet eine ManagedTracing-Instanz 20Gi Blockspeicher, nicht die 12Gi, auf die ihre beiden Anforderungen zusammen kämen. Derselbe 10Gi-Boden pro Volume gilt bei jedem Plan; nur m und l verlangen von ClickHouse mehr als diesen Boden selbst.

ManagedTracing hat absichtlich kein replicas-Feld. OpenLITs eigener Identitätszustand — Organisationen, Projekte, API-Keys, die Konfiguration, die Mandanten auseinanderhält — liegt in einer einzigen SQLite-Datei auf einem Read-Write-Once-Volume. Ein zweiter Pod scheitert entweder beim Scheduling oder beschädigt diese Datei. Eine Instanz ist dafür gedacht, ein ganzes Team über ihre eigenen Dashboards zu bedienen, nicht dafür, wie eine zustandslose App skaliert zu werden.

Es gibt noch keine öffentliche Route zur OpenLIT-Oberfläche — status.endpoint.ui ist absichtlich eine clusterinterne Adresse. Erreiche sie von deinem Rechner aus mit:

Terminal-Fenster
kubectl -n shop port-forward svc/traces 3000:3000