Apache ZooKeeper es un servicio de coordinación distribuida: guarda pequeños datos en un árbol de nodos (znodes) replicado en varios servidores y ofrece primitivas como nodos efímeros, nodos secuenciales y notificaciones (watches), sobre las que se construyen la elección de líder, el registro de servicios o los bloqueos distribuidos. Lo usan Apache HBase, Solr, Hadoop y muchas aplicaciones propias. En este tutorial instalarás un ensemble de tres servidores ZooKeeper en Ubuntu 24.04, lo gestionarás con systemd y aprenderás a trabajar con znodes y ACLs.

Requisitos previos

Para seguir esta guía necesitas:

  • Tres servidores con Ubuntu 24.04 LTS, por ejemplo tres VPS de CubePath, con al menos 2 GB de RAM cada uno.
  • Un usuario no root con privilegios sudo en cada servidor.
  • Una red privada entre los tres. En los ejemplos se usan estas direcciones, sustitúyelas por las tuyas:
NombreIP privadaID de ZooKeeper
zk-0110.0.1.101
zk-0210.0.1.112
zk-0310.0.1.123

Un ensemble necesita que la mayoría de servidores estén activos para funcionar. Con tres servidores tolera la caída de uno; con cinco, de dos. Salvo que se indique lo contrario, ejecuta cada paso en los tres servidores.

Paso 1: Instalar Java

ZooKeeper es una aplicación Java. Instala el entorno de ejecución de OpenJDK 17 sin componentes gráficos:

sudo apt update
sudo apt install -y openjdk-17-jre-headless
java -version
openjdk version "17.0.x" ...
OpenJDK Runtime Environment (build 17.0.x+...-Ubuntu-...)
OpenJDK 64-Bit Server VM (build 17.0.x+..., mixed mode, sharing)

Paso 2: Descargar e instalar ZooKeeper

Usarás el paquete binario oficial de Apache. Consulta la versión estable actual en la página de releases de ZooKeeper y ajusta la variable. El archivo de Apache (archive.apache.org) conserva todas las versiones, así que la URL no deja de funcionar cuando sale una nueva:

ZK_VERSION=3.9.3
cd /tmp
curl -fsSLO "https://archive.apache.org/dist/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz"
curl -fsSLO "https://archive.apache.org/dist/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz.sha512"
sha512sum "apache-zookeeper-${ZK_VERSION}-bin.tar.gz"
cat "apache-zookeeper-${ZK_VERSION}-bin.tar.gz.sha512"

Los dos comandos deben mostrar el mismo hash. Si no coinciden, borra el archivo y vuelve a descargarlo.

Extrae el paquete en /opt y crea un enlace simbólico estable, para que las actualizaciones consistan en cambiar el enlace:

sudo tar -xzf "apache-zookeeper-${ZK_VERSION}-bin.tar.gz" -C /opt
sudo ln -sfn "/opt/apache-zookeeper-${ZK_VERSION}-bin" /opt/zookeeper

Crea un usuario de sistema sin shell y el directorio de datos:

sudo useradd --system --home-dir /var/lib/zookeeper --shell /usr/sbin/nologin zookeeper
sudo install -d -o zookeeper -g zookeeper -m 0750 /var/lib/zookeeper /var/lib/zookeeper/data /var/log/zookeeper

Los binarios quedan como propiedad de root; el usuario zookeeper solo necesita escribir en su directorio de datos y de logs.

Paso 3: Configurar el ensemble

Crea el archivo de configuración principal:

sudo nano /opt/zookeeper/conf/zoo.cfg

El contenido es idéntico en los tres servidores:

tickTime=2000
initLimit=10
syncLimit=5

dataDir=/var/lib/zookeeper/data
clientPort=2181
maxClientCnxns=60

autopurge.snapRetainCount=5
autopurge.purgeInterval=24

4lw.commands.whitelist=ruok,srvr,stat,mntr,conf

admin.serverAddress=127.0.0.1
admin.serverPort=8080

server.1=10.0.1.10:2888:3888
server.2=10.0.1.11:2888:3888
server.3=10.0.1.12:2888:3888

Qué hace cada bloque:

  • tickTime es la unidad de tiempo en milisegundos. initLimit y syncLimit son los ticks que un seguidor puede tardar en conectarse al líder y en mantenerse sincronizado (20 y 10 segundos).
  • dataDir guarda los snapshots y el registro de transacciones.
  • autopurge.* borra cada 24 horas los snapshots antiguos y conserva los 5 últimos. Sin esto, el disco se llena con el tiempo.
  • 4lw.commands.whitelist habilita los comandos de diagnóstico de cuatro letras. Por defecto solo está permitido srvr.
  • admin.serverAddress limita la AdminServer HTTP a 127.0.0.1. Por defecto escucha en todas las interfaces en el puerto 8080.
  • Cada línea server.N define un miembro: su ID, su IP, el puerto por el que los seguidores hablan con el líder (2888) y el de elección de líder (3888). Usa IP y no nombres de host: en Ubuntu, el nombre propio del servidor suele resolver a 127.0.1.1 y ZooKeeper escucharía solo en local.

Cada servidor necesita saber cuál es su ID. Escríbelo en el archivo myid del directorio de datos, con el valor que corresponda a cada nodo (1 en zk-01, 2 en zk-02, 3 en zk-03):

echo 1 | sudo tee /var/lib/zookeeper/data/myid
sudo chown zookeeper:zookeeper /var/lib/zookeeper/data/myid

Paso 4: Crear el servicio systemd

Crea la unidad systemd. zkServer.sh start-foreground mantiene el proceso en primer plano, que es lo que espera systemd, y los logs van directamente al journal:

sudo nano /etc/systemd/system/zookeeper.service
[Unit]
Description=Apache ZooKeeper
Documentation=https://zookeeper.apache.org
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=zookeeper
Group=zookeeper
Environment=ZOO_LOG_DIR=/var/log/zookeeper
Environment="SERVER_JVMFLAGS=-Xms1g -Xmx1g"
ExecStart=/opt/zookeeper/bin/zkServer.sh start-foreground
Restart=on-failure
RestartSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

SERVER_JVMFLAGS fija la memoria de la JVM. Un valor fijo (mismo -Xms y -Xmx) de la mitad de la RAM del servidor es un buen punto de partida; ZooKeeper guarda todo el árbol en memoria, pero con datos de coordinación rara vez necesita más de unos pocos GB.

Paso 5: Abrir los puertos y arrancar

Permite el tráfico de ZooKeeper solo desde la red privada: 2181 para clientes, 2888 y 3888 entre servidores. Si UFW no está activo, permite antes SSH:

sudo ufw allow OpenSSH
sudo ufw allow from 10.0.1.0/24 to any port 2181 proto tcp
sudo ufw allow from 10.0.1.0/24 to any port 2888 proto tcp
sudo ufw allow from 10.0.1.0/24 to any port 3888 proto tcp
sudo ufw enable

Arranca el servicio en los tres servidores:

sudo systemctl daemon-reload
sudo systemctl enable --now zookeeper
sudo systemctl status zookeeper

Cuando los tres estén en marcha, comprueba el rol de cada uno:

/opt/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

Uno de los tres servidores mostrará Mode: leader y los otros dos Mode: follower. Si ves Mode: standalone, las líneas server.N no se han leído; si el comando no puede conectarse, revisa los logs con sudo journalctl -u zookeeper -e.

Comprueba también la salud con un comando de cuatro letras:

echo ruok | nc 127.0.0.1 2181
imok

Paso 6: Trabajar con znodes

El cliente zkCli.sh permite explorar y modificar el árbol. Conéctate al ensemble indicando los tres servidores; si uno cae, el cliente se reconecta a otro:

/opt/zookeeper/bin/zkCli.sh -server 10.0.1.10:2181,10.0.1.11:2181,10.0.1.12:2181

Los siguientes comandos se escriben dentro del shell de ZooKeeper. Crea un znode persistente con datos y léelo:

create /app ""
create /app/config "log_level=info"
get /app/config
Created /app
Created /app/config
log_level=info

Actualiza el valor y consulta los metadatos. El campo dataVersion aumenta con cada cambio, lo que permite actualizaciones condicionales:

set /app/config "log_level=debug"
stat /app/config

Los nodos efímeros desaparecen cuando se cierra la sesión del cliente que los creó. Son la base del registro de servicios: cada instancia crea el suyo y, si se cae, desaparece solo. Los nodos secuenciales reciben un sufijo numérico creciente, útil para colas y elección de líder:

create /app/workers ""
create -e /app/workers/worker-01 "10.0.2.10:8080"
create -s /app/workers/job- "tarea"
ls /app/workers
[job-0000000001, worker-01]

Para recibir una notificación cuando cambie un nodo, registra un watch. Ejecuta esto, cambia el valor desde otra sesión de zkCli.sh y verás el aviso WatchedEvent:

get -w /app/config

Sal del shell con quit. Puedes comprobar desde otro servidor que los datos se han replicado:

/opt/zookeeper/bin/zkCli.sh -server 10.0.1.12:2181 get /app/config

Paso 7: Proteger znodes con ACLs

Por defecto cualquier cliente que llegue al puerto 2181 puede leer y modificar todo el árbol. Las ACLs se definen por znode con el formato esquema:id:permisos, donde los permisos son c (crear hijos), d (borrar hijos), r (leer), w (escribir) y a (administrar ACLs).

El esquema digest autentica con usuario y contraseña. Conéctate con zkCli.sh y, dentro del shell, autentícate y crea un znode que solo ese usuario pueda usar. El esquema auth aplica la ACL a los usuarios autenticados en la sesión actual. Sustituye your_strong_password por una contraseña real:

addauth digest appuser:your_strong_password
create /secretos "dato-privado" auth::cdrwa
getAcl /secretos
Created /secretos
'digest,'appuser:...
: cdrwa

Abre una segunda sesión de zkCli.sh sin autenticarte e intenta leerlo:

get /secretos
Insufficient permission : /secretos

Tras ejecutar addauth digest appuser:your_strong_password en esa sesión, la lectura funciona. Para un znode que todos puedan leer pero solo el usuario autenticado pueda modificar, combina dos ACLs: setAcl /app world:anyone:r,auth::cdrwa.

Paso 8: Monitorizar el ensemble

El comando mntr devuelve métricas en formato clave-valor, fáciles de recoger con cualquier sistema de monitorización:

echo mntr | nc 127.0.0.1 2181 | grep -E 'zk_server_state|zk_avg_latency|zk_outstanding_requests|zk_synced_followers'
zk_server_state	leader
zk_avg_latency	0.4
zk_outstanding_requests	0
zk_synced_followers	2

Vigila especialmente:

  • zk_server_state: debe haber exactamente un leader en el ensemble.
  • zk_synced_followers (solo en el líder): debe ser el número de servidores menos uno.
  • zk_outstanding_requests: si crece de forma sostenida, el ensemble no da abasto.
  • zk_avg_latency: latencia media en milisegundos; valores altos suelen indicar disco lento.

La AdminServer ofrece la misma información en JSON a través de HTTP, solo en local:

curl -s http://127.0.0.1:8080/commands/ruok
{
  "command" : "ruok",
  "error" : null
}

Solución de problemas

  • El servidor muestra Mode: standalone: falta el archivo myid o las líneas server.N no están en zoo.cfg. Revisa ambos y reinicia con sudo systemctl restart zookeeper.
  • No se elige líder (Error contacting service en todos los nodos): los servidores no se ven por el puerto 3888. Prueba nc -zv 10.0.1.11 3888 y revisa las reglas de UFW. Comprueba también que cada myid coincide con su línea server.N.
  • ruok is not executed because it is not in the whitelist: añade el comando a 4lw.commands.whitelist en zoo.cfg y reinicia.
  • Address already in use en el puerto 8080: otra aplicación usa ese puerto. Cambia admin.serverPort o desactiva la AdminServer con admin.enableServer=false.
  • Disco lleno en /var/lib/zookeeper: falta la purga automática. Comprueba los valores autopurge.*.

Conclusión

Tienes un ensemble de tres servidores ZooKeeper replicado, gestionado con systemd, con purga automática de snapshots, comandos de diagnóstico habilitados y ACLs para proteger los znodes. Como siguientes pasos, recoge las métricas de mntr en tu sistema de monitorización, configura TLS para las conexiones de clientes si salen de la red privada y conecta tus aplicaciones al ensemble con la cadena 10.0.1.10:2181,10.0.1.11:2181,10.0.1.12:2181.