Im vorherigen Artikel habe ich über Kafka-Broker-Metriken geschrieben und darüber, warum ihre Erfassung schwieriger ist, als sie sein sollte.
Die Kurzfassung lautete: Kafka hat nicht ein einziges Metriksystem.
Es hat die neuere Kafka Metrics SPI, konfiguriert über metric.reporters, und die ältere Yammer/Coda-Hale-Registry, konfiguriert über kafka.metrics.reporters. Einige wichtige Broker-Metriken liegen im einen System, andere im anderen, und die meisten realen Monitoring-Setups laufen nach wie vor über JMX, reguläre Ausdrücke, Prometheus-Mappings und Dashboards, die zu den exportierten Namen passen können oder auch nicht.
Das war der Grund für die Entwicklung von monedula-metrics-reporter: ein Kafka-Plugin, das Metriken direkt über OTLP an einen OpenTelemetry Collector exportiert und beide Kafka-Metrik-Registries mit einer einzigen Konfiguration abdeckt.
Doch ein Teil fehlte noch immer.
Broker-Metriken sagen uns viel über den Cluster, aber sie erklären nicht vollständig, was innerhalb der Client-Anwendungen geschieht.
Ein Broker kann uns sagen, dass Produce-Requests langsam sind. Er kann uns sagen, dass Fetch-Requests langsam sind. Er kann uns Auskunft über Request-Queues, Network-Processors, unterreplizierte Partitionen, fehlgeschlagene Requests und vieles mehr geben.
Aber manchmal ist die eigentliche Frage nicht nur:
Was passiert auf dem Broker?
Sondern auch:
Was passiert innerhalb des Producers oder Consumers?
Batcht der Producer effizient? Wird er gedrosselt? Pollt der Consumer regelmäßig? Verbringt er Zeit in Rebalances? Verhält sich eine Anwendung anders als eine andere?
Traditionell bedeutete die Beantwortung dieser Fragen, jede Client-Anwendung einzeln zu instrumentieren. In Java hieß das oft wieder JMX. In anderen Sprachen hing es von der Client-Bibliothek, vom Framework und vom jeweiligen Observability-Setup des Teams ab.
Und in größeren Organisationen wird daraus ein sehr vertrautes Problem: Das Kafka-Team betreibt den Cluster, aber die Client-Anwendungen gehören vielen unterschiedlichen Teams. Wenn also etwas schiefläuft, hat der Cluster-Betreiber oft nicht die Client-seitigen Metriken, die er braucht, und um sie zu bekommen, sind Koordination, Codeänderungen, Redeployments oder zumindest Neustarts erforderlich.
Genau dieses Problem versucht KIP-714 zu lösen.
Die Idee hinter KIP-714
KIP-714 fügt eine Broker-gesteuerte Möglichkeit hinzu, Metriken von Kafka-Clients zu erfassen.
Anstatt dass jedes Anwendungsteam seinen eigenen Metrik-Exporter konfiguriert, kann der Kafka-Cluster Client-Metrik-Subscriptions definieren. Eine Subscription legt fest, welche Client-Metriken erfasst werden sollen und wie oft die Clients sie übermitteln sollen.
Die Clients übermitteln ihre Metriken dann über das Kafka-Protokoll an die Broker. Die Payload selbst ist OTLP, sodass das Broker-seitige Plugin sie an einen OpenTelemetry Collector, eine Zeitreihendatenbank oder ein beliebiges anderes Observability-Backend weiterleiten kann.
Der entscheidende Punkt ist die Richtung der Steuerung.
Der Cluster-Betreiber entscheidet, was erfasst wird.
Clients, die KIP-714 unterstützen, können die Subscription empfangen und die angeforderten Metriken übermitteln. Die Anwendung muss kein JMX bereitstellen. Sie braucht keinen Sidecar-Exporter. Sie braucht keinen eigenen Prometheus-Endpunkt.
Es gibt dennoch zwei Voraussetzungen:
- Auf dem Broker muss ein Client-Telemetry-Plugin konfiguriert sein.
- Es muss mindestens eine Client-Metrik-Subscription existieren.
Ohne eine Subscription können Clients das Protokoll zwar unterstützen, sie werden aber keine Metriken übermitteln.
Das ist ein gutes Design. Client-Metriken werden nicht versehentlich erfasst. Der Betreiber muss die Erfassung auf der Cluster-Seite ausdrücklich aktivieren.
Ein wichtiges Detail: Nicht jeder Client sendet dieselben Metriken
KIP-714 schafft einen gemeinsamen Mechanismus zur Erfassung von Client-Metriken, aber es sorgt nicht auf magische Weise dafür, dass jeder Kafka-Client denselben Metriksatz bereitstellt.
Welche Metriken man erhält, hängt von der Client-Bibliothek ab.
Der Java-Client sendet derzeit den umfangreichsten Metriksatz. Das ist nicht verwunderlich: Der Java-Client ist die Referenzimplementierung, er verfügt über eine sehr ausgereifte interne Metrik-Registry, und viele Kafka-Metriken wurden zuerst dort entworfen.
Andere Clients stellen möglicherweise einen kleineren Satz bereit. librdkafka-basierte Clients, etwa die von Python-, Go-, .NET- und Node-Wrappern verwendeten Clients, können ebenfalls an KIP-714 teilnehmen, aber die verfügbaren Metriken sind nicht zwangsläufig identisch mit denen, die der Java-Client sendet.
Das mentale Modell sollte also lauten:
KIP-714 standardisiert, wie Client-Metriken angefordert und transportiert werden. Der tatsächliche Metriksatz hängt weiterhin von der Client-Implementierung ab.
Das ist beim Bau von Dashboards von Bedeutung.
Einige Panels können auf gemeinsamen Metriken basieren und sollten über verschiedene Client-Bibliotheken hinweg funktionieren. Andere Panels sind nur dann aussagekräftig, wenn der Client tatsächlich den umfangreicheren Metriksatz sendet. In der Praxis liefert der Java-Client heute die vollständigste Observability-Geschichte.
Warum das zu monedula-metrics-reporter hinzufügen?
Ursprünglich ging es bei monedula-metrics-reporter um Broker-Metriken.
Es verarbeitete die Kafka-SPI-Registry. Es verarbeitete die Yammer-Registry. Es exportierte beide über OTLP. Es fügte die Broker-Identität als Resource-Attribute hinzu. Es wurde mit Grafana-Dashboards ausgeliefert, die zu den emittierten Metriken passen.
Doch sobald KIP-714 existiert, ist der nächste Schritt ganz naheliegend:
Wenn das Plugin ohnehin schon innerhalb des Brokers läuft und ohnehin OTLP an den Collector exportiert, sollte es auch die Client-übermittelten Metriken verarbeiten.
Andernfalls bliebe das Kafka-Monitoring weiterhin fragmentiert.
Man hätte einen Pfad für Broker-SPI-Metriken, einen weiteren für Yammer-Metriken und noch ein weiteres Plugin oder eine weitere Komponente für KIP-714-Client-Telemetrie.
Das würde funktionieren, aber es würde die gesamte Idee, den Kafka-Metrik-Stack zu vereinfachen, zunichtemachen.
Die neue Version von monedula-metrics-reporter verarbeitet nun also drei Metrikquellen:
- Kafka Metrics SPI
- Yammer / Coda Hale Broker-Metriken
- KIP-714-Client-übermittelte Telemetrie
Das Ziel ist einfach: ein Plugin, eine Collector-Pipeline, ein Satz von Dashboards.
Client-Metriken werden über denselben OpenTelemetry-Pfad wie die Broker-Metriken weitergeleitet. Sie können dieselben Broker-Labels tragen, etwa kafka_cluster_id und kafka_node_id, und sie können zusätzlich mit der client_id angereichert werden, sodass Metriken verschiedener Anwendungen nicht zu einem einzigen anonymen Strom verschmelzen.
Dieser letzte Punkt ist sehr wichtig.
Wenn zwei Producer denselben Metriknamen übermitteln, müssen wir wissen, welcher Producer welcher ist. Standardmäßig fügt der Reporter die Client-ID als Label hinzu. Sensiblere oder Labels mit hoher Kardinalität, wie Client-Instance-ID, Principal oder Client-Adresse, sind optional zuschaltbar (opt-in).
Wie es funktioniert
Der Broker-seitige Teil ist über Kafkas ClientTelemetry-Interface implementiert.
Wenn ein KIP-714-fähiger Client Telemetrie an einen Broker übermittelt, empfängt das Plugin die OTLP-Payload. Der Request-Handler-Thread leistet dabei nur minimale Arbeit: Er kopiert die Payload in eine beschränkte In-Memory-Queue. Anschließend liest ein Daemon-Thread aus dieser Queue, wandelt die Payload in OpenTelemetry-SDK-Metrikdaten um, reichert sie mit Labels an und exportiert sie über einen dedizierten Metrik-Exporter.
Das ist dasselbe Designprinzip wie beim Rest des Reporters:
Kafka darf niemals von der Observability-Pipeline blockiert werden.
Wenn der Collector nicht verfügbar, langsam oder fehlkonfiguriert ist, sollte Kafka den Datenverkehr weiter bedienen. Der Telemetrie-Batch kann verworfen werden. Die Kafka-Latenz sollte nicht vom Monitoring-Backend abhängen.
Der Client-Telemetry-Receiver hat ebenfalls seine eigenen Selbstüberwachungs-Metriken:
monedula_reporter_clienttelemetry_received_total
monedula_reporter_clienttelemetry_forwarded_total
monedula_reporter_clienttelemetry_dropped_total
monedula_reporter_clienttelemetry_unsupported_metrics_dropped_total
monedula_reporter_clienttelemetry_queue_depth
Wenn also die Client-Telemetrie nicht mehr fließt, gibt es etwas, das man sich ansehen kann, bevor man ins Raten gerät.
Installation
Das Artefakt wird in Maven Central veröffentlicht, sodass kein manueller Build erforderlich ist.
Für Version 0.10.0 lädst du das JAR aus Maven Central herunter:
curl -L \
-o monedula-metrics-reporter-0.10.0.jar \
https://repo1.maven.org/maven2/dev/monedula/monedula-metrics-reporter/0.10.0/monedula-metrics-reporter-0.10.0.jar
Dann kopierst du es in Kafkas Classpath:
cp monedula-metrics-reporter-0.10.0.jar $KAFKA_HOME/libs/
Danach konfigurierst du den Broker:
metric.reporters=dev.monedula.metricsreporter.OtlpMetricReporter
otlp.metric.reporter.endpoint=http://otel-collector:4317
otlp.metric.reporter.transport=grpc
Damit wird der Reporter für Kafka-SPI-Metriken, Yammer-Broker-Metriken und Client-Telemetrie aktiviert.
Client-Telemetrie ist auf Brokern standardmäßig aktiviert:
otlp.metric.reporter.client.telemetry.enabled=true
Du kannst sie explizit deaktivieren:
otlp.metric.reporter.client.telemetry.enabled=false
Wenn sie deaktiviert ist, wird der Broker die Telemetrie-Fähigkeit nicht an Clients ankündigen.
Collector-Konfiguration
Eine minimale OpenTelemetry-Collector-Konfiguration kann OTLP vom Reporter empfangen und Metriken für Prometheus bereitstellen:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
resource_to_telemetry_conversion:
enabled: true
service:
pipelines:
metrics:
receivers: [otlp]
exporters: [prometheus]
Die Einstellung resource_to_telemetry_conversion ist bei der Verwendung von Prometheus wichtig. Sie überführt OpenTelemetry-Resource-Attribute in Prometheus-Labels.
Ohne sie erscheinen Labels wie kafka_cluster_id, kafka_node_id und client_id möglicherweise nicht dort, wo du sie erwartest.
Prometheus kann den Collector dann scrapen:
scrape_configs:
- job_name: otel-collector
static_configs:
- targets: ["otel-collector:8889"]
Der wichtige Teil: Client-Metrik-Subscriptions
Das Installieren des Plugins reicht nicht aus.
Das ist der Teil, den man bei KIP-714 leicht übersieht.
Clients übermitteln Metriken nur dann, wenn auf dem Cluster eine Client-Metrik-Subscription konfiguriert ist.
Zum Beispiel:
kafka-client-metrics.sh \
--bootstrap-server broker:9092 \
--alter \
--name all-clients \
--metrics "*" \
--interval 5000
Damit wird eine Subscription namens all-clients erstellt oder aktualisiert, alle verfügbaren Client-Metriken angefordert und das Push-Intervall auf 5 Sekunden gesetzt.
Für den Produktivbetrieb ist es möglicherweise zu viel, alles zu erfassen. Du kannst dich auch nur auf ausgewählte Metrik-Präfixe abonnieren.
Zum Beispiel könntest du mit den Standard-Kafka-Client-Metriken beginnen:
kafka-client-metrics.sh \
--bootstrap-server broker:9092 \
--alter \
--name standard-client-metrics \
--metrics "org.apache.kafka." \
--interval 10000
Die genaue Subscription-Strategie hängt davon ab, was du beobachten möchtest und wie viel Metrikvolumen für dich vertretbar ist.
Wichtig ist, dass die Subscription auf der Broker-Seite lebt. Dadurch kann der Kafka-Betreiber entscheiden, was erfasst werden soll, ohne jedes Anwendungsteam bitten zu müssen, sein Monitoring-Setup zu ändern.
Was ist mit der Client-Konfiguration?
Bei Clients, die KIP-714 unterstützen, ist das Übermitteln von Metriken üblicherweise standardmäßig aktiviert.
Dennoch kannst du es explizit angeben:
enable.metrics.push=true
Bei Java-Clients ist dies Teil der Kafka-Client-Konfiguration.
Bei librdkafka-basierten Clients, etwa confluent-kafka-python, gilt dieselbe Idee. Zum Beispiel:
Producer({
"bootstrap.servers": "broker:9092",
"client.id": "orders-producer",
"enable.metrics.push": True,
})
Und für einen Consumer:
Consumer({
"bootstrap.servers": "broker:9092",
"group.id": "orders-consumer-group",
"client.id": "orders-consumer",
"enable.metrics.push": True,
})
Es lohnt sich, die client.id bewusst zu setzen.
Der Reporter fügt sie standardmäßig zu den weitergeleiteten Client-Metriken hinzu, sodass Dashboards die Metriken nach Anwendung trennen können. Ohne eine aussagekräftige Client-ID erhältst du zwar trotzdem Metriken, aber sie sind bei der Fehlersuche deutlich schwerer zu verwenden.
Anreicherungsoptionen
Standardmäßig werden weitergeleitete Client-Metriken mit der Broker-Identität und der Client-ID angereichert:
otlp.metric.reporter.client.telemetry.enrich.broker=true
otlp.metric.reporter.client.telemetry.enrich.client.id=true
Das bedeutet, dass Client-Metriken nach Cluster, Broker und Anwendung gruppiert werden können.
Es gibt außerdem optionale Anreicherungen:
otlp.metric.reporter.client.telemetry.enrich.client.instance.id=true
otlp.metric.reporter.client.telemetry.enrich.client.identity=true
Die Client-Instance-ID ist nützlich, wenn du einzelne laufende Client-Instanzen unterscheiden möchtest, sie hat jedoch eine hohe Kardinalität.
Die Client-Identität fügt Principal und Client-Adresse hinzu. Das kann in kontrollierten Umgebungen sehr nützlich sein, aber es kann auch sensible Informationen enthalten und ist daher standardmäßig deaktiviert.
Kurz gesagt: Beginne mit Broker-Labels und Client-ID. Aktiviere den Rest nur dann, wenn du ihn wirklich brauchst.
Quickstart
Das Repository enthält einen Quickstart-Stack mit Kafka, dem OpenTelemetry Collector, Prometheus und Grafana.
Starte den Quickstart:
cd quickstart
docker compose up -d
Der Demo-Service erstellt eine KIP-714-Client-Metrik-Subscription und startet Producer- und Consumer-Verkehr. Sobald der Stack läuft, sollten also sowohl die Broker-Dashboards als auch die Client-Dashboards beginnen, Daten zu empfangen.
Grafana ist erreichbar unter:
http://localhost:3000
Prometheus ist erreichbar unter:
http://localhost:9090
Der Quickstart erstellt die Subscription mit:
kafka-client-metrics.sh \
--bootstrap-server kafka1:9092 \
--alter \
--name quickstart-clients \
--metrics "*" \
--interval 5000
Das ist bewusst einfach gehalten: alles erfassen, alle 5 Sekunden übermitteln und die Dashboards schnell zum Leben erwecken.
Für reale Umgebungen würde ich üblicherweise vorsichtiger beginnen. Zuerst die Standard-Metriken erfassen, das Volumen validieren und bei Bedarf weitere Metriken hinzufügen.
Java-Clients im Vergleich zu anderen Clients
Eine interessante Sache an KIP-714 ist, dass es einen gemeinsamen Mechanismus schafft, aber nicht jede Client-Bibliothek dieselbe Metriktiefe bereitstellt.
Der Java-Client sendet den umfangreichsten Metriksatz. Er kann viele Metriken aus seiner internen Registry übermitteln, darunter detailliertere Producer- und Consumer-Informationen: Batching, Buffer-Nutzung, Request-Timing, Aufschlüsselungen auf Topic-Ebene, Consumer-Lag und -Lead sowie weitere Java-Client-spezifische Signale.
Andere Client-Bibliotheken senden möglicherweise einen kleineren Satz.
Das bedeutet nicht, dass KIP-714 nur für Java-Clients nützlich ist. Es ist nach wie vor sehr wertvoll, einen gemeinsamen, Broker-gesteuerten Erfassungspfad für alle Clients zu haben, die es unterstützen. Aber wenn du dir Dashboards ansiehst, solltest du daran denken, dass fehlende Panels nicht immer bedeuten, dass etwas kaputt ist.
Manchmal existiert die Metrik in dieser Client-Bibliothek schlicht nicht.
Aus diesem Grund sind die neuen Grafana-Dashboards in zwei Teile aufgeteilt:
- Standard-Panels
- Erweiterte Panels
Die Standard-Panels sollten für jeden KIP-714-konformen Client funktionieren, der die relevanten gemeinsamen Metriken bereitstellt.
Die erweiterten Panels sind hauptsächlich für den Java-Client nützlich, da der Java-Client derzeit den umfangreichsten Metriksatz bereitstellt.
Diese Aufteilung ist beabsichtigt. Ich möchte kein Dashboard, das nur deshalb kaputt aussieht, weil ein Nicht-Java-Client keine Java-spezifischen Metriken bereitstellt. Es ist besser, die Grenze sichtbar zu machen.
Neue Dashboards
Die neue Version fügt zwei Client-fokussierte Dashboards hinzu:
- Kafka Producers (client)
- Kafka Consumers (client)
Das Producer-Dashboard konzentriert sich auf die Client-seitige Sicht des Produzierens von Records: Request-Latenz, Throttling, Queueing, Batching, Fehler und Durchsatz.
Das Consumer-Dashboard konzentriert sich auf die Client-seitige Sicht des Konsumierens von Records: Fetch-Latenz, Commit-Rate, Poll-Verhalten, Rebalance-bezogene Signale, Zuordnungsänderungen und, wo verfügbar, Consumer-seitiges Lag/Lead.
Beide Dashboards enthalten Filter für Cluster und Client-ID. Wo der Metriksatz es unterstützt, enthalten sie zudem Drill-downs auf Topic- oder Group-Ebene.


Eines gefällt mir an diesem Setup besonders gut: Derselbe Grafana-Ordner kann nun beide Seiten der Geschichte zeigen.
Broker-Dashboards beantworten:
Ist der Cluster gesund?
Client-Dashboards beantworten:
Wie erleben die Anwendungen den Cluster?
Und weil beide aus derselben OTLP-Pipeline mit konsistenten Labels stammen, wird es viel einfacher, sie zu korrelieren.
Zusammenfassung
KIP-714 ist ein sehr wichtiger Schritt für die Kafka-Observability.
Es verwandelt Client-Metriken von etwas, das jedes Anwendungsteam eigenständig bereitstellen muss, in etwas, das der Kafka-Cluster zentral anfordern kann.
monedula-metrics-reporter unterstützt dieses Modell nun.
Es exportiert weiterhin Broker-Metriken aus beiden Kafka-Metrik-Registries. Aber jetzt empfängt es auch Client-übermittelte OTLP-Telemetrie von KIP-714-Clients und leitet sie an denselben OpenTelemetry Collector weiter.
Das Plugin deckt somit nun drei Quellen ab:
- Kafka-SPI-Metriken
- Yammer-Broker-Metriken
- KIP-714-Client-Telemetrie
Das Ergebnis ist eine einfachere Kafka-Monitoring-Architektur: ein Plugin auf den Brokern, eine OTLP-Pipeline und Dashboards, die sowohl das Broker-seitige als auch das Client-seitige Verhalten abdecken.
Der Code ist Open Source und auf GitHub verfügbar