MySQL Master failover cluster with Hast & Heartbeat

Suite à la réalisation d’un Proof-of-Concept (PoC) en entreprise sur une infrastructure MySQL en mode cluster (master résilient, multi slave), je vais vous présenter ici l’infrastructure retenue et sa configuration.

Notre configuration se base sur deux serveurs pouvant faire office de maître et un pool de slaves dont le nombre n’est pas fixe. Cette configuration est plutôt réservée pour des applicatifs supportant la gestion de serveurs d’écriture et de serveurs de lecture. Néanmoins vous pouvez exclure la partie esclave si seul un maître redondant vous convient.

Pour la partie maître, l’un des serveur est actif, l’autre en stand-by. Si le serveur actif tombe, l’autre serveur prend le relai et dessert le service MySQL maître.

Pour la partie eslave, ceux-ci répliquent leurs données depuis le maître actif en se basant sur une adresse IP virtuelle.Voici le schéma d’architecture de la réplication des données

Schéma infra MySQL nominaleAu niveau composants logiciels, la solution utilise:

  • HAST (ou DRBD pour les Linuxiens) en mode Primary/Secondary en réplication sécurisée (synchronisation des deux noeuds obligatoire)
  • Heartbeat pour la gestion de bascule du service MySQL maître, de la réplication de disques et de l’IP virtuelle
  • MySQL 5.6 pour ses GTID, permettant d’avoir une réplication MySQL master/slave beaucoup plus fiable dans le cas d’une panne avec retour de maître (là où les versions précédentes nécessitaient des interventions afin de reprendre la réplication sur les slaves

Au niveau hardware il vous faudra:

  • Pour les maîtres: deux serveurs physiques ou virtuels avec 2 disques (ou 1 disque avec une partition dédiée à la réplication HAST/DRBD) ayant chacun deux cartes réseau.
  • Pour les esclaves: des serveurs physiques, virtuels ou conteneurs

Topologie

Afin de bien se repérer, voici la topologie réseau de nos serveurs

  • 192.168.150.1: serveur MySQL maître primaire
  • 192.168.150.2: serveur MySQL maître secondaire
  • 192.168.150.99: adresse IP partagée par les deux maîtres
  • 192.168.150.21: serveur MySQL esclave
  • 10.255.255.1: serveur MySQL maître primaire (câble croisé dédié entre les deux noeuds)
  • 10.255.255.2: serveur MySQL maître secondaire (câble croisé dédié entre les deux noeuds)

Schéma infra MySQL - Network

Configuration des maîtres

Réplication HAST

Dans un premier temps nous allons configurer la réplication de disques HAST. Créez le fichier hast.conf suivant sur chacun de vos serveurs MySQL maîtres:

resource mymasters {
    checksum sha256

    on master1 {
        local /dev/da1
        remote 10.255.255.2
    }

    on master2 {
        local /dev/da1
        remote 10.255.255.1
    }
}

Dans un premier temps on définit une ressource HAST nommée mymasters. Celle-ci contient deux noeuds: master1 et master2, noms d’hôtes respectifs de chaque machine. On définit également l’utilisation d’un checksum en sha256 afin de vérifier l’intégrité des blocs.

Pour chaque hôte, on définit le disque local à répliquer et l’adresse IP du noeud sur lequel répliquer.

On active ensuite le service hastd et on le lance

sysrc hastd_enable=YES
service hastd start

Lors du démarrage du service hast les volumes sont en état init. Cela peut se vérifier avec la commande hastctl status.

Name    Status     Role        Components
mymasters    -        init           /dev/da1    10.255.255.2

Tapez ensuite la commande suivante afin d’initialiser les disques pour HAST sur chaque serveur:

hastctl create mymasters

Il convient alors de définir un noeud primaire maître et un noeud secondaire. Sur le primaire entrez la commande suivante:

hastctl role primary mymasters

Et sur le secondaire:

hastctl role secondary mymasters

Le volume va se retrouver dans l’état suivant:

Name    Status     Role        Components
mymasters    complete primary        /dev/da1    10.255.255.2

Il faut maintenant laisser le temps aux deux volumes de se synchroniser. Vérifiez le champ dirty sur les deux noeuds afin de vérifier l’état d’avancement. Le volume HAST ne sera pas présenté sur le secondaire lors de son passante en primaire si le champ dirty contient des données à synchroniser lors de la synchronisation initiale.

Configuration de Heartbeat

Sur les deux serveurs maîtres installer tout d’abord le port sysutils/heartbeat (ou le paquet heartbeat). Les options de compilation par défaut sont suffisantes.

Dans un premier temps créez le fichier /usr/local/etc/ha.d/ha.cf, contenant la configuration globale de Heartbeat, suivant:

logfile /var/log/ha.log
logfacility local0
node DB1 DB2

auto_failback off
keepalive 1
deadtime 3
initdead 10
bcast bce1
udpport 694

deadping 3
ping 192.168.150.250
respawn hacluster /usr/local/lib/heartbeat/ipfail
apiauth ipfail gid=haclient uid=hacluster

On applique ici la stratégie suivante:

  • Les serveurs se contactent en broadcast sur l’interface réseau bce1, sur le port 694 (bcast et udpport). Ils s’annoncent toutes les secondes (keepalive).
  • Si au bout de 3 secondes le noeud primaire ne répond plus le noeud secondaire prend le relai (deadtime)
  • Lors du démarrage du service, si le noeud ne retrouve pas de noeud primaire, il le devient au bout de 10 secondes
  • Afin de gérer le cas de la panne de l’interface réseau MySQL, on vérifie la disponibilité de la route par défaut (ping). Si au bout de 3 secondes elles n’est pas disponible (deadping) le noeud primaire va demander au noeud secondaire si il arrive à la joindre. Si ce dernier arrive à la joindre, une bascule sera effectuée. (respawn, apiauth ipfail)

Il faut maintenant configurer la stratégie de noeud heartbeat. Créez le fichier /usr/local/etc/ha.cf/haresources et insérez le contenu suivant

DB1 hastdev::mymasters 192.168.150.99 MailTo::admin@example.org

On déclare ici le noeud primaire par défaut DB1 qui devra lancer les ressources suivantes dans l’ordre:

  • Application de la stratégie hastdev pour notre volume HAST mymasters, ajout de l’adresse IP 192.168.150.99 à la machine et enfin notification de l’administrateur à l’adresse admin@example.org

Lors de la bascule (bascule forcée proprement), les tâches s’exécuteront dans l’ordre inverse.

Note: le noeud primaire ne reprendra pas la main s’il y a déjà un maître présent (directive initdead)

Puis on créée la clef d’authentification heartbeat entre les noeuds dans le fichier /usr/local/etc/ha.cf/authkeys

auth 1
1 sha1 mysqld-ha

Spécifiez ensuite les droits 600 sur le fichier, auquel cas heartbeat refusera de fonctionner:

chmod 600  /usr/local/etc/ha.cf/authkeys

Créez ensuite le fichier de ressource /usr/local/etc/ha.d/resources.d/hastdev avec le contenu suivant puis attribuez lui les droits d’écriture (chmod +x)

#!/bin/sh
#
#
# License:      BSD
#    usage: $0  {start|stop}
#

usage() {
    echo "usage: $0 $LEGAL_ACTIONS"
    exit 1
}

start() {
    hastctl role primary $1
    while [ ! -e /dev/hast/$1 ];
        do
                sleep 1;
        done
    fsck_ufs -y /dev/hast/$1
    mount -o sync,noatime /dev/hast/$1 /mnt/$1
    if [ $? -ne 0 ]; then
        echo "Failed to mount /dev/hast/$1 (code $?)"
        return 1;
    fi
    service mysql-server onestart
    return 0
}

stop() {
    service mysql-server onestop
    umount /dev/hast/$1
    sleep 1;
    hastctl role secondary $1
    return 0
}

case $2 in
    start)
        start $1
        ;;
    stop)
        stop $1
        ;;
    *)
        usage
        exit 1
        ;;
esac

exit $?

Ce script heartbeat va se charger de basculer et monter le volume HAST précédemment créé et de démarrer le service MySQL. Il faut noter que le volume HAST est monté avec l’option sync garantissant l’écriture sur le disque, permettant de ne pas perdre de données en cas de bascule suite à une panne.

Note: si vous souhaitez l’utiliser sous Linux, remplacez onestart/onestop par start et stop et désactivez le service au niveau système.

Configuration de MySQL

La couche basse de notre cluster de maîtres MySQL étant maintenant rodée, il nous faut installer et configurer MySQL. Nous aurons ici besoin de MySQL 5.6 ou supérieur puisque nous allons utiliser les GTID. Installez le depuis les packages ou les ports (database/mysql56-server), suivant votre préférence, sur les deux serveurs maîtres.

pkg install mysql56-server

Créez le répertoire /mnt/mymasters et configurez MySQL au niveau du système afin qu’il utilise ce répertoire, correspondant au point de montage HAST

mkdir -p /mnt/mymasters
sysrc mysql_dbdir="/mnt/mymasters"

Créez ensuite le fichier /mnt/mymasters/my.cnf, contenant la configuration du serveur MySQL suivante:

[mysqld]
port        = 3306
socket        = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 128K

server-id    = 1

log-bin=mysql-bin
binlog_format=mixed
sync_binlog=1

gtid_mode = ON
log-slave-updates = ON
enforce-gtid-consistency = true
innodb_flush_log_at_trx_commit = 1

Nous ne nous attarderons pas sur le premier paragraphe de la configuration.

Pour la réplication, nous avons besoin de définir un server-id unique, notre maître redondé sera d’ID 1. On active ensuite les logs binaires du serveur maître afin que les esclaves puissent les lire.

  • log-bin: préfixe du nom des logs binaires sur le disque
  • binlog_format: le type de logs enregistrées. On choisi ici mixed pour plus de sécurité
  • log-slave-updates: active les journaux sur les slaves. Même si cela peut sembler inutile ici, c’est obligatoire pour activer la réplication avec des GTID

Afin de garantir la cohérence des enregistrements sur le disque et éviter une perte de données, en cas de bascule, on va demander à la base MySQL de lancer des fsync sur le disque à chaque écriture et de flusher les logs InnoDB à chaque transaction. Cela rend la base plus lente en écriture mais assure la cohérence des données.

  • sync_binlog: active les fsync à chaque écriture
  • innodb_flush_log_at_trx_commit: on flush les logs à chaque transaction InnoDB

Enfin on active les GTID:

  • gtid_mode: active la réplication utilisant des GTID plutôt que des positions dans les fichiers de logs
  • enforce-gtid-consistency: assure la cohérence des GTID, évitant des soucis de conflits de GTID en cas de panne

Lancez maintenant le service MySQL grâce à Heartbeat sur le noeud maître.

service heartbeat start

Une fois la base MySQL démarrée, lancez heartbeat sur le second noeud.

Connectez vous maintenant à la base SQL sur le noeud actif et créez un utilisateur de réplication:

CREATE USER 'repl'@'192.168.150.%' IDENTIFIED BY 'mypwd';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.150.%' IDENTIFIED BY 'mypwd';

Vos noeuds maîtres sont désormais prêts.

Configuration des noeuds esclaves

La configuration de nos noeuds esclaves sera relativement similaire aux noeuds maîtres

Installez tout d’abord le paquet/port mysql56-server

pkg install mysql56-server

Créez ensuite le fichier /var/db/mysql/my.cnf avec la section mysqld suivante:

[mysqld]
port        = 3306
socket        = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 128K

server-id    = 13

log-bin=mysql-bin
slave-net-timeout=5
gtid_mode = ON
log-slave-updates = ON
enforce-gtid-consistency = true
sync_master_info=1
master_info_repository=TABLE
sync_relay_log_info=1
relay_log_info_repository=TABLE

Je ne détaillerai pas ici les options précédémment définies. Interessons nous aux options qui diffèrent avec le maître:

  • server-id: l’identifiant unique de serveur
  • slave-net-timeout: on définit un timeout de connexion à 5 secondes. Par défaut c’est 86400, une valeur très mal positionnée.
  • master_info_repository: on stocke les informations de connexion au maître en base de données. Cela permet de sécuriser l’écriture de ces informations grâce aux logs binaires.
  • relay_log_info_repository: on stocke les informations concernant la position dans les logs binaire de relai en base de données. Cela permet de sécuriser l’écriture de ces informations grâce aux logs binaires.
  • syncmasterinfo: on force le flush de l’écriture du master.info sur le disque
  • sync_relay_log_info: on force le flush de l’écriture du relay-log.info sur le disque

Cette configuration permet d’obtenir un esclave crash-safe. Activez et lancez le service:

sysrc mysql_enable=YES
service mysql-server start

Connectez vous ensuite à la base de données et activez la réplication par GTID:

CHANGE MASTER TO
MASTER_HOST='192.168.150.99',
MASTER_USER='repl',
MASTER_PASSWORD='mypwd',
MASTER_AUTO_POSITION=1;

Enfin activez le processus slave:

START SLAVE;

Votre esclave est désormais opérationnel. Si vous souhaitez suivre l’état de la réplication, tapez

SHOW SLAVE STATUS;

Note: si vous activez la réplication sur une base maître déjà existante, n’oubliez pas de faire les étapes suivantes avant d’activer le slave:

  1. Verrouillage des tables en écriture sur le maître
  2. Dump de la base maître en SQL (mysqldump … > dump.sql)
  3. Injection de la base maître sur le slave (mysql < dump.sql)
  4. Activation de la réplication côté esclave (START SLAVE)
  5. Déverrouillage de l’écriture sur le maître

Répétez les opérations précédentes sur chacun des esclaves que vous souhaitez créer (n’oubliez pas de changer le server-id).

Tester la panne du noeud maître

Vous avez plusieurs moyens de basculer le noeud heartbeat:

  • Débranchez l’alimentation du noeud primaire
  • Débranchez le câble réseau DATA (pas celui de synchronisation !!)
  • Tapez la commande /usr/local/lib/heartbeat/hb_standy sur le noeud actif
  • Tapez la commande /usr/local/lib/heartbeat/hb_takeover sur le noeud inactif

Vérifiez ensuite l’état de la bascule en regardant le fichier /var/log/ha.log, et en tapant les commandes hastctl status et ps ax|grep mysql

Vérifiez également l’état de la réplication sur les esclaves au moyen de la commande MySQL SHOW SLAVE STATUS;

Tester la panne d’un noeud esclave

Vous avez plusieurs moyen de tester le noeud esclave:

  • Débranchez l’alimentation du noeud esclave
  • Redémarrez le service MySQL
  • Tuez (kill -9) et démarrez le service MySQL

Conclusion

Ce projet complet basé sur FreeBSD 9.3 et MySQL 5.6 vous permet d’appréhender et construire une architecture MySQL robuste et résiliente, utilisant des mécaniques de cluster lecture/écriture avec de multiples noeuds, au moyen d’une technique de réplication plus moderne (les GTIDs) et robuste que le curseur de logs binaires, tels qu’on le retrouve dans un cluster Galera.

See Also