In this article we will setup a simple kafka cluster with 2 nodes in it.
We will validate that cluster is working by producing and consuming messages. We will setup
kafka-ui to see the cluster details. We will also export cluster metrics and watch them in grafana.
We will use docker compose for this to make things simpler.
The docker image for the kafka will be bitnami/kafka/.
Let's start with this docker-compose.yml:
version: '3.8'
x-kafka-common: &kafka-common
image: 'bitnami/kafka:latest'
ports:
- "9092"
networks:
- kafka
healthcheck:
test: "bash -c 'printf \"\" > /dev/tcp/127.0.0.1/9092; exit $$?;'"
interval: 5s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
x-kafka-env-common: &kafka-env-common
ALLOW_PLAINTEXT_LISTENER: 'yes'
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka-0:9093,1@kafka-1:9093
KAFKA_KRAFT_CLUSTER_ID: abcdefghijklmnopqrstuv
KAFKA_CFG_PROCESS_ROLES: controller,broker
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
EXTRA_ARGS: "-Xms128m -Xmx256m"
services:
kafka-0:
<<: *kafka-common
environment:
<<: *kafka-env-common
KAFKA_CFG_NODE_ID: 0
volumes:
- kafka_0_data:/bitnami/kafka
kafka-1:
<<: *kafka-common
environment:
<<: *kafka-env-common
KAFKA_CFG_NODE_ID: 1
volumes:
- kafka_1_data:/bitnami/kafka
networks:
kafka:
volumes:
kafka_0_data:
driver: local
kafka_1_data:
driver: localHere we define 2 services: kafka-0 and kafka-1.
We don't need any encryption, so we use ALLOW_PLAINTEXT_LISTENER=yes.
Also we don't use zookeeper, instead we will use KRaft protocol.
We will add a healthcheck command, which will later help us to run dependent services. The reason why we do not
use curl is because the response will be empty, and it will make curl produce an error
curl: (52) Empty reply from server
As a result, node will be marked as unhealthy.
On each node we need to set its id with this variable: KAFKA_CFG_NODE_ID.
By setting KAFKA_CFG_CONTROLLER_QUORUM_VOTERS environment variable on both services, we make them to work in a cluster.
Also we will use random value for the cluster id with the variable KAFKA_KRAFT_CLUSTER_ID: just use any string.
We set also KAFKA_CFG_PROCESS_ROLES, KAFKA_CFG_CONTROLLER_LISTENER_NAMES and KAFKA_CFG_LISTENERS because
they do not have default values.
All other variables will use their default values, you can read more about them here: https://github.com/bitnami/containers/blob/main/bitnami/kafka/README.md#configuration.
Let's run our cluster:
docker compose upNow we need to create a new topic and put some messages into it. Kafka comes with a set of scripts for managing it, let's use them:
docker compose exec kafka-0 /opt/bitnami/kafka/bin/kafka-topics.sh --create \
--bootstrap-server kafka-0:9092,kafka-1:9092 --replication-factor 1 --partitions 1 --topic testWe tell the kafka-topics.sh that it should use our kafka instances as a bootstrap servers.
As a result you should see
Created topic test.
Let's list all topics:
$ docker compose exec kafka-0 /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server kafka-0:9092,kafka-1:9092
test
$ docker compose exec kafka-0 /opt/bitnami/kafka/bin/kafka-topics.sh --describe --topic test --bootstrap-server kafka-0:9092,kafka-1:9092
Topic: test TopicId: 7FEciEQRRjqJ2zntIONOcQ PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: test Partition: 0 Leader: 0 Replicas: 0 Isr: 0
Let's write some messages into this topic. Open new terminal (this will be producer terminal) and type:
docker compose exec kafka-0 /opt/bitnami/kafka/bin/kafka-console-producer.sh --bootstrap-server kafka-0:9092,kafka-1:9092 --producer.config /opt/bitnami/kafka/config/producer.properties --topic test
>message0
Open another terminal (consumer terminal) and read the message with a consumer:
docker compose exec kafka-0 /opt/bitnami/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka-0:9092,kafka-1:9092 --consumer.config /opt/bitnami/kafka/config/consumer.properties --topic test --from-beginningYou should see message0. Switch back to the producer terminal and type another message. You will see it immediately
in consumer terminal.
Press Control+C to close producer and consumer.
At this point we can start a new cluster, create topic, produce the messages and consume them.
Next we will add kafka-ui. This application will give us a nice UI to view our cluster.
Add the following to the docker-compose.yml:
kafka-ui:
container_name: kafka-ui
image: provectuslabs/kafka-ui:latest
volumes:
- ./kafka-ui/config.yml:/etc/kafkaui/dynamic_config.yaml
environment:
DYNAMIC_CONFIG_ENABLED: 'true'
depends_on:
- kafka-0
- kafka-1
networks:
- kafka
ports:
- '8080:8080'
healthcheck:
test: wget --no-verbose --tries=1 --spider localhost:8080 || exit 1
interval: 5s
timeout: 10s
retries: 3
start_period: 30sYou need to create a configuration file for the kafka ui:
mkdir -p kafka-ui
touch kafka-ui/config.ymlKafka ui can be configured with environment variables, but I prefer yaml, because variables will become very messy once you will have more then 1 cluster.
Put the following into kafka-ui/config.yml:
auth:
type: LOGIN_FORM
spring:
security:
user:
name: admin
password: admin
kafka:
clusters:
- bootstrapServers: kafka-0:9092,kafka-1:9092
name: kafkaStop current docker compose process (control+c) and restart it:
docker compose upOpen kafka ui in the browser http://localhost:8080. Use admin:admin as a credentials (see kafka-ui/config.yml).
You will see our new cluster.
Visit http://localhost:8080/ui/clusters/kafka/all-topics/test/messages?keySerde=String&valueSerde=String&limit=100 and make sure you see all messages, that we produced earlier.
Now we need to expose kafka metrics to the prometheus and be able to see them in grafana.
Kafka exporter will be used to actually export the metrics.
Let's add new services to the docker-compose.yml:
prometheus:
image: prom/prometheus
container_name: prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
ports:
- 9090:9090
volumes:
- ./prometheus:/etc/prometheus
- prom_data:/prometheus
networks:
- kafka
healthcheck:
test: wget --no-verbose --tries=1 --spider localhost:9090 || exit 1
interval: 5s
timeout: 10s
retries: 3
start_period: 5s
kafka-exporter:
image: docker.io/bitnami/kafka-exporter:latest
depends_on:
kafka-0:
condition: service_healthy
kafka-1:
condition: service_healthy
networks:
- kafka
command: --kafka.server=kafka-0:9092 --kafka.server=kafka-1:9092
healthcheck:
test: "bash -c 'printf \"\" > /dev/tcp/127.0.0.1/9308; exit $$?;'"
interval: 5s
timeout: 10s
retries: 3
start_period: 5s
grafana:
image: grafana/grafana
container_name: grafana
ports:
- 3000:3000
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=grafana
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/var/lib/grafana/dashboards
networks:
- kafka
healthcheck:
test: curl --fail localhost:3000
interval: 5s
timeout: 10s
retries: 3
start_period: 10sand this in volumes section:
prom_data:
driver: localKafka exporter depends on kafka instances, it will produce an error during start, if there are no running kafka brokers. This is why we added the healthcheck for kafka.
There is also one more way to collect metrics (they can be used together): jmx exporter. We will use it as a java agent.
Download the jar and the config:
mkdir -p jmx-exporter
curl https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.19.0/jmx_prometheus_javaagent-0.19.0.jar \
-o jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar
curl https://raw.githubusercontent.com/prometheus/jmx_exporter/main/example_configs/kafka-2_0_0.yml \
-o jmx-exporter/kafka-2_0_0.ymlMount it to kafka images:
kafka-0:
<<: *kafka-common
environment:
<<: *kafka-env-common
KAFKA_CFG_NODE_ID: 0
volumes:
- kafka_0_data:/bitnami/kafka
- ./jmx-exporter:/opt/jmx-exporter
kafka-1:
<<: *kafka-common
environment:
<<: *kafka-env-common
KAFKA_CFG_NODE_ID: 1
volumes:
- kafka_1_data:/bitnami/kafka
- ./jmx-exporter:/opt/jmx-exporter
Update the x-kafka-env-common block in docker-compose.yml like this:
x-kafka-env-common: &kafka-env-common
ALLOW_PLAINTEXT_LISTENER: 'yes'
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka-0:9093,1@kafka-1:9093
KAFKA_KRAFT_CLUSTER_ID: abcdefghijklmnopqrstuv
KAFKA_CFG_PROCESS_ROLES: controller,broker
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
EXTRA_ARGS: "-Xms128m -Xmx256m -javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar=9404:/opt/jmx-exporter/kafka-2_0_0.yml"The exporter will be available at port 9404:
docker compose exec kafka-0 curl localhost:9404We also want Kafka UI to use the metrics. Add this to kafka-ui/config.yml:
kafka:
clusters:
- bootstrapServers: kafka-0:9092,kafka-1:9092
name: kafka
metrics:
type: JMX
port: 9404Create configuration file for a prometheus:
mkdir -p prometheus
touch prometheus/prometheus.ymlPut the following content:
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
scrape_configs:
- job_name: kafka-exporter
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- kafka-exporter:9308
- job_name: jmx-exporter
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- kafka-0:9404
- kafka-1:9404Here we ask prometheus to get metrics from kafka exporter from /metrics endpoint on port 9093 and from
jmx-exporter on port 9404.
Finally, create the configuration for grafana:
mkdir -p grafana/provisioning/datasources
mkdir -p grafana/provisioning/dashboards
touch grafana/provisioning/datasources/datasource.yml
touch grafana/provisioning/dashboards/dashboard.yml
curl https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/main/examples/metrics/grafana-dashboards/strimzi-kafka-exporter.json -o grafana/dashboards/strimzi-kafka-exporter.json
curl https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/main/examples/metrics/grafana-dashboards/strimzi-kafka.json -o grafana/dashboards/strimzi-kafka.jsonWe tell the grafana to provision prometheus datasource. Also we download the dashboard for the kafka exporter and jmx exporter and tell grafana to use it.
Put the following into grafana/provisioning/datasources/datasource.yml:
apiVersion: 1
deleteDatasources:
- name: Prometheus
orgId: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://prometheus:9090
password:
user:
database:
basicAuth: false
basicAuthUser:
basicAuthPassword:
withCredentials:
isDefault: true
version: 1
editable: falseand the following into grafana/provisioning/dashboards/dashboard.yml:
apiVersion: 1
providers:
- name: 'dashboards-from-file'
orgId: 1
folder: ''
folderUid: ''
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: trueNow restart docker compose and visit http://localhost:3000/dashboards. Use admin:grafana as credentials.
Open dashboard with name Strimzi Kafka Exporter, you will see some details about the cluster.
Now we can run grafana cluster, we can validate it by producing and consuming messages from CLI. Also we can view the details of the cluster with UI and watch the cluster metrics in grafana.
- APACHE KAFKA: https://kafka.apache.org/
- Bitnami kafka image: https://hub.docker.com/r/bitnami/kafka/
- Grafana: https://grafana.com/
- jmx_exporter: https://github.com/prometheus/jmx_exporter
- Kafka Exporter: https://github.com/danielqsj/kafka_exporter
- Kafka UI: https://github.com/provectus/kafka-ui
- Prometheus: https://prometheus.io/