Instalación y Configuración de Apache ZooKeeper

Apache ZooKeeper es un servicio de coordinación distribuida de alto rendimiento que proporciona sincronización, configuración centralizada y elección de líder para sistemas distribuidos como Apache Kafka, HBase y Hadoop. Su arquitectura de ensemble con replicación y consistencia garantizada mediante ZAB (ZooKeeper Atomic Broadcast) lo hace esencial en infraestructuras de big data. Esta guía cubre el despliegue de un ensemble ZooKeeper en Linux, configuración, ACLs y monitorización.

Requisitos Previos

  • 3 o 5 servidores Linux (Ubuntu 22.04/Debian 12 o CentOS 9/Rocky 9)
  • Java 11 o superior instalado en todos los nodos
  • Mínimo 2 GB de RAM por nodo (4 GB recomendado en producción)
  • Disco separado para datos de ZooKeeper (SSD recomendado)
  • Puertos 2181 (cliente), 2888 (follower→leader) y 3888 (elección) abiertos entre nodos
  • NTP sincronizado

Instalación de ZooKeeper

# Instalar Java si no está disponible
apt-get update && apt-get install -y openjdk-17-jre-headless
# CentOS/Rocky:
# dnf install -y java-17-openjdk-headless

java -version

# Definir la versión a instalar
ZK_VERSION="3.9.2"

# Descargar ZooKeeper desde Apache
wget "https://downloads.apache.org/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz"
tar xzf "apache-zookeeper-${ZK_VERSION}-bin.tar.gz" -C /opt/
ln -s "/opt/apache-zookeeper-${ZK_VERSION}-bin" /opt/zookeeper

# Crear usuario de sistema
useradd -r -d /opt/zookeeper -s /bin/false zookeeper
chown -R zookeeper:zookeeper /opt/zookeeper

# Crear directorios de datos y logs en disco separado (si disponible)
mkdir -p /var/lib/zookeeper/data /var/log/zookeeper
chown -R zookeeper:zookeeper /var/lib/zookeeper /var/log/zookeeper

# Añadir ZooKeeper al PATH del sistema
echo 'export PATH=$PATH:/opt/zookeeper/bin' > /etc/profile.d/zookeeper.sh

Configuración del Ensemble

Configura el archivo zoo.cfg en cada nodo del ensemble:

cat > /opt/zookeeper/conf/zoo.cfg << 'EOF'
# Duración de un tick en milisegundos (unidad base de tiempo)
tickTime=2000

# Número de ticks que un servidor follower puede tardar en conectarse al líder
initLimit=10

# Número de ticks para la sincronización entre líder y followers
syncLimit=5

# Directorio donde ZooKeeper almacena los snapshots
dataDir=/var/lib/zookeeper/data

# Directorio para los logs de transacciones (idealmente disco separado)
dataLogDir=/var/lib/zookeeper/datalog

# Puerto de escucha para clientes
clientPort=2181

# Límite de conexiones por cliente
maxClientCnxns=60

# Retención de snapshots (cuántos snapshots mantener)
autopurge.snapRetainCount=5
autopurge.purgeInterval=24

# Configuración del ensemble (todos los nodos deben tenerlo igual)
# Formato: server.ID=hostname:puertoFollower:puertoEleccion
server.1=zk-01.empresa.local:2888:3888
server.2=zk-02.empresa.local:2888:3888
server.3=zk-03.empresa.local:2888:3888

# Métricas con Prometheus (ZooKeeper 3.6+)
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7070
metricsProvider.exportJvmInfo=true

# Logs de auditoría (ZooKeeper 3.6+)
audit.enable=true
EOF

# CRÍTICO: Establecer el ID del nodo en el archivo myid
# En zk-01: echo "1" > /var/lib/zookeeper/data/myid
# En zk-02: echo "2" > /var/lib/zookeeper/data/myid
# En zk-03: echo "3" > /var/lib/zookeeper/data/myid
echo "1" > /var/lib/zookeeper/data/myid  # Ajustar según el nodo

chown zookeeper:zookeeper /var/lib/zookeeper/data/myid

Configura los parámetros de la JVM:

# Configurar la JVM para producción
cat > /opt/zookeeper/conf/java.env << 'EOF'
# Memoria de la JVM (ajustar según la RAM disponible)
export JVMFLAGS="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=50"

# Directorio de logs
export ZOO_LOG_DIR=/var/log/zookeeper
export ZOO_LOG4J_PROP="INFO,ROLLINGFILE"
EOF

Gestión del Servicio

Crea el servicio systemd para ZooKeeper:

cat > /etc/systemd/system/zookeeper.service << 'EOF'
[Unit]
Description=Apache ZooKeeper - Distributed Coordination Service
Documentation=https://zookeeper.apache.org
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
User=zookeeper
Group=zookeeper
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64"
ExecStart=/opt/zookeeper/bin/zkServer.sh start
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
ExecReload=/opt/zookeeper/bin/zkServer.sh restart
PIDFile=/var/lib/zookeeper/data/zookeeper_server.pid
Restart=on-failure
RestartSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now zookeeper

# Verificar el estado del servidor
/opt/zookeeper/bin/zkServer.sh status
# Debe mostrar: Mode: leader (en el líder) o Mode: follower

Operaciones con znodes

El shell de ZooKeeper permite administrar el árbol de znodes:

# Conectar al shell de ZooKeeper
zkCli.sh -server localhost:2181

# Dentro del shell (las siguientes líneas son comandos de zkCli):
# Listar el directorio raíz
ls /

# Crear un znode persistente
create /config ""
create /config/app "configuracion-inicial"

# Crear un znode efímero (desaparece cuando el cliente se desconecta)
create -e /workers/worker-01 "10.0.2.10:8080"

# Crear un znode secuencial (útil para colas y elección de líder)
create -s /tasks/task- "datos-tarea"

# Leer el contenido de un znode
get /config/app

# Actualizar el valor de un znode
set /config/app "nueva-configuracion"

# Observar cambios en un znode (watch)
get -w /config/app
ls -w /workers

# Eliminar un znode (debe estar vacío)
delete /config/app

# Eliminar recursivamente
deleteall /config

Desde la línea de comandos del sistema:

# Verificar la conectividad (usando el comando four-letter word "ruok")
echo "ruok" | nc localhost 2181
# Respuesta esperada: imok

# Ver estadísticas del servidor
echo "stat" | nc localhost 2181

# Ver la configuración del ensemble
echo "conf" | nc localhost 2181

# Ver las conexiones activas de clientes
echo "dump" | nc localhost 2181

Configuración de ACLs

ZooKeeper soporta ACLs por znode para control de acceso:

# Conectar al shell con autenticación
zkCli.sh -server localhost:2181

# Dentro del shell de ZooKeeper:
# Añadir autenticación de tipo "digest" (usuario:password)
addauth digest admin:password_seguro

# Crear un znode con ACL restringida
create /secretos "" digest:admin:$(echo -n "admin:password_seguro" | sha1sum | cut -d' ' -f1):rwcda

# Permisos disponibles: r(read), w(write), c(create), d(delete), a(admin)
# ACL "world:anyone:r" permite lectura a todos
create /publico "dato-publico" world:anyone:r

# Ver las ACLs de un znode
getAcl /secretos

Monitorización y Métricas

# Verificar métricas de Prometheus (si está habilitado en zoo.cfg)
curl -s http://localhost:7070/metrics | grep -E "^zookeeper_"

# Comandos four-letter para monitorización básica
echo "mntr" | nc localhost 2181  # Métricas detalladas del servidor
echo "srvr" | nc localhost 2181  # Información del servidor
echo "stat" | nc localhost 2181  # Estadísticas de conexiones

# Métricas clave a vigilar:
# zk_outstanding_requests  - Solicitudes en cola (debe ser bajo)
# zk_avg_latency           - Latencia media (debe ser < 10ms)
# zk_max_latency           - Latencia máxima
# zk_watch_count           - Número de watches activos
# zk_followers             - Número de followers (debe ser count-1)

# Script de monitorización básica
cat > /usr/local/bin/zk-check.sh << 'EOF'
#!/bin/bash
# Verificar el estado de ZooKeeper y alertar si hay problemas
ZK_HOST="localhost"
ZK_PORT="2181"

STATUS=$(echo "ruok" | nc -w 3 $ZK_HOST $ZK_PORT 2>/dev/null)
if [ "$STATUS" != "imok" ]; then
    echo "ALERTA: ZooKeeper no responde en $ZK_HOST:$ZK_PORT"
    exit 1
fi

MODE=$(echo "stat" | nc -w 3 $ZK_HOST $ZK_PORT | grep "Mode:" | awk '{print $2}')
echo "ZooKeeper funcionando. Modo: $MODE"
EOF
chmod +x /usr/local/bin/zk-check.sh

Integración con Kafka

# Kafka usa ZooKeeper para gestión de brokers, tópicos y coordinación
# En el archivo server.properties de Kafka:
# zookeeper.connect=zk-01:2181,zk-02:2181,zk-03:2181/kafka

# Los metadatos de Kafka se almacenan bajo /kafka en ZooKeeper
# Verificar que Kafka puede conectarse a ZooKeeper
zkCli.sh -server localhost:2181 ls /kafka
# Debe mostrar: [admin, brokers, cluster, config, consumers, controller, ...]

# Ver los brokers registrados en ZooKeeper
zkCli.sh -server localhost:2181 ls /kafka/brokers/ids

# Nota: Las versiones modernas de Kafka (3.x) pueden operar sin ZooKeeper
# usando KRaft (Kafka Raft Metadata mode) - considera migrar si usas Kafka 3.3+

Solución de Problemas

# Ver logs de ZooKeeper
tail -f /var/log/zookeeper/zookeeper.log

# El ensemble no puede elegir líder
# Verificar que el puerto 3888 está accesible entre todos los nodos
nc -zv zk-02.empresa.local 3888
nc -zv zk-03.empresa.local 3888

# Verificar el contenido del archivo myid
cat /var/lib/zookeeper/data/myid

# Limpiar snapshots y logs de transacción (CUIDADO: solo si es necesario)
# Primero detener ZooKeeper
systemctl stop zookeeper
# Usar la herramienta de limpieza integrada
java -cp /opt/zookeeper/lib/*:/opt/zookeeper/*.jar \
  org.apache.zookeeper.server.PurgeTxnLog \
  /var/lib/zookeeper/data /var/lib/zookeeper/data -n 5

# Comprobar la integridad del snapshot
java -cp /opt/zookeeper/lib/*:/opt/zookeeper/*.jar \
  org.apache.zookeeper.server.SnapshotFormatter \
  /var/lib/zookeeper/data/version-2/snapshot.0 | head -50

Conclusión

Apache ZooKeeper es una pieza fundamental en la infraestructura de sistemas distribuidos, proporcionando las primitivas de coordinación que permiten a Kafka, HBase y otros sistemas mantener consistencia y elegir líderes de forma confiable. Su configuración como ensemble de 3 o 5 nodos garantiza alta disponibilidad, y su monitorización activa mediante los comandos four-letter y métricas Prometheus es esencial para operación estable en producción.