Mensajería y streaming de eventos
Instalar y operar Apache Kafka en modo KRaft: puesta en marcha, CLI y monitorización
Intermedio8 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Un clúster de Kafka sin ZooKeeper se reduce a unos cuantos procesos Java con un fichero de propiedades cada uno, pero los ajustes de ese fichero deciden si el clúster sobrevive a la pérdida de un disco o a un disco lleno. Esta guía instala Kafka 3.x en modo KRaft, primero en un solo nodo y luego como clúster de tres brokers, repasa las herramientas de línea de comandos que usas a diario para topics, consumer groups y offsets, y explica los parámetros del broker, las métricas, las opciones de seguridad y los pasos de actualización que importan en producción.
Lo que aprenderás
- Formatear el almacenamiento y arrancar un broker KRaft o un nodo combinado broker/controlador desde el tarball o un contenedor
- Escribir el server.properties de un clúster de producción con tres brokers y tres controladores
- Crear, inspeccionar y reconfigurar topics y restablecer los offsets de un consumer group con las herramientas CLI
- Definir los valores por defecto del broker que protegen los datos: replicación, min.insync.replicas, retención y creación de topics
- Monitorizar las métricas que anticipan las caídas: particiones infrarreplicadas, estado del controlador, latencia de peticiones y lag de los consumidores
- Activar TLS, SASL y ACL, y planificar una actualización escalonada o una migración desde ZooKeeper
Kafka arrastra una fama de ser difícil de operar que viene de la época de ZooKeeper, cuando cada clúster suponía dos sistemas distribuidos, dos configuraciones de seguridad y un failover del controlador que podía tardar minutos. En modo KRaft, listo para producción desde Kafka 3.3 y único modo disponible en la 4.0, un clúster es un conjunto de procesos Java idénticos cuyo rol se define en un único fichero de propiedades. Esta guía instala Kafka 3.x de esa manera y después cubre las herramientas del día a día y los ajustes de producción que el quickstart no menciona. Todos los comandos son para el tarball de Linux; la variante con contenedores se muestra cuando difiere.
Requisitos previos y estructura de ficheros
Un broker necesita un JDK —17 es la opción segura para la 3.x y es obligatorio para la 4.x—, un usuario dedicado y un sistema de ficheros con espacio para log.dirs. El disco importa más que la CPU: Kafka escribe de forma secuencial y lee a través de la page cache, así que lo habitual es usar varios discos corrientes o un volumen rápido por broker, con el directorio de datos en su propio punto de montaje. Asigna a la JVM entre 4 y 6 GB de heap y deja el resto de la memoria de la máquina para la page cache; un heap más grande hace que Kafka sea más lento, no más rápido.
sudo useradd --system --home /opt/kafka --shell /usr/sbin/nologin kafka
curl -LO https://archive.apache.org/dist/kafka/3.9.0/kafka_2.13-3.9.0.tgz
sudo tar -xzf kafka_2.13-3.9.0.tgz -C /opt
sudo ln -sfn /opt/kafka_2.13-3.9.0 /opt/kafka
sudo mkdir -p /var/lib/kafka && sudo chown -R kafka:kafka /var/lib/kafka /opt/kafka_2.13-3.9.0
El 2.13 del nombre del fichero es la versión de Scala con la que se compiló el broker; no afecta en nada a los clientes Java. Dentro del archivo, bin/ contiene los scripts de la CLI, config/kraft/ las configuraciones de ejemplo de KRaft (server.properties para un nodo combinado, broker.properties y controller.properties para roles separados) y libs/ los jars. Kafka 4.0 elimina el subdirectorio kraft/, porque ya no existe otro modo.
Un clúster de un solo nodo
Antes de arrancar, hay que decirle a un nodo KRaft a qué clúster pertenece, y de eso se encarga el paso de formateo del almacenamiento. Escribe en cada directorio de logs un fichero meta.properties con el id del clúster y el id del nodo; un broker se niega a arrancar sobre un directorio formateado para otro clúster, lo que evita el clásico accidente de un broker extraviado que se une a producción.
# config/kraft/server.properties — combined broker and controller for development
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@localhost:9093
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
advertised.listeners=PLAINTEXT://localhost:9092
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
log.dirs=/var/lib/kafka
num.partitions=3
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
KAFKA_CLUSTER_ID=$(bin/kafka-storage.sh random-uuid) # once per cluster; keep it
bin/kafka-storage.sh format -t "$KAFKA_CLUSTER_ID" -c config/kraft/server.properties
bin/kafka-server-start.sh config/kraft/server.properties # foreground; add -daemon to background
Lo mismo en un contenedor, con la imagen oficial que el proyecto publica desde la 3.7:
docker run -d --name kafka -p 9092:9092 \
-e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
-e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \
-e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \
-e KAFKA_LOG_DIRS=/var/lib/kafka/data \
-v kafka-data:/var/lib/kafka/data \
apache/kafka:3.9.0
Cada propiedad se corresponde con una variable de entorno: se pasa a mayúsculas, se sustituyen los puntos por guiones bajos y se añade el prefijo KAFKA_. En el primer arranque, la propia imagen formatea el almacenamiento con un id de clúster generado.
Comprueba el nodo con un topic y una ida y vuelta:
bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic smoke --partitions 1 --replication-factor 1
echo "hello" | bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic smoke
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic smoke --from-beginning --max-messages 1
Un clúster de producción
Un clúster de verdad separa los roles. Tres controladores en máquinas pequeñas forman el quórum de metadatos; tres o más brokers, en máquinas con discos de verdad, guardan los datos. Cada nodo tiene su propio node.id, único entre ambos roles.
# controller.properties on controller nodes 1, 2, 3 (change node.id on each)
process.roles=controller
node.id=1
controller.quorum.voters[email protected]:9093,[email protected]:9093,[email protected]:9093
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
log.dirs=/var/lib/kafka/metadata
# broker.properties on broker nodes 11, 12, 13 (change node.id and advertised host on each)
process.roles=broker
node.id=11
controller.quorum.voters[email protected]:9093,[email protected]:9093,[email protected]:9093
listeners=INTERNAL://:9092,EXTERNAL://:9094
advertised.listeners=INTERNAL://kafka-11.internal:9092,EXTERNAL://kafka-11.example.com:9094
listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:SASL_SSL,CONTROLLER:PLAINTEXT
inter.broker.listener.name=INTERNAL
controller.listener.names=CONTROLLER
log.dirs=/data/kafka-1,/data/kafka-2 # one directory per disk; partitions are spread across them
# Defaults that protect data
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
auto.create.topics.enable=false # topics are created deliberately, with the right settings
num.partitions=6
log.retention.hours=168
log.segment.bytes=1073741824
message.max.bytes=1048588
Dos de estos parámetros merecen especial atención. auto.create.topics.enable=false evita que un nombre de topic mal escrito en un productor cree en silencio un topic de una sola partición e infrarreplicado. Y min.insync.replicas=2 es la mitad del contrato de durabilidad que corresponde al broker: con él, un productor con acks=all recibe un rechazo cuando hay menos de dos réplicas vivas, en lugar de que se le diga que un registro está a salvo cuando solo existe en una máquina.
Kafka 3.9 añadió la pertenencia dinámica al quórum (controller.quorum.bootstrap.servers en lugar de una lista estática controller.quorum.voters, además de kafka-metadata-quorum.sh add-controller), que permite sustituir un controlador sin reiniciar los demás. La lista estática sigue funcionando y resulta más fácil de entender en un primer clúster.
Ejecuta cada nodo bajo systemd con Restart=on-failure, un LimitNOFILE generoso (Kafka mantiene un descriptor de fichero por segmento y por índice) y KAFKA_HEAP_OPTS="-Xms4g -Xmx4g" en el entorno.
Operación diaria con la CLI
Todos los scripts de bin/ hablan con el clúster a través de --bootstrap-server; no hay un puerto de administración aparte. Los cuatro que más vas a usar:
Topics. Créalos con parámetros explícitos, descríbelos para ver los líderes y las réplicas sincronizadas, y modifícalos para añadir particiones o cambiar la configuración.
bin/kafka-topics.sh --bootstrap-server kafka-11.internal:9092 --create --topic orders \
--partitions 12 --replication-factor 3 --config min.insync.replicas=2 --config retention.ms=1209600000
bin/kafka-topics.sh --bootstrap-server kafka-11.internal:9092 --describe --topic orders
# Topic: orders Partition: 0 Leader: 11 Replicas: 11,12,13 Isr: 11,12,13
# A partition whose Isr is shorter than Replicas is under-replicated
bin/kafka-configs.sh --bootstrap-server kafka-11.internal:9092 --alter --entity-type topics \
--entity-name orders --add-config cleanup.policy=compact,delete
Consumer groups. Al describirlos ves, para cada partición, el offset confirmado, el offset de fin de log, el lag y qué miembro la tiene asignada; restablecer los offsets te permite reprocesar mensajes o saltártelos.
bin/kafka-consumer-groups.sh --bootstrap-server kafka-11.internal:9092 --describe --group billing
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
# billing orders 0 1204 1210 6 billing-1-… /10.0.1.5 billing
# Rewind the whole group to two hours ago — the group must be stopped first
bin/kafka-consumer-groups.sh --bootstrap-server kafka-11.internal:9092 --group billing \
--topic orders --reset-offsets --to-datetime 2026-09-07T07:00:00.000 --execute
# Other targets: --to-earliest, --to-latest, --to-offset N, --shift-by -100, --by-duration PT1H
Console producer y console consumer. Sirven para comprobar que los datos fluyen y para echar un vistazo al contenido de un topic; --property print.key=true --property print.timestamp=true en el consumidor y --property parse.key=true --property key.separator=: en el productor hacen visibles las claves.
Quórum de metadatos. bin/kafka-metadata-quorum.sh --bootstrap-server kafka-11.internal:9092 describe --status muestra el controlador activo, los votantes, su retraso respecto al líder y la versión de metadatos actual; es el primer comando que hay que lanzar cuando el clúster se porta mal.
Otros dos comandos se ganan su sitio en un runbook: kafka-reassign-partitions.sh mueve réplicas entre brokers cuando añades o retiras un nodo (no reequilibra de forma automática), y kafka-leader-election.sh restablece los líderes preferidos cuando un broker vuelve, para que el tráfico se reparta de nuevo de forma equilibrada.
Monitorización
Los brokers exponen cientos de métricas JMX; un exportador JMX para Prometheus o el agente de tu sistema de monitorización las convierte en dashboards. Una lista corta anticipa la mayoría de los incidentes:
| Métrica (JMX) | Valor sano | Qué significa cuando no lo es |
|---|---|---|
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions | 0 | Un follower va con retraso o está caído; la durabilidad con acks=all se reduce y, al llegar a min.insync.replicas, se rechazará a los productores |
kafka.controller:type=KafkaController,name=ActiveControllerCount | 1 en todo el clúster | 0 significa que no hay controlador —ni elecciones de líder ni cambios en topics—; más de 1 indica un split-brain |
kafka.server:type=ReplicaManager,name=OfflinePartitionsCount | 0 | Particiones sin líder; sus productores y consumidores fallan |
kafka.network:type=RequestMetrics,name=TotalTimeMs,request=Produce (p99) | Unas pocas decenas de ms | Latencia del disco o de la replicación; los productores se acercan a delivery.timeout.ms |
kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent | Por encima de 0,3 | Threads del broker saturados; añade brokers o reduce el número de particiones por broker |
kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMs | Estable | Un tiempo de flush creciente indica que el disco es el cuello de botella |
Lag de los consumidores por grupo (kafka-consumer-groups.sh o records-lag-max en el consumidor) | Estable o bajando | Los consumidores son demasiado lentos, están parados o se han atascado en un mensaje envenenado |
Configura también alertas sobre el uso de disco de cada ruta de log.dirs: un broker cuyo disco de datos se llena deja fuera de línea el directorio afectado y, si solo hay un directorio, eso significa el broker entero. Los límites de retención son tu defensa; dimensiónalos en función del disco, con margen para el pico del topic más grande.
Seguridad
Por defecto, un listener de Kafka va en texto plano y sin autenticación. Protegerlo requiere tres capas, todas configuradas por listener:
- Cifrado con TLS:
ssl.keystore.locationyssl.truststore.locationen el broker, ySSLoSASL_SSLenlistener.security.protocol.map. - Autenticación con SASL.
SCRAM-SHA-512guarda las credenciales en los metadatos del clúster (kafka-configs.sh --alter --add-config 'SCRAM-SHA-512=[password=...]' --entity-type users --entity-name orders),OAUTHBEARERse integra con un proveedor de identidad yGSSAPIcon Kerberos. El mTLS con certificados de cliente es una cuarta opción que no necesita SASL en absoluto. - Autorización con ACL:
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizeren todos los nodos,super.userspara los operadores ykafka-acls.sh --add --allow-principal User:orders --operation Write --topic orderspara las aplicaciones. A los consumidores concédelesReadsobre los topics yReadsobre el consumer group; olvidar la ACL del grupo es la causa más habitual de unaGroupAuthorizationException.
Mantén el listener de los controladores en una red privada: transporta el log de metadatos y, en un despliegue con roles separados, los clientes no necesitan alcanzarlo en absoluto.
Actualizaciones y migración desde ZooKeeper
Los brokers se actualizan de uno en uno: detén el broker, instala la nueva versión, arráncalo, espera a que UnderReplicatedPartitions vuelva a cero y pasa al siguiente. Los clientes siguen funcionando durante todo el proceso porque el protocolo se negocia en cada conexión. Cuando todos los nodos ejecuten la nueva versión, sube la versión de metadatos —bin/kafka-features.sh --bootstrap-server ... upgrade --metadata 3.9— para desbloquear las funcionalidades que aporta la nueva release; hasta entonces el clúster se comporta como la versión anterior, y puedes aplazar este paso todo lo que quieras.
Los clústeres que siguen en ZooKeeper deben migrar antes de la 4.0, que ya no puede leer ZooKeeper en absoluto. El procedimiento documentado aprovisiona un quórum de controladores KRaft en modo migración, deja que copie los metadatos desde ZooKeeper, reinicia los brokers de uno en uno en modo KRaft y, por último, saca a los controladores del modo migración; hasta ese último paso existe una vía de marcha atrás. Plantéalo como un proyecto, con un ensayo sobre una copia de producción, y no como una ventana de mantenimiento.
Errores comunes
advertised.listenersdejado en localhost. El problema más típico de «en la máquina funciona, pero en ningún otro sitio»; configúralo con una dirección que los clientes puedan alcanzar.- Factor de replicación 1 en los topics internos. El
offsets.topic.replication.factor=1del quickstart, en producción, significa que al perder un solo broker se pierde la posición de todos los consumer groups. Pon a 3 los tres factores de replicación internos. auto.create.topics.enabledejado activado. Las erratas se convierten en topics infrarreplicados permanentes; tarde o temprano alguien consume del equivocado.- Un heap dimensionado como el de una base de datos. Con 4 a 6 GB hay de sobra; el trabajo de verdad lo hace la page cache. Los heaps muy grandes provocan pausas largas del garbage collector que, para el controlador, parecen un broker muerto.
- Añadir un broker y esperar que asuma carga. Las particiones existentes se quedan donde están hasta que ejecutes
kafka-reassign-partitions.sh. - Sin alertas de lag. Un consumidor que lleva una semana muerto se descubre cuando se agota su ventana de retención y los datos ya han desaparecido.
Ideas clave
- KRaft elimina ZooKeeper: formatea el almacenamiento de cada nodo con el id del clúster, define
process.roles,node.idy el quórum de controladores, y arranca el proceso. - En desarrollo basta con un nodo combinado; en producción se usan tres controladores dedicados y tres o más brokers, cada uno con unos
advertised.listenersque los clientes puedan alcanzar de verdad. default.replication.factor=3,min.insync.replicas=2, topics internos replicados,auto.create.topics.enable=falsey una retención explícita son los ajustes del broker que mantienen los datos a salvo.kafka-topics.sh,kafka-consumer-groups.sh,kafka-configs.shykafka-metadata-quorum.shcubren casi todo el trabajo diario, incluido el restablecimiento de los offsets de un grupo para reprocesar mensajes.- Vigila las particiones infrarreplicadas y fuera de línea, el número de controladores activos, la latencia de producción, el porcentaje de inactividad de los handlers, el uso de disco y el lag de los consumidores.
- Protege los listeners con TLS, SASL y ACL; actualiza los brokers de uno en uno y sube después la versión de metadatos; migra los clústeres que queden en ZooKeeper antes de pasar a la 4.x.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Cuántos controladores necesita un clúster KRaft?
- Tres en la mayoría de los clústeres y cinco en los muy grandes o muy críticos. Raft necesita una mayoría para avanzar, así que tres controladores toleran un fallo y cinco toleran dos. Para desarrollo basta con uno. Los nodos combinados broker/controlador son aceptables en clústeres pequeños, pero en producción lo habitual es ejecutar los controladores como procesos separados y poco cargados, para que la carga de los brokers no pueda retrasar las decisiones sobre los metadatos.
- ¿Por qué mis clientes se conectan desde el host pero no desde otras máquinas o contenedores?
- Porque advertised.listeners indica a los clientes adónde conectarse en las peticiones siguientes, y por defecto toma el valor de listeners, que suele ser localhost. El cliente hace el bootstrap sin problemas, recibe localhost:9092 como dirección del líder y entonces falla. Configura advertised.listeners con un nombre de host o una IP que todos los clientes puedan resolver y alcanzar, con listeners separados para el tráfico interno y el externo si son distintos.
- ¿Puedo reducir el número de particiones de un topic?
- No. Solo se pueden añadir. Para reducirlas, crea un topic nuevo con el número deseado, copia los datos con una pareja consumidor-productor o con MirrorMaker, cambia consumidores y productores al nuevo topic y elimina el antiguo. Como las claves se asignan a las particiones por hash, la copia altera el orden por clave respecto al topic antiguo.
- ¿Tengo que actualizar los clientes cuando actualizo los brokers?
- No. El protocolo de Kafka está versionado y los brokers aceptan peticiones de clientes más antiguos; los clientes más nuevos que el broker también funcionan, pero no usarán las funcionalidades que le falten al broker. Actualiza primero los brokers, de uno en uno, luego sube la versión de metadatos con kafka-features.sh y, por último, actualiza los clientes a tu ritmo.