Blog

Installation de HAProxy pour un cluster MySQL utilisant Galera

Écrit le 02 05 2013 par Kévin MET _

Fonctionnement

Lorsque vous avez mis en place un cluster MySQL utilisant Galera (cela peut être un cluster percona, MariaDB ou MySQL community) vous pouvez configurer vos applications pour écrire et lire sur chacun de vos nœuds mais dans la plupart des applications web vous ne pouvez indiquer l'adresse que d'une seule base de données. HAProxy va donc vous permettre de faire du load-balancing sur tous vos nodes en conservant une seule ip (celle du serveur HAProxy) pour effectuer vos connexions.

HAProxy permet donc de faire du load-balancing en vérifiant que les nœuds sur lesquels il envoie les requête sont disponibles. Pour cela nous allons utiliser xinetd ainsi que des scripts bash pour surveiller que les nodes sont bien UP.

Installation

Dans notre exemple nous utlisons les machines suivantes :

  • 192.168.1.1 node1.mnt-tech.fr : Percona XtraDB Cluster
  • 192.168.1.2 node2.mnt-tech.fr : Percona XtraDB Cluster
  • 192.168.1.3 node3.mnt-tech.fr : Percona XtraDB Cluster
  • 192.168.1.4 node4.mnt-tech.fr : HAProxy

Dans cette configuration tous les requêtes seront envoyées sur 192.168.1.4 qui les transmettra sur les 3 nœuds Percona. Pour l'installation d'un cluster MySQL Percona je vous renvoie à ce tutoriel sur le sujet. Nous démarrons donc notre installation sur 192.168.1.4 qui porte HAProxy.


# apt-get install haproxy

Une fois que c'est installé, il faut le configurer. Je vous balance direct la config et je vous explique ensuite :


# cat /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local0
maxconn 4096
user haproxy
group haproxy
daemon
 
defaults
log global
mode http
option tcplog
option dontlognull
retries 3
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
 
frontend pxc-front
bind *:3306
mode tcp
default_backend pxc-back
 
frontend stats-front
bind *:8282
mode http
default_backend stats-back
 
frontend pxc-onenode-front
bind *:3307
mode tcp
default_backend pxc-onenode-back
 
backend pxc-back
mode tcp
balance leastconn
option httpchk
server node5 192.168.1.1:3306 check port 9200 inter 12000 rise 3 fall 3
server node6 192.168.1.2:3306 check port 9200 inter 12000 rise 3 fall 3
server node7 192.168.1.3:3306 check port 9200 inter 12000 rise 3 fall 3
 
backend stats-back
mode http
option httpchk
balance roundrobin
stats uri /
stats auth pxcstats:secret
stats refresh 10s
 
backend pxc-onenode-back
mode tcp
balance leastconn
option httpchk
server node5 192.168.1.1:3306 check port 9200 inter 12000 rise 3 fall 3
server node6 192.168.1.2:3306 check port 9200 inter 12000 rise 3 fall 3 backup

Comme vous pouvez le constater, tout est assez explicite dans cette configuration. Je ne vais donc pas rentrer dans les détails car le manuel est très bien détaillé. On utilise balance leastconn qui permet de garder un équilibrage du nombre de connexion. Chaque nouvelle connexion sera effectuée sur le nœud ayant le moins de connexions actives. On peut remarquer check port 9200 qui indique que l'on va tester l'état des nœuds via le port 9200 (ixnetd). Nous avons configuré deux connexions :

  • Une qui écoute sur 3306 et qui renvoie sur les trois nœuds
  • Une autre sur 3307 qui renvoie uniquement sur un nœud avec un deuxième nœud en backup

Enfin, on a ajouté une partie "gui" stats-back et stats-front qui nous permettra en nous connectant sur http://192.168.1.4:8282 d'avoir une belle interface avec l'état de nos nœuds. Nous allons passer à la configuration de xinetd sur les nodes percona.

On commence par installer xinetd :


apt-get install xinetd

Ensuite on édite /etc/xinetd.d/mysqlchk qui doit déjà être créé car il est dans les dépots de percona :


vim /etc/xinetd.d/mysqlchk

# default: on
# description: mysqlchk
service mysqlchk
{
# this is a config for xinetd, place it in /etc/xinetd.d/
        disable = no
        flags           = REUSE
        socket_type     = stream
        port            = 9200
        wait            = no
        user            = nobody
        server          = /usr/bin/clustercheck
        log_on_failure  += USERID
        only_from       = 0.0.0.0/0
        # recommended to put the IPs that need
        # to connect exclusively (security purposes)
        per_source      = UNLIMITED
}

On ajoute le port dans /etc/services :


mysqlchk        9200/tcp                        # mysqlchk

Il ne reste plus qu'à modifier le script /usr/bin/clustercheck en changeant les valeurs de MYSQL_USERNAME et MYSQL_PASSWORD pour les adapter à votre situation :


#!/bin/bash
#
# Script to make a proxy (ie HAProxy) capable of monitoring Percona XtraDB Cluster nodes properly
#
# Author: Olaf van Zandwijk <olaf.vanzandwijk@nedap.com>
# Documentation and download: https://github.com/olafz/percona-clustercheck
#
# Based on the original script from Unai Rodriguez
#

if [[ $1 == '-h' || $1 == '--help' ]];then
    echo "Usage: $0 <user> <pass> <available_when_donor=0|1> <log_file>"
    exit
fi

MYSQL_USERNAME="mysql"
MYSQL_PASSWORD="password"
AVAILABLE_WHEN_DONOR=${3:-0}
ERR_FILE="${4:-/dev/null}"
#Timeout exists for instances where mysqld may be hung
TIMEOUT=10

#
# Perform the query to check the wsrep_local_state
#
WSREP_STATUS=`mysql -nNE --connect-timeout=$TIMEOUT --user=${MYSQL_USERNAME} --password=${MYSQL_PASSWORD} \
-e "SHOW STATUS LIKE 'wsrep_local_state';" 2>${ERR_FILE} | tail -1 2>>${ERR_FILE}`

if [[ "${WSREP_STATUS}" == "4" ]] || [[ "${WSREP_STATUS}" == "2" && ${AVAILABLE_WHEN_DONOR} == 1 ]]
then
    # Percona XtraDB Cluster node local state is 'Synced' => return HTTP 200
    # Shell return-code is 0
    echo -en "HTTP/1.1 200 OK\r\n"
    echo -en "Content-Type: text/plain\r\n"
    echo -en "Connection: close\r\n"
    echo -en "Content-Length: 40\r\n"
    echo -en "\r\n"
    echo -en "Percona XtraDB Cluster Node is synced.\r\n"
    exit 0
else
    # Percona XtraDB Cluster node local state is not 'Synced' => return HTTP 503
    # Shell return-code is 1
    echo -en "HTTP/1.1 503 Service Unavailable\r\n"
    echo -en "Content-Type: text/plain\r\n"
    echo -en "Connection: close\r\n"
    echo -en "Content-Length: 44\r\n"
    echo -en "\r\n"
    echo -en "Percona XtraDB Cluster Node is not synced.\r\n"
    exit 1
fi

Vous devez faire cela sur chacun des nodes percona. Et ensuite relancer xinetd pour prendre en compte les modifications.


/etc/init.d/xinetd restart

Il faut ensuite "activer" haproxy en éditant le fichier /etc/default/haproxy et en changer la valeur de ENABLED pour la mettre à 1


ENABLED=1

Il ne vous reste plus qu'à démarrer HAProxy :


/etc/init.d/haproxy start

Et voilà, vous avez une configuration basique mais fonctionnelle pour faire du load-balancing entre vos différents nœuds. Vous pouvez lancer un sysbench sur HAProxy et vous amusez à faire tomber un des nodes pour voir le comportement de HAProxy. Le mieux est de voir le résultat graphiquement directement dans l'interface de statistiques. (sur le port 8282 dans notre exemple)

Informations complémentaires

Pour activer les logs de HAproxy il faut modifier la conf de rsyslog, en effet tous les logs sont envoyés sur le rsyslog local via la local0. Nous allons donc ajouter ces lignes dans /etc/rsyslog.conf :


$ModLoad imudp
$UDPServerAddress 127.0.0.1
$UDPServerRun 514

# HAproxy
local0.* -/var/log/haproxy.log
& ~

.Le & ~ permet de spécifier que tout ce qui match avec la ligne précédente ne doit pas terminer dans une autre règle qui pourrait matcher avec cette règle. On indique également à rsyslog d'écouter uniquement sur 127.0.0.1 pour éviter qu'il écoute les requêtes en provenance d'internet. Il ne faut pas oublier de relancer rsyslog


/etc/init.d/rsyslog restart

Voici également un petit script logrotate qui garde 365 jours de logs et qui tournent une fois par nuit à mettre dans /etc/logrotate.d/haproxy


/var/log/haproxy.log {
        copytruncate
        daily
        dateext
        missingok
        rotate 365
        compress
        notifempty
}

Il y a également quelques petits truc coté système qui peuvent s'avérer utile. En effet, la façon dont un client MySQL ferme sa connexion vers le serveur n'est pas propre. Du coup la connexion reste en TIME_WAIT pendant un certain temps. Sur un HAproxy très chargé on peut donc vite arriver à saturer le nombre de ports utilisables pour une même adresse IP. Avant de faire ce genre de manip il vaut mieux vérifier que l'on utilise un grand nombre de ports avec netstat par exemple :


netstat -ntulape | grep -c 3306

Si le nombre est supérieur à 15000 ou 20000 avec beaucoup de socket en TIME_WAIT, alors il faut envisager les manips suivantes. Par défaut sur une debian le nombre de ports disponibles est de 28232. Pour le vérifier :


cat /proc/sys/net/ipv4/ip_local_port_range 
32768	61000

On va donc commencer par augmenter cette plage de port pour en avoir 64000 de disponible :


sysctl net.ipv4.ip_local_port_range="1025 65000"

On oublie pas de mettre à jour /etc/sysctl.conf pour que la modification soit fonctionnelle au prochain reboot :


net.ipv4.ip_local_port_range="1025 65000"

On peut ensuite activer net.ipv4.tcp_tw_reuse qui permet de réutiliser les sockets qui sont en état TIME_WAIT


sysctl net.ipv4.tcp_tw_reuse=1

Sinon on peut essayer de jouer sur la valeur de net.ipv4.tcp_fin_timeout en la passant à 30 secondes par exemple :


sysctl net.ipv4.tcp_fin_timeout

Il ne faut pas oublier d'utiliser net.ipv4.tcp_tw_reuse sur les serveurs web et MySQL si ils en ont également besoin.

On peut ajouter à cette liste le nombre de fichier q'un utilisateur peut ouvrir simultanément. Dernièrement en faisant quelques benchs avec percona-playback j'ai été confronté à ce problème. J'essayais de rejouer des logs avec 2000 threads et j'obtenais cette erreur : Can't create TCP/IP socket (24). Cela venait du nombre de fichiers que root pouvait ouvrir simultanément qui est par défaut à 1024. On vérifie ça avec :


ulimit -n
1024

Et on change cette valeur en faisant :


ulimit -n 8192

On n'oublie pas de modifier également le fichier /etc/security/limits.conf pour que la modification soit de nouveau effective après un reboot.

♥ Partage sur tes réseaux sociaux ♥
Kévin MET
Kévin MET

Auteur de ce blog et gérant de la société MNT-TECH, je publie sur ce blog lorsque le temps me le permet et lorsqu'un sujet qui me parait intéressant n'a pas encore été abordé en français. Toutes les informations techniques présentes sur cette page peuvent être réutilisées moyennant le fait de citer la source.