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.12 y 10.0.0.13; sustitúyelas por las tuyas.
  • Kafka 4 instalado en cada nodo en /opt/kafka, con Java 21, el usuario kafka, el directorio /var/lib/kafka/data y la unidad systemd kafka.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 sudo en 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

ConceptoValor en este tutorialPor qué
Nodos3, cada uno con process.roles=broker,controllerEl quórum KRaft necesita mayoría: con 3 controladores tolera la caída de 1
Factor de replicación3Cada partición tiene una copia en cada nodo
min.insync.replicas2Con acks=all, un mensaje solo se confirma cuando lo tienen al menos 2 réplicas
Elección no limpiaDesactivadaUna 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.voters define un quórum estático de tres controladores. Cada node.id debe coincidir con el número antes de la @.
  • offsets.topic.replication.factor y transaction.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=false evita 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	...
	...
  • Leader es el broker que atiende lecturas y escrituras de la partición.
  • Replicas son 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:

  1. Comprueba que no hay particiones subreplicadas con --under-replicated-partitions.
  2. Reinicia el nodo con sudo systemctl restart kafka.
  3. Espera a que el comando anterior vuelva a no devolver nada y a que kafka-metadata-quorum.sh describe --replication muestre 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/data en 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 9093 y que controller.quorum.voters es idéntico en todos los nodos.
  • NotEnoughReplicasException en los productores: el ISR de alguna partición tiene menos réplicas que min.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 en controller.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.