Zookeeper – Installation et bonnes pratiques

Zookeeper logo

Lors de l’installation d’un Cluster Solrcloud, il est nécessaire de disposer d’un ensemble Zookeeper opérationnel. On appelle ensemble Zookeeper, un groupe de serveurs Zookeeper qui fonctionnent de concert (« ensemble » donc).

Dans cet article qui décrit l’installation d’un ensemble Zookeeper, sont également abordées les bonnes pratiques qui doivent être impérativement suivies.

Nombre de serveurs dans l’ensemble

La réponse rapide est au minimum trois et idéalement cinq.

Le but est de fournir un service hautement disponible ou l’arrêt pour panne ou maintenance d’un des serveurs de l’ensemble ne perturbe pas le service. Le principe de base pour qu’un ensemble fonctionne est que la moitié plus un des serveurs qui le composent soient disponibles, c’est ce l’on appelle disposer du quorum. Un ensemble de trois serveurs permet donc l’indisponibilité d’un serveur, alors qu’un ensemble de cinq serveurs permet l’indisponibilité de deux serveurs soit la possibilité de stopper un serveur pour une maintenance ou une mise à jour tout en pouvant accepter la perte d’un second serveur pour cause de panne.

Pré-requis

Logiciel

Zookeeper est une application Java. Le seul vrai pré-requis est de disposer d’un environnement Java. Nous préconisons les versions Oracle ou OpenJDK 1.8 ou 1.9.

Hardware

Zookeeper ne requiert pas des serveurs très puissants, les caractéristiques de base conseillées afin de supporter une grande majorité de cas d’usages sont généralement ceux indiqués ci-dessous. Il va de soi que selon l’usage réel de SolrCloud, le monitoring des serveurs Zookeeper (CPU, espace disque, JVM, …) doit amener à adapter ces caractéristiques.

  • Processeurs quad-core
  • 2 Go de RAM
  • 100 Go d’espace disque
  • des I/O disques très performantes en écriture
  • un réseau gigabits très performant avec une très faible latence entre les membres de l’ensemble Zookeeper et les noeuds Solr

Bonnes pratiques

L’ensemble Zookeeper est un point critique de la stabilité et des performances de SolrCloud. Pour atteindre cette objectif, les bonnes pratiques impératives sont :

  • Utilisé des serveurs dédiés
  • Désactiver le swap des serveurs (vm.swappiness=1)
  • Augmenter la limite « nofile » à 8192 (1024 par défaut) dans « /etc/security/limits.conf »
  • Rendre Zookeeper indépendant des DNS en utilisant le fichier « /etc/hosts »
  • Allouer à la mémoire heap de Zookeeper au maximum la quantité de mémoire disponible moins 1 Go (Xmx=1g pour 2 Go de RAM disponible). Dans la pratique une Heap de 512 Mo doit couvrir les besoins. Il faut monitorer la JVM et ses GC afin de valider la bonne taille de heap.
  • Utiliser des volumes disques distincts pour les données et les logs de transaction (paramètres dataDir et dataLogDir dans zoo.cfg)
  • Activer les logs de GC de la JVM et les analyser régulièrement avec le site gceasy.io
  • Installer des outils de monitoring du système (CPU, I/O disques, …) afin des contrôler le bon fonctionnement et les performances du système. Nous préconisons « sar » qui fait parti du package sysstat

I/O disque

Il est nécessaire d’expliquer pourquoi il est demandé de disposer d’I/O disque très performants en écriture et d’utiliser des volumes distincts pour le stockage des données et des logs de transaction de Zookeeper. Zookeeper réponds à des requêtes de ses clients (les noeuds Solr). Systématiquement, avant de retourner une réponse, Zookeeper synchronise les transactions sur disque. On comprend alors que si ces écritures sont peu rapides, elles deviennent le goulet d’étranglement qui va provoquer des temps de réponses insuffisants et conduire à une instabilité de SolrCloud.

Utiliser des disques SSD est-il conseillé pour le stockage des logs de transaction ?

Tout d’abord, il faut savoir de quoi on parle. S’agit-il d’un datastore d’un système virtualisé construit sur des disques SSD ou de disques SSD directement et physiquement rattachés au serveur. Dans le premier cas, le gain n’est malheureusement pas forcément significatif. Dans le second cas, le gain doit être très significatif et utiliser des disques SSD pourrait sembler être un bon choix. Malheureusement, les disques SSD très performants en lecture peuvent être pénalisés par ponctuellement des latences importantes en écriture, hors ce sont les performances en écriture qui sont critiques pour Zookeeper ! Pour cette raison et uniquement si on a la certitude de ne pas être victime (même très ponctuellement) de problèmes de latence en écriture, l’usage de disque SSD est possible, sinon l’usage de disques SSD est déconseillé !

Installation de Zookeeper

Il s’agit d’une procédure d’installation sur la base de 5 membres.

Cette opération est a répéter sur chaque serveur de l’ensemble.

  • créer un utilisateur zookeeper
$ sudo useradd zookeeper
  • mettre en place le fichier /etc/hosts avec le contenu suivant. Le but est d’être totalement indépendant des DNS (pannes ou configuration)
xxx.xxx.xxx.xxa zk1
xxx.xxx.xxx.xxb zk2 
xxx.xxx.xxx.xxc zk3 
xxx.xxx.xxx.xxd zk4 
xxx.xxx.xxx.xxe zk5 
  • extraire l’archive dans /opt/zookeeper-3.x.x et créer un lien symbolique générique “/opt/zookeeper” indépendant de la version. Les mises à jours seront plus faciles

En version 3.4.x

$ cd /opt
$ sudo tar xzf zookeeper-3.4.x.tar.gz
$ sudo ln -s zookeeper-3.4.x zookeeper
$ sudo chown -R zookeeper: zookeeper-3.4.x

En version 3.5.x

$ cd /opt
$ sudo tar xzf apache-zookeeper-3.5.x-bin.tar.gz
$ sudo ln -s apache-zookeeper-3.5.x-bin zookeeper
$ sudo chown -R zookeeper: apache-zookeeper-3.5.x-bin
  • créer des répertoires pour les données, les logs de transactions et le logs de Zookeeper sur des volumes si possible distincts
$ sudo mkdir /<volume_dedie_aux_donnees>/zookeeper-data
$ sudo mkdir /<volume_dedie_aux_logs_de_transaction>/zookeeper-data-log 
$ sudo mkdir /var/log/zookeeper
$ sudo chown -R zookeeper: /<volume_dedie_aux_donnees>/zookeeper-data /<volume_dedie_aux_logs_de_transaction>/zookeeper-data-log /var/log/zookeeper
  • créer le fichier de configuration “/opt/zookeeper/conf/zoo.cfg” avec le contenu suivant. Il s’agit des paramètres par défaut conseillés avec notamment la distinction des volumes pour les données et les logs de transaction et également l’activation de la purge automatique des données.
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/<volume_dedie_aux_donnees>/zookeeper-data/
dataLogDir=/<volume_dedie_aux_logs_de_transaction>/zookeeper-data-log/ 
clientPort=2181
autopurge.snapRetainCount=10
autopurge.purgeInterval=24
server.1=zk1:2888:3888
server.2=zk2:2888:3888
server.3=zk3:2888:3888
server.4=zk4:2888:3888
server.5=zk5:2888:3888
  • Créer un fichiers myid dans le répertoire data. Ce fichier ne contient uniquement une ligne qui est le numéro de l’instance Zookeeper qui utilise ce répertoire de données (un chiffre de 1 à 255).
cd /opt
echo 1 > /<volume_dedie_aux_donnees>/zookeeper-data/myid

L’id 1 est à remplacer par 2, 3, … n sur chacun des serveurs

  • mettre en place le fichier d’initialisation “/opt/zookeeper/conf/zookeeper-env.sh” avec le contenu suivant. Contrairement à zoo.cfg, ce fichier est relatif à la configuration de la JVM et des logs. Il permet également de pallier à un problème de conservation des logs de la JVM lors des redémarrages. Les logs des JVM sont en rotation avec une taille de 20 Mo par fichier et un historique de 10 fichiers. Ces valeurs peuvent être adaptées.
    Si une version de JVM supérieur a 9 et détectée, le garbadge collectore choisi est le G1GC.
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]];  then
    JAVA="$JAVA_HOME/bin/java"
elif type -p java; then
    JAVA=java
else
    echo "Error: JAVA_HOME is not set and java could not be found in PATH." 1>&2
    exit 1
fi
JAVA_VER=$("$JAVA" -version 2>&1)
JAVA_VER_NUM=$(echo $JAVA_VER | head -1 | awk -F '"' '/version/ {print $2}' | sed -e's/^1\.//' | sed -e's/[._-].*$//')
echo "Java version $JAVA_VER_NUM"

# Configure Log 
ZOO_LOG_DIR="/opt/zookeeper/log" 
ZOO_LOG4J_PROP="WARN, ROLLINGFILE" 
ZOO_GC_LOG_DIR="$ZOO_LOG_DIR" 

# Configure mémoire JVM selon la mémoire disponible
SERVER_JVMFLAGS="-Xmx512m"

# Configure JMX (enabled by default for local monitoring by PID)
JMXDISABLE=true
JMXPORT=10900

# Configure JVM GC and log 
if [[ "$JAVA_VER_NUM" -lt "9" ]] ; then
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -verbose:gc -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps" 
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime"
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -Xloggc:$ZOO_GC_LOG_DIR/zookeeper-gc.log" 
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=20M" 
else
	# Use G1GC
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
	#SERVER_JVMFLAGS="-XX:+PerfDisableSharedMem -XX:+ParallelRefProcEnabled -XX:+UseLargePages -XX:+AlwaysPreTouch"
	SERVER_JVMFLAGS="$SERVER_JVMFLAGS -Xlog:gc*:file=$ZOO_GC_LOG_DIR/zookeeper-gc.log:time,uptime:filecount=10,filesize=20M"
fi

# Backup des logs GC au démarrage (sinon ils sont perdus)
if [ "x$1" = "xstart" ]; then
    for f in $ZOO_GC_LOG_DIR/zookeeper-gc.log*; do
        ## Check if the glob gets expanded to existing files.
        ## If not, f here will be exactly the pattern above
        ## and the exists test will evaluate to false.
        if [ -e "$f" ] ; then
            echo "GC log files found - backing up"
            d=$PWD && cd $ZOO_GC_LOG_DIR && tar czf zookeeper-gc.$(date +%Y%m%d-%H%M%S).tgz zookeeper-gc.log* && cd $d
        else
            echo "No GC log files found"
        fi
        ## This is all we needed to know, so we can break after the first iteration
        break
    done
fi
  • Mettre en place le fichier de configuration des logs “/opt/zookeeper/conf/log4j.properties” avec le contenu suivant. Les logs sont en rotation avec une taille de 20 Mo par fichier et un historique de 10 fichiers. Ces valeurs peuvent être adaptées.
# Define some default values that can be overridden by system properties 
zookeeper.root.logger=INFO, LOGFILE 
zookeeper.console.threshold=INFO 
zookeeper.log.dir=. 
zookeeper.log.file=zookeeper.log 
zookeeper.log.threshold=DEBUG 
zookeeper.tracelog.dir=. 
zookeeper.tracelog.file=zookeeper_trace.log 

# 
# ZooKeeper Logging Configuration 
# 

# Format is " (, )+ 
  
# DEFAULT: console appender only 
log4j.rootLogger=${zookeeper.root.logger} 

# 
# Log to the console 
# 
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold} 
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n 

# Add ROLLINGFILE to rootLogger to get log file output
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
log4j.appender.ROLLINGFILE.MaxFileSize=20MB
log4j.appender.ROLLINGFILE.MaxBackupIndex=10
log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
  • repositionner le bon propriétaire des fichiers de configuration
$ sudo chown -R zookeeper: /opt/zookeeper/conf

Script de démarrage

  • Mettre en place le script de démarrage “/etc/init.d/zookeeper” avec le contenu suivant
#!/bin/sh

# Purpose: This script starts and stops the Zookeeper daemon
# chkconfig: - 90 10
# description: Zookeeper daemon

APP=/opt/zookeeper/bin/zkServer.sh
USER=zookeeper

app(){
	su - $USER -c "$APP $1"
}

error(){
	echo -e "Error: Parameter non valide !"
	echo -e "Usage: $0 {start | stop | restart | status}"
	exit 1
}

usage(){
	echo -e "Usage: $0 {start | stop | restart | status}"
	echo ""
}

start(){
	echo -e "Starting Zookeeper"
	app start
	echo -e "Done"
}

stop(){
    echo -e "Stopping Zookeeper"
    app stop
	echo -e "Done"
}

restart(){
	echo -e "Restarting Zookeeper"
	app stop
	sleep 5
	app start
	echo -e "Done"
}

status(){
	echo -e "Zookeeper status"
	app status
}

case "$1" in
	start)  
		start 
		;;
	stop)   
		stop 
		;;
	status) 
		status
		;;
	restart) 
		restart
		;;
	help) 
		usage 
		;;
	*) 
		error 
		;;
esac
exit 0
  • Démarrer chaque serveur de l’ensemble
$ sudo chmod +x /etc/init.d/zookeeper
$ sudo /etc/init.d/zookeeper start
  • Mettre en démarrage automatique

Sous Centos :

chkconfig zookeeper on

Sous Debian :

update-rc.d zookeeper defaults

Vous souhaitez bénéficier d’une expertise Solr ou intégrer une ressource ponctuelle à vos projets ? Rendez vous sur la page Contact