Better Stack Kafka monitoring

Monitor Apache Kafka with Better Stack collector. Broker discovery and partition health out of the box, full broker internals with the Prometheus JMX exporter.

Kafka dashboard

Start collecting Kafka metrics

Install Better Stack collector on the hosts running Kafka. The collector automatically discovers your brokers and starts collecting cluster metadata. No Kafka configuration needed:

  • Broker count
  • Partitions per topic
  • In-sync replicas (ISR) per partition
  • Leader status per partition
  • Under-replication status

These metrics power the Overview and Partitions & replication sections of the Kafka dashboard.

Basic Kafka metrics in Kubernetes

Brokers are discovered and reached automatically through the cluster network. No extra setup needed.

Basic Kafka metrics in Docker Compose

The collector connects to brokers from the host network. Publish the broker port and advertise a listener host clients can follow:

docker-compose.yml
services:
  kafka:
    image: apache/kafka:4.2.1
    ports:
      - "9092:9092"
    environment:
      KAFKA_LISTENERS: PLAINTEXT_HOST://0.0.0.0:9092,PLAINTEXT://0.0.0.0:19092,CONTROLLER://0.0.0.0:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT_HOST://localhost:9092,PLAINTEXT://kafka:19092

Containers on the compose network keep connecting to kafka:19092.

  1. Go to Sources β†’ your collector β†’ Configure β†’ Collect metrics.
  2. Click Collect metrics and select Kafka.
  3. Set Service name to match your Kafka service, like kafka.
  4. Set Endpoint to a broker address reachable from the host, like localhost:9092.

Use the broker address, not a metrics endpoint

The Kafka target is a connection to the Kafka protocol port, typically 9092. Don't point it at the JMX exporter port. The JMX exporter is connected separately as a Prometheus scrape target below.

Get full Kafka metrics with JMX exporter

Kafka keeps broker-level performance metrics in JMX (Java Management Extensions) and doesn't expose them by default.

Deploy the Prometheus JMX exporter as a Java agent on each broker to light up the rest of the Kafka dashboard:

  • Bytes and messages in/out, per topic
  • Active controller, broker count, offline partitions
  • Leader count, partition count, ISR changes
  • Request rates per request type
  • Log size per topic and partition

The setup is the same everywhere: put the agent JAR and a rules file on each broker, add the -javaagent flag to Kafka's JVM options, and let the collector scrape port 7071.

Create the rules file

Save the following configuration next to where the agent JAR will live:

jmx-kafka-config.yaml
lowercaseOutputName: true
lowercaseOutputLabelNames: true
rules:
# Per-topic broker throughput (BytesInPerSec, MessagesInPerSec, ...)
- pattern: kafka.server<type=(.+), name=(.+), topic=(.+)><>Count
  name: kafka_server_$1_$2_total
  type: COUNTER
  labels:
    topic: "$3"
# Per-client, per-partition gauges
- pattern: kafka.server<type=(.+), name=(.+), clientId=(.+), topic=(.+), partition=(.*)><>Value
  name: kafka_server_$1_$2
  type: GAUGE
  labels:
    clientId: "$3"
    topic: "$4"
    partition: "$5"
- pattern: kafka.server<type=(.+), name=(.+), clientId=(.+), brokerHost=(.+), brokerPort=(.+)><>Value
  name: kafka_server_$1_$2
  type: GAUGE
  labels:
    clientId: "$3"
    broker: "$4:$5"
# Log size per topic-partition
- pattern: kafka.log<type=(.+), name=(.+), topic=(.+), partition=(.*)><>Value
  name: kafka_log_$1_$2
  type: GAUGE
  labels:
    topic: "$3"
    partition: "$4"
# Network request counters
- pattern: kafka.network<type=(.+), name=(.+), request=(.+)><>Count
  name: kafka_network_$1_$2_total
  type: COUNTER
  labels:
    request: "$3"
# Catch-alls with a single extra mbean property, kept as a label
- pattern: kafka.server<type=(.+), name=(.+), (.+)=(.+)><>Value
  name: kafka_server_$1_$2
  type: GAUGE
  labels:
    $3: "$4"
- pattern: kafka.server<type=(.+), name=(.+), (.+)=(.+)><>Count
  name: kafka_server_$1_$2_total
  type: COUNTER
  labels:
    $3: "$4"
# Generic gauges and counters
- pattern: kafka.server<type=(.+), name=(.+)><>Value
  name: kafka_server_$1_$2
  type: GAUGE
- pattern: kafka.server<type=(.+), name=(.+)><>Count
  name: kafka_server_$1_$2_total
  type: COUNTER
- pattern: kafka.controller<type=(.+), name=(.+)><>Value
  name: kafka_controller_$1_$2
  type: GAUGE
- pattern: kafka.network<type=(.+), name=(.+)><>Value
  name: kafka_network_$1_$2
  type: GAUGE
- pattern: kafka.log<type=(.+), name=(.+)><>Value
  name: kafka_log_$1_$2
  type: GAUGE

Rules are applied first-match-wins. Keep the specific patterns above the generic catch-alls, otherwise per-topic metric names get mangled.

JMX exporter in Kubernetes

Fetch the agent in an init container and attach it to the broker container. Mount the rules file to /jmx-exporter/jmx-kafka-config.yaml, for example from a ConfigMap:

Kafka pod template
initContainers:
  - name: jmx-exporter
    image: curlimages/curl:8.14.1
    command: ["curl", "-sSL", "-o", "/jmx-exporter/jmx_prometheus_javaagent.jar",
              "https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/1.0.1/jmx_prometheus_javaagent-1.0.1.jar"]
    volumeMounts:
      - name: jmx-exporter
        mountPath: /jmx-exporter
containers:
  - name: kafka
    env:
      - name: KAFKA_OPTS
        value: "-javaagent:/jmx-exporter/jmx_prometheus_javaagent.jar=7071:/jmx-exporter/jmx-kafka-config.yaml"
    ports:
      - containerPort: 7071
    volumeMounts:
      - name: jmx-exporter
        mountPath: /jmx-exporter

Annotate the Kafka pod template and the collector discovers and scrapes the endpoint automatically:

Kafka pod template
annotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "7071"
  prometheus.io/path: "/metrics"

JMX exporter in Docker Compose

Download the agent JAR next to the rules file:

Download JMX exporter agent
curl -sSL -o ./jmx-exporter/jmx_prometheus_javaagent.jar \
  https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/1.0.1/jmx_prometheus_javaagent-1.0.1.jar

Mount the folder, attach the agent, and publish the metrics port:

docker-compose.yml
services:
  kafka:
    image: apache/kafka:4.2.1
    ports:
      - "7071:7071" # JMX exporter Prometheus endpoint
    environment:
      KAFKA_OPTS: >-
        -javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent.jar=7071:/opt/jmx-exporter/jmx-kafka-config.yaml
    volumes:
      - ./jmx-exporter:/opt/jmx-exporter:ro

Restart Kafka and verify the endpoint:

Verify the metrics endpoint
curl -s http://localhost:7071/metrics | grep kafka_server

Then add the endpoint as a scrape target in Better Stack:

  1. Go to Sources β†’ your collector β†’ Configure β†’ Collect metrics.
  2. Click Collect metrics and select Custom Prometheus exporter or service.
  3. Set Service name to match your Kafka service, like kafka. The shared name groups both metric sources under one service.
  4. Set Endpoint to http://localhost:7071/metrics.

The collector scrapes the endpoint from the host network every 30 seconds. That's why port 7071 is published.

Beware of Kafka CLI tools inheriting KAFKA_OPTS

Kafka CLI tools, including Docker healthchecks and Kubernetes probes calling scripts like kafka-broker-api-versions.sh, inherit KAFKA_OPTS and crash trying to attach a second agent to the busy port.

Clear the variable for CLI invocations:

Healthcheck without the agent
KAFKA_OPTS="" /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092

Verify the configuration

Within a few minutes, metrics appear in your collector source, and the scrape target shows as Active in your collector's Configure β†’ Collect metrics.

Check out the Kafka dashboard. You should see your broker throughput, controller state, partition health, and storage in one place.

Need help?

Please let us know at hello@betterstack.com. We're happy to help! πŸ™