Apache Kafka es una plataforma de streaming de eventos que guarda los mensajes en registros persistentes y particionados, de forma que muchos productores y consumidores pueden escribir y leer a gran velocidad. Desde Kafka 4.0 ya no existe ZooKeeper: el propio Kafka gestiona sus metadatos con el protocolo KRaft. En este tutorial instalarás Kafka 4 en un único servidor Ubuntu 24.04 en modo combinado (broker y controlador en el mismo proceso), lo ejecutarás como servicio systemd y trabajarás con topics, productores, consumidores y retención.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 4 GB de RAM y 2 vCPU.
  • Espacio en disco suficiente para los mensajes que vayas a retener (20 GB libres es un buen punto de partida para pruebas).
  • Un usuario no root con privilegios sudo.
  • UFW activo con SSH permitido.

Esta instalación de un solo nodo es adecuada para desarrollo, pruebas y cargas pequeñas. Para alta disponibilidad necesitas un clúster de tres nodos o más.

Paso 1: Instalar Java

Los brokers de Kafka 4 necesitan Java 17 o superior. Instala el runtime de OpenJDK 21, que es la versión LTS disponible en Ubuntu 24.04:

sudo apt update
sudo apt install openjdk-21-jre-headless

Verifica la versión:

java -version
openjdk version "21.0.8" 2025-07-15
OpenJDK Runtime Environment (build 21.0.8+9-Ubuntu-0ubuntu124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.8+9-Ubuntu-0ubuntu124.04.1, mixed mode, sharing)

Paso 2: Crear el usuario y descargar Kafka

Ejecutar Kafka con un usuario de sistema propio limita el alcance de un posible fallo de seguridad. Crea el usuario kafka sin shell de inicio de sesión:

sudo useradd --system --home-dir /opt/kafka --shell /usr/sbin/nologin kafka

Descarga Kafka desde el archivo de Apache. Consulta la última versión en la página de descargas de Kafka y ajusta la variable si hay una más reciente:

KAFKA_VERSION=4.1.0
cd /tmp
curl -fLO "https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_2.13-${KAFKA_VERSION}.tgz"
curl -fLO "https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_2.13-${KAFKA_VERSION}.tgz.sha512"

Comprueba la integridad del archivo. Calcula el hash y compáralo con el publicado (el archivo .sha512 lo muestra en bloques separados por espacios, pero los caracteres deben coincidir):

sha512sum "kafka_2.13-${KAFKA_VERSION}.tgz"
cat "kafka_2.13-${KAFKA_VERSION}.tgz.sha512"

Extrae Kafka en /opt, crea un enlace simbólico /opt/kafka (en futuras actualizaciones solo tendrás que cambiar el enlace) y prepara el directorio logs, donde el broker escribe sus registros:

sudo tar -xzf "kafka_2.13-${KAFKA_VERSION}.tgz" -C /opt
sudo ln -sfn "/opt/kafka_2.13-${KAFKA_VERSION}" /opt/kafka
sudo mkdir -p "/opt/kafka_2.13-${KAFKA_VERSION}/logs"
sudo chown -R kafka:kafka "/opt/kafka_2.13-${KAFKA_VERSION}"

Crea el directorio de datos:

sudo install -d -o kafka -g kafka -m 0750 /var/lib/kafka/data

Verifica que las herramientas funcionan:

/opt/kafka/bin/kafka-topics.sh --version
4.1.0

Paso 3: Configurar el broker en modo KRaft

El archivo /opt/kafka/config/server.properties ya viene preparado para un nodo combinado (process.roles=broker,controller). Solo necesitas cambiar dónde guarda los datos y qué dirección anuncia a los clientes.

sudo nano /opt/kafka/config/server.properties

Localiza y modifica estas líneas. Sustituye your_server_ip por la IP con la que se conectarán los clientes (la privada, si están en la misma red privada):

advertised.listeners=PLAINTEXT://your_server_ip:9092,CONTROLLER://localhost:9093

log.dirs=/var/lib/kafka/data

num.partitions=3

log.retention.hours=168

Qué significa cada ajuste:

  • advertised.listeners es la dirección que Kafka devuelve a los clientes para que se conecten. Si dejas localhost, los clientes remotos conectarán al principio y luego fallarán al intentar hablar con localhost en su propia máquina.
  • log.dirs es donde se guardan las particiones. El valor por defecto está en /tmp, que se vacía al reiniciar.
  • num.partitions es el número de particiones de los topics creados sin indicarlo.
  • log.retention.hours conserva los mensajes 7 días.

A continuación genera un identificador de clúster y formatea el directorio de datos. Este paso crea el archivo meta.properties y solo se hace una vez:

KAFKA_CLUSTER_ID="$(/opt/kafka/bin/kafka-storage.sh random-uuid)"
sudo -u kafka /opt/kafka/bin/kafka-storage.sh format --standalone \
  -t "$KAFKA_CLUSTER_ID" -c /opt/kafka/config/server.properties
Formatting dynamic metadata voter directory /var/lib/kafka/data with metadata.version 4.1-IV1.

Paso 4: Crear el servicio systemd

Crea una unidad systemd para que Kafka arranque con el sistema y se reinicie si falla:

sudo nano /etc/systemd/system/kafka.service
[Unit]
Description=Apache Kafka (KRaft)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=kafka
Group=kafka
Environment="KAFKA_HEAP_OPTS=-Xms1g -Xmx1g"
ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties
ExecStop=/opt/kafka/bin/kafka-server-stop.sh
Restart=on-failure
RestartSec=5
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target

KAFKA_HEAP_OPTS fija la memoria de la JVM. Kafka confía mucho en la caché de páginas del sistema operativo, así que no le des más de la mitad de la RAM: con 4 GB, 1 GB de heap es suficiente. LimitNOFILE sube el límite de archivos abiertos, porque cada segmento de partición es un archivo.

Carga la unidad y arranca Kafka:

sudo systemctl daemon-reload
sudo systemctl enable --now kafka

Comprueba el estado y los últimos mensajes:

systemctl status kafka --no-pager
sudo journalctl -u kafka -n 20 --no-pager

Busca en el log una línea parecida a esta:

[KafkaRaftServer nodeId=1] Kafka Server started (kafka.server.KafkaRaftServer)

Confirma también el estado del quórum de metadatos:

/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status
ClusterId:              q7x8R9...
LeaderId:               1
LeaderEpoch:            1
HighWatermark:          152
MaxFollowerLag:         0
CurrentVoters:          [{"id": 1, ...}]

Paso 5: Crear y gestionar topics

Un topic es un flujo de mensajes con nombre, dividido en particiones. Las particiones permiten repartir la lectura entre varios consumidores de un mismo grupo. Crea un topic orders con 3 particiones:

/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \
  --create --topic orders --partitions 3 --replication-factor 1
Created topic orders.

Lista los topics y consulta el detalle:

/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders
Topic: orders	TopicId: 3xk...	PartitionCount: 3	ReplicationFactor: 1	Configs: 
	Topic: orders	Partition: 0	Leader: 1	Replicas: 1	Isr: 1	Elr: 	LastKnownElr: 
	Topic: orders	Partition: 1	Leader: 1	Replicas: 1	Isr: 1	Elr: 	LastKnownElr: 
	Topic: orders	Partition: 2	Leader: 1	Replicas: 1	Isr: 1	Elr: 	LastKnownElr: 

En un solo nodo el factor de replicación solo puede ser 1. Puedes aumentar las particiones más adelante, pero nunca reducirlas:

/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --alter --topic orders --partitions 6

Paso 6: Enviar y leer mensajes

Las herramientas de consola sirven para comprobar que el broker funciona. Abre el productor y escribe mensajes con clave, separando clave y valor con ::

/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders \
  --property parse.key=true --property key.separator=:
>cliente-1:{"order_id": 1, "total": 19.90}
>cliente-2:{"order_id": 2, "total": 5.00}
>cliente-1:{"order_id": 3, "total": 42.10}

Pulsa Ctrl+C para salir. Los mensajes con la misma clave van siempre a la misma partición, lo que garantiza su orden.

En otra terminal, lee los mensajes desde el principio como parte del grupo de consumidores billing:

/opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders \
  --group billing --from-beginning --property print.key=true --property print.partition=true
Partition:0	cliente-2	{"order_id": 2, "total": 5.00}
Partition:2	cliente-1	{"order_id": 1, "total": 19.90}
Partition:2	cliente-1	{"order_id": 3, "total": 42.10}

Deja el consumidor abierto, envía más mensajes desde el productor y verás que llegan en tiempo real. Sal con Ctrl+C y consulta la posición del grupo:

/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group billing
GROUP    TOPIC   PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG  CONSUMER-ID  HOST  CLIENT-ID
billing  orders  0          1               1               0    -            -     -
billing  orders  1          0               0               0    -            -     -
billing  orders  2          2               2               0    -            -     -

La columna LAG indica cuántos mensajes quedan por leer en cada partición. Un lag que crece de forma constante significa que los consumidores no dan abasto.

Paso 7: Configurar la retención por topic

La retención global está en server.properties, pero puedes cambiarla para un topic concreto sin reiniciar. Por ejemplo, conserva orders solo 24 horas o hasta 5 GB por partición, lo que ocurra antes:

/opt/kafka/bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
  --entity-type topics --entity-name orders \
  --add-config retention.ms=86400000,retention.bytes=5368709120

Comprueba la configuración aplicada:

/opt/kafka/bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe \
  --entity-type topics --entity-name orders
Dynamic configs for topic orders are:
  retention.ms=86400000 sensitive=false synonyms={DYNAMIC_TOPIC_CONFIG:retention.ms=86400000, DEFAULT_CONFIG:log.retention.hours=168}
  retention.bytes=5368709120 sensitive=false synonyms={DYNAMIC_TOPIC_CONFIG:retention.bytes=5368709120}

Para topics que representan el estado actual de algo (por ejemplo, el último perfil de cada cliente), usa compactación en lugar de retención por tiempo: Kafka conserva solo el último mensaje de cada clave.

/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create \
  --topic customer-profiles --partitions 3 --replication-factor 1 --config cleanup.policy=compact

Paso 8: Abrir el acceso a los clientes

Este tutorial usa el listener PLAINTEXT, sin cifrado ni autenticación. Permite el puerto 9092 solo desde las IPs de tus aplicaciones, nunca desde cualquier origen:

sudo ufw allow from app_server_ip to any port 9092 proto tcp
sudo ufw status

El puerto 9093 del controlador solo lo usa el propio nodo y no debe abrirse. Desde un cliente con las herramientas de Kafka instaladas, comprueba la conexión:

kafka-topics.sh --bootstrap-server your_server_ip:9092 --list

Solución de problemas

  • No readable meta.properties files found al arrancar: el directorio de log.dirs no se ha formateado. Ejecuta el kafka-storage.sh format del paso 3 como usuario kafka.
  • Los clientes remotos conectan pero dan timeout o intentan conectar a localhost: advertised.listeners no contiene la IP accesible desde el cliente.
  • UnsupportedClassVersionError: la versión de Java es anterior a la 17. Comprueba java -version y que no haya otro Java por delante en el PATH.
  • El servicio muere con OutOfMemoryError o el sistema mata el proceso: reduce KAFKA_HEAP_OPTS o añade RAM, y revisa sudo journalctl -u kafka.

Conclusión

Tienes Kafka 4 funcionando en modo KRaft como servicio systemd, con datos en /var/lib/kafka/data, un topic particionado, un grupo de consumidores y políticas de retención y compactación. Como siguientes pasos, amplía la instalación a un clúster de tres nodos con replicación, protege los listeners con TLS y SASL, y conecta tus aplicaciones con un cliente oficial como librdkafka o el cliente Java.