Installer Kafka en mode KRaft : VM Ubuntu et Docker Compose — chapitre 1
Installez et lancez Kafka en local en mode KRaft, sans ZooKeeper, puis vérifiez que le broker répond avec vos premières commandes.
Contexte
Pour installer Kafka aujourd’hui, on utilise le mode KRaft, sur une VM Ubuntu ou avec Docker Compose. Depuis Kafka 4.0, Kafka gère seul ses métadonnées grâce à KRaft : ZooKeeper a été retiré. Ce chapitre installe Kafka 4.4 sur une VM Ubuntu 24.04 (Java 21) sous forme d’un vrai cluster de 3 nœuds, qui servira à tous les labs de la formation, avec une variante Docker Compose.
Lexique Essentiel
KRaft
Mode natif de Kafka (sans ZooKeeper), seul mode supporté depuis Kafka 4.0.
ZooKeeper
Service de coordination historique, supprimé de Kafka depuis la version 4.0.
Broker
Serveur Kafka stockant les messages.
Cluster ID
ID unique d’un cluster KRaft.
Schéma Illustratif du Mode KRaft
Comment lire le schéma
- Le cadre du haut est le quorum KRaft : 3 contrôleurs qui se répliquent un journal de métadonnées (
__cluster_metadata) avec le protocole Raft. Un seul est actif (violet) ; s’il tombe, les 2 autres en élisent un nouveau en quelques secondes. - Le cadre du bas contient les brokers : ils stockent les messages. Ils ne décident rien seuls, ils appliquent les métadonnées (topics, leaders, ISR) reçues du contrôleur actif.
- Les clients ne parlent qu’aux brokers, jamais aux contrôleurs.
- Avec 3 contrôleurs, le cluster survit à la perte d’1 contrôleur (majorité = 2 sur 3).
Démarrage express : un seul nœud KRaft (test en 2 minutes)
# 1. Installer Java 21
sudo apt update && sudo apt install -y openjdk-21-jre-headless
# 2. Télécharger Kafka 4.4.0 (lien expiré ? https://archive.apache.org/dist/kafka/4.4.0/)
curl -LO https://downloads.apache.org/kafka/4.4.0/kafka_2.13-4.4.0.tgz
tar -xzf kafka_2.13-4.4.0.tgz
cd kafka_2.13-4.4.0
# 3. Générer un ID de cluster
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
# 4. Formater le stockage (contrôleur unique : --standalone)
bin/kafka-storage.sh format -t "$KAFKA_CLUSTER_ID" --standalone -c config/server.properties
# 5. Lancer Kafka au premier plan (Ctrl+C pour l'arrêter)
bin/kafka-server-start.sh config/server.properties
Depuis Kafka 4.0, le fichier config/server.properties est directement une configuration KRaft (le dossier config/kraft/ n’existe plus). Ce nœud unique utilise les ports 9092/9093, comme le nœud 1 du lab : arrêtez-le avant de monter le cluster ci-dessous.
Commandes pratiques (mode ZooKeeper) — obsolète
Ce mode a été supprimé dans Kafka 4. Voir l’encadré « Kafka 4 : ZooKeeper a été supprimé » et le lab VM Ubuntu plus bas.
Sous le capot : ce que fait vraiment KRaft
- Le journal des métadonnées : chaque création de topic, changement de leader ou modification de config devient un événement écrit dans
__cluster_metadata. Les brokers lisent ce journal comme un consommateur lit un topic. - L’élection Raft : le contrôleur actif envoie des battements de cœur. Sans nouvelles pendant
controller.quorum.election.timeout.ms(1 s par défaut), un follower se présente et devient actif s’il obtient la majorité des votes. - Pourquoi c’est mieux que ZooKeeper : un seul système à exploiter, des métadonnées chargées en mémoire, un redémarrage et un basculement beaucoup plus rapides, et des clusters capables de gérer des millions de partitions.
- En production : séparez les rôles (
process.roles=controllersur 3 petites machines,process.roles=brokersur les autres). Le mode combiné est réservé au développement et aux labs.
Astuce d’expert : préparer un broker de production
- Limite de fichiers ouverts : chaque segment et chaque connexion consomme un descripteur. Montez
LimitNOFILE=100000dans l’unité systemd (la valeur par défaut, 1024, fait tomber un broker chargé). - Tas JVM modeste (4 à 6 Go suffisent) : le reste de la RAM sert au page cache Linux, qui fait la vitesse de Kafka. Évitez le swap (
vm.swappiness=1). - Données sur un disque dédié (XFS ou ext4, option
noatime), jamais dans/tmp: leconfig/server.propertiesd’exemple écrit dans/tmp/kraft-combined-logs, effacé au redémarrage. - Contrôleurs dédiés (3 ou 5) dès que le cluster dépasse quelques brokers : un contrôleur surchargé par du trafic client ralentit toutes les élections.
Kafka 4 : ZooKeeper a été supprimé
Depuis Kafka 4.0, le mode ZooKeeper n’existe plus : seul KRaft est supporté. ZooKeeper ne sert plus qu’à comprendre un ancien cluster 3.x à migrer : la migration vers KRaft se fait en 3.x (3.9 conseillée) avant de passer en 4.x. Pour tout nouveau projet, utilisez exclusivement le lab ci-dessous.
Lab VM Ubuntu : un vrai cluster de 3 nœuds
Objectif : monter sur une seule VM un cluster Kafka 4.4 de 3 nœuds, répliqué, qui servira à tous les labs de la formation. Durée : 30 minutes.
1. Préparer la VM
sudo apt update && sudo apt install -y openjdk-21-jre-headless curl jq
java -version # doit afficher 21.x (Java 17 minimum)
# Utilisateur et dossiers dédiés
sudo useradd -r -m -d /opt/kafka -s /bin/bash kafka
sudo mkdir -p /var/lib/kafka && sudo chown kafka:kafka /var/lib/kafka
# Kafka 4.4.0
cd /tmp
curl -LO https://downloads.apache.org/kafka/4.4.0/kafka_2.13-4.4.0.tgz
sudo tar -xzf kafka_2.13-4.4.0.tgz -C /opt/kafka --strip-components=1
sudo chown -R kafka:kafka /opt/kafka
echo 'export PATH=$PATH:/opt/kafka/bin' | sudo tee /etc/profile.d/kafka.sh
source /etc/profile.d/kafka.sh
2. Générer la configuration des 3 nœuds
sudo -iu kafka
cd /opt/kafka
for i in 1 2 3; do
BROKER=$((9090 + i*2)); CTRL=$((BROKER + 1))
cat > config/node$i.properties <<EOF
process.roles=broker,controller
node.id=$i
controller.quorum.voters=1@localhost:9093,2@localhost:9095,3@localhost:9097
listeners=PLAINTEXT://localhost:$BROKER,CONTROLLER://localhost:$CTRL
advertised.listeners=PLAINTEXT://localhost:$BROKER
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
log.dirs=/var/lib/kafka/node$i
num.partitions=6
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
EOF
done
3. Formater le stockage et démarrer
CLUSTER_ID=$(kafka-storage.sh random-uuid)
for i in 1 2 3; do
kafka-storage.sh format -t $CLUSTER_ID -c config/node$i.properties
done
export KAFKA_HEAP_OPTS="-Xms1g -Xmx1g"
for i in 1 2 3; do
kafka-server-start.sh -daemon config/node$i.properties
done
sleep 10
# Vérifier le quorum
kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status
Vous devez voir LeaderId (le contrôleur actif) et CurrentVoters avec les 3 nœuds.
3 bis. Le script kafka-lab.sh : piloter le lab en une commande
Plutôt que de retaper ces boucles à chaque séance, installez une fois pour toutes ce script. Il démarre et arrête les 3 nœuds, attend que le quorum KRaft ait élu un leader, et remet le lab à zéro. Il est idempotent : lancer start deux fois ne crée pas de doublon, start après une panne ne relance que les nœuds arrêtés. Tous les labs de la formation commencent par kafka-lab.sh status.
À coller une seule fois, avec votre utilisateur habituel (tapez exit pour quitter la session kafka) :
sudo tee /usr/local/bin/kafka-lab.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
# kafka-lab.sh : pilote le cluster de lab Kafka 4.4 (3 nœuds KRaft combinés)
# Usage : kafka-lab.sh {start|stop|restart|status|reset [-y]|topics}
set -euo pipefail
KAFKA_HOME="${KAFKA_HOME:-/opt/kafka}"
DATA_ROOT="${DATA_ROOT:-/var/lib/kafka}"
KAFKA_USER="${KAFKA_USER:-kafka}"
BOOTSTRAP="localhost:9092,localhost:9094,localhost:9096"
WAIT_SECONDS="${WAIT_SECONDS:-90}"
KAFKA_HEAP_OPTS="${KAFKA_HEAP_OPTS:--Xms1g -Xmx1g}"
NODES=(1 2 3)
# Lance une commande sous l'utilisateur kafka (sans sudo si on l'est déjà)
as_kafka() {
if [[ "$(id -un)" == "$KAFKA_USER" ]]; then
env KAFKA_HEAP_OPTS="$KAFKA_HEAP_OPTS" "$@"
else
sudo -u "$KAFKA_USER" env KAFKA_HEAP_OPTS="$KAFKA_HEAP_OPTS" "$@"
fi
}
node_pid() { pgrep -f "java.*node$1\.properties" | head -n1 || true; }
quorum_status() {
as_kafka timeout 20 "$KAFKA_HOME/bin/kafka-metadata-quorum.sh" \
--bootstrap-server "$BOOTSTRAP" describe --status 2>/dev/null
}
quorum_ready() {
local out
out="$(quorum_status)" || return 1
grep -Eq '^LeaderId:[[:space:]]+[0-9]+' <<<"$out"
}
wait_quorum() {
local deadline=$((SECONDS + WAIT_SECONDS))
echo -n "Attente du quorum KRaft"
while ((SECONDS < deadline)); do
if quorum_ready; then
echo " : prêt (${SECONDS}s)."
return 0
fi
echo -n "."
sleep 2
done
echo
echo "ERREUR : quorum non prêt après ${WAIT_SECONDS}s. Voir $KAFKA_HOME/logs/server.log" >&2
return 1
}
start() {
local i
for i in "${NODES[@]}"; do
if [[ -n "$(node_pid "$i")" ]]; then
echo "node$i : déjà démarré"
continue
fi
if [[ ! -f "$DATA_ROOT/node$i/meta.properties" ]]; then
echo "ERREUR : $DATA_ROOT/node$i n'est pas formaté. Lancez : kafka-lab.sh reset" >&2
exit 1
fi
as_kafka "$KAFKA_HOME/bin/kafka-server-start.sh" -daemon "$KAFKA_HOME/config/node$i.properties"
echo "node$i : démarrage lancé"
done
wait_quorum
}
stop() {
local i deadline
for i in "${NODES[@]}"; do
if [[ -n "$(node_pid "$i")" ]]; then
as_kafka pkill -TERM -f "java.*node$i\.properties" || true
fi
done
deadline=$((SECONDS + 60))
for i in "${NODES[@]}"; do
while [[ -n "$(node_pid "$i")" ]] && ((SECONDS < deadline)); do sleep 1; done
if [[ -n "$(node_pid "$i")" ]]; then
echo "node$i : ne s'arrête pas proprement, kill -9"
as_kafka pkill -KILL -f "java.*node$i\.properties" || true
fi
done
echo "Cluster arrêté."
}
status() {
local i pid rc=0
for i in "${NODES[@]}"; do
pid="$(node_pid "$i")"
if [[ -n "$pid" ]]; then
printf 'node%s UP pid=%-8s broker=localhost:%s\n' "$i" "$pid" "$((9090 + i * 2))"
else
printf 'node%s DOWN\n' "$i"
rc=1
fi
done
local out
if out="$(quorum_status)" && grep -Eq '^LeaderId:[[:space:]]+[0-9]+' <<<"$out"; then
grep -E '^(ClusterId|LeaderId|LeaderEpoch|HighWatermark|CurrentVoters):' <<<"$out" || true
else
echo "Quorum KRaft : NON DISPONIBLE"
rc=1
fi
return "$rc"
}
reset() {
local i cid answer=""
if [[ "${1:-}" != "-y" ]]; then
read -r -p "Effacer TOUTES les données du lab ($DATA_ROOT/node1..3) ? [o/N] " answer
[[ "$answer" =~ ^[oOyY]$ ]] || { echo "Annulé."; exit 0; }
fi
stop
for i in "${NODES[@]}"; do
as_kafka rm -rf "$DATA_ROOT/node$i"
done
cid="$(as_kafka "$KAFKA_HOME/bin/kafka-storage.sh" random-uuid)"
for i in "${NODES[@]}"; do
as_kafka "$KAFKA_HOME/bin/kafka-storage.sh" format -t "$cid" -c "$KAFKA_HOME/config/node$i.properties"
done
echo "Stockage reformaté (cluster.id=$cid)."
start
}
topics() {
as_kafka "$KAFKA_HOME/bin/kafka-topics.sh" --bootstrap-server "$BOOTSTRAP" --list --exclude-internal
echo "--- Partitions sous-répliquées (vide = tout va bien) :"
as_kafka "$KAFKA_HOME/bin/kafka-topics.sh" --bootstrap-server "$BOOTSTRAP" --describe --under-replicated-partitions
}
case "${1:-}" in
start) start ;;
stop) stop ;;
restart) stop; start ;;
status) status ;;
reset) reset "${2:-}" ;;
topics) topics ;;
*)
echo "Usage : $(basename "$0") {start|stop|restart|status|reset [-y]|topics}" >&2
exit 2
;;
esac
EOF
sudo chmod 755 /usr/local/bin/kafka-lab.sh
kafka-lab.sh status
| Commande | Effet |
|---|---|
kafka-lab.sh start | Démarre les nœuds arrêtés, puis attend (90 s max) que kafka-metadata-quorum.sh describe --status affiche un LeaderId |
kafka-lab.sh stop | Arrêt propre (SIGTERM) des 3 nœuds, kill -9 au bout de 60 s si un nœud reste bloqué |
kafka-lab.sh status | État de chaque nœud + ClusterId, LeaderId, LeaderEpoch, votants. Code retour 1 si quelque chose ne va pas |
kafka-lab.sh reset | Arrête, efface toutes les données, reformate avec un nouveau Cluster ID et redémarre (reset -y sans confirmation) |
kafka-lab.sh topics | Liste les topics (hors internes) et les partitions sous-répliquées |
kafka-lab.sh restart | stop puis start |
Astuce d’expert
- VM lente au démarrage ?
WAIT_SECONDS=180 kafka-lab.sh start. - Si
startéchoue, la cause est presque toujours dans/opt/kafka/logs/server.log(port déjà pris, Cluster ID différent entre nœuds, disque plein) :tail -50 /opt/kafka/logs/server.log. - Variante Docker : le script ne s’applique pas, utilisez
docker compose ps/start/stop.
4. Exercice de panne : tuer le contrôleur actif
kafka-lab.sh status # le cluster doit être UP (à lancer en tant qu'utilisateur kafka : sudo -iu kafka)
BOOT=localhost:9092,localhost:9094,localhost:9096 # le client essaie les 3 nœuds
LEADER=$(kafka-metadata-quorum.sh --bootstrap-server $BOOT describe --status | awk '/^LeaderId:/{print $2}')
echo "Contrôleur actif : $LEADER"
pkill -f "java.*node$LEADER\.properties"
sleep 5
kafka-metadata-quorum.sh --bootstrap-server $BOOT describe --status | grep -E '^(LeaderId|LeaderEpoch):'
kafka-topics.sh --bootstrap-server $BOOT --create --topic test-panne --partitions 3 --replication-factor 2
# Remettre le nœud : seul le nœud arrêté est relancé
kafka-lab.sh start
| Ce que vous observez | Pourquoi |
|---|---|
LeaderId change et LeaderEpoch augmente de 1 | Les 2 contrôleurs restants ont élu un nouveau leader (majorité 2/3) |
| La création de topic fonctionne pendant la panne | Le cluster reste disponible tant que la majorité du quorum est là |
| Le nœud redémarré rattrape le journal tout seul | Il relit __cluster_metadata depuis le leader |
À tester en plus
- Arrêtez 2 nœuds sur 3 : la création de topic échoue. Pourquoi ? (plus de majorité)
- Regardez la réplication du quorum :
kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --replication
Variante Docker Compose : le même cluster + Kafka UI
Si vous préférez Docker à une installation directe, ce fichier lance le même cluster (3 nœuds KRaft, ports 9092, 9094, 9096) et Kafka UI, une interface web pour voir topics, messages, groupes et retards. Tous les labs de la formation fonctionnent à l’identique avec les deux installations. Ne lancez pas les deux en même temps : les ports seraient en conflit.
Comment lire le schéma
- Le grand cadre est le réseau Docker créé par Compose : les conteneurs s’y appellent par leur nom (
kafka-1,kafka-2…). - Chaque broker a deux adresses :
kafka-1:19092pour les autres conteneurs (Kafka UI, réplication) etlocalhost:9092pour vos commandes sur la VM. C’est le réglageadvertised.listeners, la source n°1 des problèmes de connexion avec Docker. - Kafka UI (violet) n’est exposé que sur
127.0.0.1: on y accède depuis son poste par un tunnel SSH, jamais directement sur Internet.
1. Installer Docker et lancer le cluster
sudo apt install -y docker.io docker-compose-v2
sudo usermod -aG docker $USER # docker sans sudo : déconnectez-vous puis reconnectez-vous
mkdir -p ~/kafka-lab && cd ~/kafka-lab
# coller le compose.yaml ci-dessous, puis :
docker compose up -d
docker compose ps # 4 services « running »
docker compose logs kafka-1 | grep -i "Kafka Server started"
# ~/kafka-lab/compose.yaml
x-kafka-env: &kafka-env
CLUSTER_ID: "4L6g3nShT-eMCtK--X86sw" # même ID pour les 3 nœuds
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
KAFKA_NUM_PARTITIONS: 6
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
services:
kafka-1:
image: apache/kafka:4.4.0
container_name: kafka-1
ports: ["9092:9092"]
environment:
<<: *kafka-env
KAFKA_NODE_ID: 1
KAFKA_LISTENERS: INTERNAL://:19092,CONTROLLER://:9093,EXTERNAL://:9092
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka-1:19092,EXTERNAL://localhost:9092
kafka-2:
image: apache/kafka:4.4.0
container_name: kafka-2
ports: ["9094:9094"]
environment:
<<: *kafka-env
KAFKA_NODE_ID: 2
KAFKA_LISTENERS: INTERNAL://:19092,CONTROLLER://:9093,EXTERNAL://:9094
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka-2:19092,EXTERNAL://localhost:9094
kafka-3:
image: apache/kafka:4.4.0
container_name: kafka-3
ports: ["9096:9096"]
environment:
<<: *kafka-env
KAFKA_NODE_ID: 3
KAFKA_LISTENERS: INTERNAL://:19092,CONTROLLER://:9093,EXTERNAL://:9096
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka-3:19092,EXTERNAL://localhost:9096
kafka-ui:
image: ghcr.io/kafbat/kafka-ui:latest
container_name: kafka-ui
ports: ["127.0.0.1:8080:8080"] # jamais exposé publiquement : pas d'authentification par défaut
environment:
KAFKA_CLUSTERS_0_NAME: lab
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka-1:19092,kafka-2:19092,kafka-3:19092
DYNAMIC_CONFIG_ENABLED: "true"
depends_on: [kafka-1, kafka-2, kafka-3]
2. Ouvrir Kafka UI depuis votre poste
# sur VOTRE poste (pas la VM) : tunnel SSH vers le port 8080 de la VM
ssh -L 8080:localhost:8080 utilisateur@IP_DE_LA_VM
# puis ouvrir http://localhost:8080 dans le navigateur
| Dans Kafka UI | Ce que vous y lisez | Leçon liée |
|---|---|---|
| Brokers | Les 3 nœuds, le contrôleur actif, l’espace disque | Chapitre 1 |
| Topics → un topic → Partitions | Leader, répliques, ISR de chaque partition | Chapitres 2 et 8 |
| Topics → Messages | Contenu, clé, partition, offset, en-têtes (utile pour lire une DLT) | Chapitres 3 et 6 |
| Consumers | Membres d’un groupe, lag par partition, réinitialisation d’offsets | Chapitres 4 et 6 |
| Topics → Settings | retention.ms, cleanup.policy, segment.ms | Chapitre 5 |
3. Commandes Kafka avec Docker
# Les outils sont dans l'image, sous /opt/kafka/bin
docker exec -it kafka-1 /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:19092 \
--create --topic orders --partitions 12 --replication-factor 3
docker exec -it kafka-1 /opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server kafka-1:19092 describe --status
# Pannes : les équivalents Docker des pkill des labs
docker stop kafka-2 # arrêt propre → docker start kafka-2
docker kill kafka-2 # arrêt brutal (kill -9)
docker network disconnect kafka-lab_default kafka-3 # coupure réseau, le processus vit toujours
docker network connect kafka-lab_default kafka-3
Erreurs fréquentes avec Docker
- Le client se connecte puis échoue sur
kafka-1:19092: il a reçu l’adresse interne. Depuis la VM, utilisezlocalhost:9092; depuis un conteneur,kafka-1:19092. C’est le rôle des deux listeners. docker compose downefface les données : les messages vivent dans les conteneurs.docker compose stop/startles conserve. Pour un vrai stockage persistant, montez un volume surKAFKA_LOG_DIRS.- Kafka UI exposé sur 0.0.0.0 : n’importe qui pourrait lire et supprimer vos topics. Gardez
127.0.0.1:8080et le tunnel SSH.
Résumé
Vous avez installé un cluster Kafka 4.4 de 3 nœuds en mode KRaft (sans ZooKeeper, supprimé depuis Kafka 4.0), sur VM ou avec Docker Compose, et vous le pilotez avec kafka-lab.sh. Ces bases sont indispensables avant la création de topics et la manipulation des messages.KRaft est recommandé pour simplifier l’architecture et éviter la dépendance à Zookeeper. Ces bases sont indispensables avant la création de topics et la manipulation des messages.
Quiz
- Quelle est la différence principale entre KRaft et Zookeeper ?
- Quel fichier de configuration est utilisé pour KRaft ?
Exercice
Lancez Kafka en mode KRaft sur votre machine → Arrêtez-le, nettoyez les logs, puis relancez-le avec un nouveau Cluster ID.
Suivez votre progression. Créez un compte gratuit pour cocher vos leçons et reprendre là où vous en étiez.
Créer un compte gratuit