Un único broker de Kafka es un punto único de fallo: si el servidor cae, los productores y consumidores se detienen y los datos no replicados pueden perderse. En este tutorial convertirás tres servidores Ubuntu 24.04 en un clúster Kafka 4 en modo KRaft, donde cada nodo actúa como broker y como controlador. Configurarás la replicación para tolerar la caída de un nodo sin perder mensajes confirmados, lo comprobarás apagando un broker y aprenderás las operaciones habituales: grupos de consumidores, reequilibrio de líderes y ampliación del clúster.
Requisitos previos
- Tres servidores con Ubuntu 24.04 LTS, por ejemplo tres VPS de CubePath, con al menos 4 GB de RAM cada uno.
- Los tres servidores conectados por una red privada. En los ejemplos se usan las IPs
10.0.0.11,10.0.0.12y10.0.0.13; sustitúyelas por las tuyas. - Kafka 4 instalado en cada nodo en
/opt/kafka, con Java 21, el usuariokafka, el directorio/var/lib/kafka/datay la unidad systemdkafka.service, tal como se explica en la guía "Cómo instalar Apache Kafka en Ubuntu 24.04 con KRaft" (pasos 1, 2 y 4). No formatees todavía el almacenamiento ni arranques el servicio. - Un usuario no root con privilegios
sudoen los tres nodos.
Si ya formateaste o arrancaste Kafka en modo de un nodo, detén el servicio y vacía el directorio de datos antes de empezar:
sudo systemctl stop kafka
sudo find /var/lib/kafka/data -mindepth 1 -delete
Cómo se organiza el clúster
| Concepto | Valor en este tutorial | Por qué |
|---|---|---|
| Nodos | 3, cada uno con process.roles=broker,controller | El quórum KRaft necesita mayoría: con 3 controladores tolera la caída de 1 |
| Factor de replicación | 3 | Cada partición tiene una copia en cada nodo |
min.insync.replicas | 2 | Con acks=all, un mensaje solo se confirma cuando lo tienen al menos 2 réplicas |
| Elección no limpia | Desactivada | Una réplica desactualizada nunca pasa a líder, así no se pierden mensajes confirmados |
Con esta combinación el clúster sigue aceptando escrituras con un nodo caído y ningún mensaje confirmado se pierde.
Paso 1: Resolver los nombres de los nodos
Asigna un nombre a cada nodo en /etc/hosts en los tres servidores. Así los mensajes de log y los comandos son más legibles:
sudo nano /etc/hosts
10.0.0.11 kafka-1
10.0.0.12 kafka-2
10.0.0.13 kafka-3
Comprueba la conectividad desde cada nodo:
ping -c 2 kafka-2
Paso 2: Abrir los puertos en la red privada
Kafka usa dos puertos: 9092 para clientes y replicación entre brokers, y 9093 para el quórum de controladores. Permítelos solo desde la red privada en los tres nodos:
sudo ufw allow from 10.0.0.0/24 to any port 9092 proto tcp
sudo ufw allow from 10.0.0.0/24 to any port 9093 proto tcp
sudo ufw status
Si tus aplicaciones están en otra red, añade una regla para el puerto 9092 desde sus IPs. El puerto 9093 no debe ser accesible para los clientes.
Paso 3: Configurar cada broker
Edita server.properties en cada nodo:
sudo nano /opt/kafka/config/server.properties
Sustituye el contenido por esta configuración. Es la del nodo kafka-1:
# Identidad y roles
process.roles=broker,controller
node.id=1
# Quórum estático de controladores: id@host:puerto de los tres nodos
[email protected]:9093,[email protected]:9093,[email protected]:9093
# Listeners
listeners=PLAINTEXT://10.0.0.11:9092,CONTROLLER://10.0.0.11:9093
advertised.listeners=PLAINTEXT://10.0.0.11:9092
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
# Almacenamiento
log.dirs=/var/lib/kafka/data
num.partitions=6
log.retention.hours=168
log.segment.bytes=1073741824
# Replicación y durabilidad
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
unclean.leader.election.enable=false
# Los topics se crean de forma explícita
auto.create.topics.enable=false
# Hilos
num.network.threads=3
num.io.threads=8
En kafka-2 y kafka-3 cambia solo tres valores: node.id (2 y 3) y la IP en listeners y advertised.listeners (10.0.0.12 y 10.0.0.13). controller.quorum.voters es idéntico en los tres.
Los ajustes clave:
controller.quorum.votersdefine un quórum estático de tres controladores. Cadanode.iddebe coincidir con el número antes de la@.offsets.topic.replication.factorytransaction.state.log.*protegen los topics internos donde Kafka guarda los offsets de los consumidores y el estado de las transacciones. Por defecto usan 3 réplicas, pero dejarlo explícito evita sorpresas.auto.create.topics.enable=falseevita que un error tipográfico en un productor cree un topic con configuración por defecto.
Paso 4: Formatear el almacenamiento con un ID de clúster común
Todos los nodos deben compartir el mismo ID de clúster. Genéralo en kafka-1:
/opt/kafka/bin/kafka-storage.sh random-uuid
MkU3OEVBNTcwNTJENDM2Qk
Copia el valor y formatea el almacenamiento en los tres nodos con ese mismo ID:
sudo -u kafka /opt/kafka/bin/kafka-storage.sh format -t MkU3OEVBNTcwNTJENDM2Qk -c /opt/kafka/config/server.properties
Formatting metadata directory /var/lib/kafka/data with metadata.version 4.1-IV1.
Como controller.quorum.voters ya lista los controladores, aquí no se usa la opción --standalone de la instalación de un nodo.
Paso 5: Arrancar el clúster
Arranca Kafka en los tres nodos, con pocos segundos de diferencia:
sudo systemctl enable --now kafka
El quórum necesita al menos dos controladores para elegir líder, así que el primer nodo esperará hasta que arranque el segundo. Cuando estén los tres, comprueba el estado del quórum desde cualquier nodo:
/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.11:9092 describe --status
ClusterId: MkU3OEVBNTcwNTJENDM2Qk
LeaderId: 2
LeaderEpoch: 1
HighWatermark: 318
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [{"id": 1, ...}, {"id": 2, ...}, {"id": 3, ...}]
CurrentObservers: []
Comprueba que los tres controladores están al día con la opción --replication:
/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.11:9092 describe --replication
NodeId DirectoryId LogEndOffset Lag LastFetchTimestamp LastCaughtUpTimestamp Status
2 ... 318 0 1758794400000 1758794400000 Leader
1 ... 318 0 1758794399800 1758794399800 Follower
3 ... 318 0 1758794399800 1758794399800 Follower
Paso 6: Crear un topic replicado
Crea un topic con 6 particiones y factor de replicación 3:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9092 \
--create --topic payments --partitions 6 --replication-factor 3
Consulta dónde está cada partición:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9092 --describe --topic payments
Topic: payments TopicId: ... PartitionCount: 6 ReplicationFactor: 3 Configs: ...
Topic: payments Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 ...
Topic: payments Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1 ...
Topic: payments Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2 ...
...
Leaderes el broker que atiende lecturas y escrituras de la partición.Replicasson los brokers que tienen copia. El primero es el líder preferido.Isr(in-sync replicas) son las réplicas al día. Si una réplica sale de esta lista, está retrasada o caída.
Envía mensajes con acks=all, la configuración que tus productores deben usar para datos que no pueden perderse:
/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server kafka-1:9092,kafka-2:9092,kafka-3:9092 \
--topic payments --producer-property acks=all
Escribe unos cuantos mensajes y sal con Ctrl+C. Indica siempre varios brokers en --bootstrap-server: el cliente solo los usa para descubrir el clúster, pero si el único que indicas está caído no podrá conectar.
Paso 7: Probar la tolerancia a fallos
En kafka-3, detén el broker:
sudo systemctl stop kafka
Desde kafka-1, comprueba el topic otra vez:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9092 --describe --topic payments
Topic: payments Partition: 2 Leader: 1 Replicas: 3,1,2 Isr: 1,2 ...
Las particiones que lideraba el broker 3 tienen ahora otro líder y el 3 ha desaparecido del ISR. Lista las particiones sin réplicas suficientes:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9092 --describe --under-replicated-partitions
Todas aparecerán, porque les falta una réplica, pero el ISR de cada una sigue teniendo 2 miembros, que es el mínimo configurado. Envía un mensaje con acks=all para confirmar que el clúster sigue aceptando escrituras:
echo "pago de prueba con un nodo caído" | /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server kafka-1:9092,kafka-2:9092 --topic payments --producer-property acks=all
El comando termina sin errores. Si apagaras un segundo nodo, cada partición quedaría con una sola réplica en el ISR y las escrituras con acks=all fallarían con NotEnoughReplicasException (además, el quórum de controladores perdería la mayoría). Es el comportamiento esperado: Kafka prefiere rechazar escrituras antes que confirmar datos que solo existen en un disco.
Vuelve a arrancar el broker en kafka-3:
sudo systemctl start kafka
Desde kafka-1, comprueba que no quedan particiones subreplicadas. Tras unos segundos, el comando no debe devolver nada:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9092 --describe --under-replicated-partitions
Tras la recuperación, el broker 3 ya no lidera ninguna partición. Kafka devuelve el liderazgo a los líderes preferidos automáticamente cada pocos minutos (auto.leader.rebalance.enable está activo por defecto), pero puedes forzarlo al momento:
/opt/kafka/bin/kafka-leader-election.sh --bootstrap-server kafka-1:9092 \
--election-type preferred --all-topic-partitions
Paso 8: Gestionar grupos de consumidores
Cada grupo de consumidores guarda su posición (offset) en cada partición. Lista los grupos y consulta el retraso de uno:
/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka-1:9092 --list
/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka-1:9092 --describe --group payments-service
La columna LAG indica los mensajes pendientes por partición. Para reprocesar mensajes, por ejemplo tras corregir un fallo en el consumidor, puedes mover los offsets del grupo. El grupo debe estar inactivo (todos sus consumidores detenidos). Primero previsualiza el cambio:
/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka-1:9092 \
--group payments-service --topic payments --reset-offsets --to-datetime 2026-09-25T00:00:00.000 --dry-run
Si el resultado es el esperado, aplícalo sustituyendo --dry-run por --execute. Otras opciones útiles son --to-earliest, --to-latest y --shift-by -100.
Paso 9: Añadir un broker y repartir particiones
Cuando el clúster se queda corto de disco o de red, añade brokers. Para un cuarto nodo (10.0.0.14) basta con que sea solo broker; el quórum de controladores sigue siendo de tres. Instala Kafka como en los requisitos, abre los puertos y usa esta configuración, cambiando solo lo que difiere de los demás nodos:
process.roles=broker
node.id=4
[email protected]:9093,[email protected]:9093,[email protected]:9093
listeners=PLAINTEXT://10.0.0.14:9092
advertised.listeners=PLAINTEXT://10.0.0.14:9092
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
Mantén también las secciones de almacenamiento y replicación del paso 3. Formatea con el mismo ID de clúster y arranca el servicio. Un broker nuevo no recibe particiones existentes por sí solo; hay que reasignarlas. Crea un archivo con los topics a mover:
nano ~/topics.json
{"version": 1, "topics": [{"topic": "payments"}]}
Genera una propuesta de reparto entre los cuatro brokers:
/opt/kafka/bin/kafka-reassign-partitions.sh --bootstrap-server kafka-1:9092 \
--topics-to-move-json-file ~/topics.json --broker-list "1,2,3,4" --generate
La salida muestra la asignación actual (guárdala para poder volver atrás) y la propuesta. Copia la propuesta en ~/reassign.json y ejecútala limitando el ancho de banda de la copia a 50 MB/s para no saturar la red:
/opt/kafka/bin/kafka-reassign-partitions.sh --bootstrap-server kafka-1:9092 \
--reassignment-json-file ~/reassign.json --execute --throttle 50000000
Comprueba el progreso hasta que todas las particiones estén completas. Ejecutar --verify al final también elimina el límite de ancho de banda:
/opt/kafka/bin/kafka-reassign-partitions.sh --bootstrap-server kafka-1:9092 \
--reassignment-json-file ~/reassign.json --verify
Status of partition reassignment:
Reassignment of partition payments-0 is completed.
...
Clearing broker-level throttles on brokers 1,2,3,4
Clearing topic-level throttles on topic payments
Reinicios y mantenimiento
Para actualizar o reiniciar nodos sin cortar el servicio, hazlo de uno en uno:
- Comprueba que no hay particiones subreplicadas con
--under-replicated-partitions. - Reinicia el nodo con
sudo systemctl restart kafka. - Espera a que el comando anterior vuelva a no devolver nada y a que
kafka-metadata-quorum.sh describe --replicationmuestre lag 0 antes de pasar al siguiente nodo.
Vigila también estas señales: particiones subreplicadas u offline, lag creciente en los grupos de consumidores y uso de disco en /var/lib/kafka/data. Para llevarlas a un sistema de monitorización, Kafka expone todas sus métricas por JMX.
Solución de problemas
- Un nodo no arranca con
Invalid cluster.id: se formateó con un ID de clúster distinto. Detén el servicio, vacía/var/lib/kafka/dataen ese nodo y formatéalo con el ID correcto. - El quórum no elige líder: menos de dos controladores se ven entre sí. Comprueba el puerto 9093 con
nc -zv kafka-2 9093y quecontroller.quorum.voterses idéntico en todos los nodos. NotEnoughReplicasExceptionen los productores: el ISR de alguna partición tiene menos réplicas quemin.insync.replicas. Revisa qué brokers están caídos o retrasados con--describe --under-replicated-partitions.- Comportamiento errático del quórum o brokers que se expulsan entre sí: dos nodos comparten
node.id. Cada nodo necesita uno único y debe coincidir con su entrada encontroller.quorum.voters.
Conclusión
Has montado un clúster Kafka de tres nodos en KRaft con replicación triple, min.insync.replicas=2 y elección no limpia desactivada, has comprobado que sobrevive a la caída de un nodo sin perder escrituras confirmadas y has visto cómo gestionar offsets, reequilibrar líderes y ampliar el clúster. Como siguientes pasos, protege los listeners con TLS y SASL/SCRAM, exporta las métricas JMX a tu sistema de monitorización y configura los productores de tus aplicaciones con acks=all e idempotencia.
