Galera, la solution pour des bases de données hautement disponibles

Spécialité(s)


Résumé

Lorsqu’une application ne peut tolérer d’indisponibilités ou quand il devient nécessaire de fournir de la redondance au service de bases de données, il convient de mettre en place une architecture limitant les risques d’interruption de service. De même, le besoin de performance peut se faire sentir avec la croissance du nombre d’utilisateurs. Galera est une extension pour MariaDB aidant à résoudre ces deux situations.


Body

Galera est une extension pour MariaDB, MySQL et Percona XtraDB offrant des fonctionnalités de clustering multi-master. La réplication classique fonctionne en mode maitre-esclave, nécessitant donc en cas de panne ou de maintenance sur le master de mettre en œuvre un mécanisme de failover sur le slave pour le promouvoir. Cette opération est potentiellement complexe et manuelle.

Avec Galera, il est possible de disposer de plusieurs serveurs master et donc disponibles en lecture comme en écriture. La réplication est opérée de manière synchrone et peut même être géorépliquée.

Pour améliorer les performances, il devient possible d’augmenter le nombre de nœuds au sein du cluster, éventuellement complété d’une mise à l’échelle verticale, c’est-à-dire par ajout de vCPU et de RAM.

La haute disponibilité du service de bases de données devient donc native et ce, sans nécessiter de mettre en œuvre une opération de promotion du slave comme master. Le service est rendu tant qu’il y a au moins un nœud fonctionnel, toute question de capacité à tenir la charge mise à part.

Ensuite, Galera connaît très peu de limitations à la mise en cluster, mais deux en particulier. La première a une portée en principe limitée, elle impose le moteur de stockage InnoDB, le moteur historique MyISAM est donc à oublier. La seconde, peut-être plus contraignante, est qu’une table doit avoir obligatoirement au moins une clé primaire. Cela peut donc demander du travail avec les développeurs qui vont coder avec cette base de données. Enfin, dernière limitation majeure, mais ce n’en est pas une pour moi, Galera n’est disponible que sous Linux, mais pas les autres systèmes supportés par MariaDB comme Windows.

1. Création du cluster

Un cluster Galera doit être composé d’un nombre impair de nœuds. Il n’est techniquement pas interdit de créer un cluster à seulement deux nœuds, ce n’est toutefois pas recommandé. En effet, on risque dans ce cas une situation de split brain. Un split brain est une situation où les nœuds qui composent le cluster se retrouvent isolés, par exemple en cas de coupure du réseau. Avec seulement deux nœuds, ils ne pourraient déterminer quel nœud se retrouve isolé. Cela impose donc au minimum de disposer de trois serveurs, sachant que l’ajout de nœuds au cluster est très aisé.

Pour la suite de cet article, je disposerai de quatre serveurs sous Ubuntu 22.04, trois serveurs de bases de données et un load balancer utilisé dans la suite de cet article :

  • db01 : 192.168.69.81 ;
  • db02 : 192.168.69.82 ;
  • db03 : 192.168.69.83 ;
  • lb : 192.168.69.70.

1.1 Installation de MariaDB

Dans un premier temps et sur chaque serveur composant le cluster, nous allons simplement y installer le service mariadb.

root@db01:~# apt -y install mariadb-server
root@db01:~# systemctl enable mariadb

Par défaut, MariaDB sous Ubuntu Server n’écoute que sur l’interface loopback. Comme les nœuds du cluster vont être accessibles et communiquer via le réseau, il faut faire en sorte que MariaDB écoute sur une socket accessible depuis les autres nœuds, idéalement en restreignant depuis une interface. Pour faire simple, nous allons le faire écouter sur toutes les interfaces :

root@db01:~# sed -i 's/^bind-address.*/bind-address = 0.0.0.0/g' /etc/mysql/mariadb.conf.d/50-server.cnf
root@db01:~# systemctl restart mariadb

1.2 Ajout du module Galera

Toujours sur chaque nœud, nous allons ensuite installer le module galera et éteindre mariadb :

root@db01:~# apt -y install galera-4
root@db01:~# systemctl stop mariadb

Il faut maintenant activer la réplication avec le provider wsrep et surtout déclarer l’ensemble des nœuds participants au cluster. La configuration se fait dans le fichier /etc/mysql/mariadb.conf.d/60-galera.cnf : wsrep_on active tout simplement la réplication Galera. Le nom du cluster doit quant à lui être identique sur l’ensemble des nœuds. Enfin, wsrep_cluster_address doit contenir les IP de tous les membres du cluster, séparés par une virgule.

[galera]
wsrep_on                       = ON
wsrep_cluster_name             = "MariaDB Galera Cluster"
wsrep_cluster_address          = gcomm://192.168.69.81,192.168.69.82,192.168.69.83
binlog_format                  = row
default_storage_engine         = InnoDB
bind-address                   = 0.0.0.0
wsrep_slave_threads            = 4
innodb_flush_log_at_trx_commit = 1
log_error                      = /var/log/mysql/error-galera.log

wsrep_slave_threads (wsrep_applier_threads) dans les versions plus récentes permet de gérer le nombre de threads pour traiter les write-sets, les transactions commitées lors de la réplication. Généralement, on souhaite avoir deux fois le nombre de cœurs du serveur pour des performances correctes.

1.3 Démarrage du cluster

La configuration précédente terminée, il est maintenant possible de démarrer le cluster. Un nœud doit être utilisé pour bootstraper le cluster avec la commande galera_new_cluster :

root@db01:~# galera_new_cluster

Cette commande initialise le primary component du cluster et démarre MariaDB. Le primary component est l’ensemble des nœuds qui forment le cluster, qui peuvent communiquer ensemble et assurer le commit d’une transaction. Chacun des nœuds démarrés par la suite se connectera à ce nœud pour démarrer la réplication. En terminologie Galera, il s’agit du State Snapshot Transfert (SST).

Le journal de log défini plus tôt doit nous confirmer à ce propos que le bootstrap est lancé et que le statut du serveur passe en Joined.

2023-03-05 22:34:39 0 [Note] WSREP: Server status change initializing -> initialized
2023-03-05 22:34:39 0 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
2023-03-05 22:34:39 1 [Note] WSREP: Bootstrapping a new cluster, setting initial position to 00000000-0000-0000-0000-000000000000:-1
2023-03-05 22:34:39 4 [Note] WSREP: Cluster table is empty, not recovering transactions
2023-03-05 22:34:39 1 [Note] WSREP: Server status change initialized -> joined

Les autres nœuds du cluster peuvent ensuite être démarrés normalement. Ils vont se connecter au primary component, lancer un state transfert pour synchroniser la copie locale de la base de données afin d’être synchronisés au cluster.

root@db02:~# systemctl start mariadb

Contrôlons à nouveau nos logs, et l’on peut constater que pas mal de lignes ont défilé. Ce que l’on doit retenir c’est que les nouveaux nœuds ont initialisé un state transfert depuis l’un des serveurs déjà synchronisé et que l’état doit être passé en « complete ».

2023-03-05 22:37:37 0 [Note] WSREP: Member 1.0 (db03) requested state transfer from '*any*'. Selected 0.0 (db02)(SYNCED) as donor.
2023-03-05 22:37:39 0 [Note] WSREP: (ea0f8bad-ad8c, 'tcp://0.0.0.0:4567') turning message relay requesting off
2023-03-05 22:37:39 0 [Note] WSREP: 0.0 (db02): State transfer to 1.0 (db03) complete.
2023-03-05 22:37:39 0 [Note] WSREP: Member 0.0 (db02) synced with group.
2023-03-05 22:37:42 0 [Note] WSREP: 1.0 (db03): State transfer from 0.0 (db02) complete.
2023-03-05 22:37:42 0 [Note] WSREP: Member 1.0 (db03) synced with group.

Chaque nœud peut ensuite être démarré ou redémarré indépendamment avec systemctl. Cependant, si chaque nœud a été arrêté proprement, le cluster n’est plus formé et il est nécessaire à nouveau de bootstrap le cluster avec galera_new_cluster.

Vous l’aurez deviné, en cas de maintenance, il conviendra de démarrer en premier le dernier nœud qui a été arrêté afin d’avoir des données cohérentes. Le problème va se poser si l’on ignore l’ordre d’arrêt ou que le cluster s’est mal arrêté. Il pourra dans ce cas être nécessaire d’indiquer que le nœud sur lequel on souhaite initialiser le bootstrap du cluster est en mesure de le prendre en charge, cela se fait dans le fichier /var/lib/mysql/grastate.dat avec la variable safe_to_bootstrap à forcer à 1. Cette valeur est à zéro après le bootstrap pour éviter une initialisation accidentelle d’un cluster déjà bootstrapé. De préférence, il s’agit d’une opération à utiliser en dernier recours.

root@db01:~# cat /var/lib/mysql/grastate.dat
# GALERA saved state
version: 2.1
uuid:    ea14ca85-bba5-11ed-bf58-0b029f897fa2
seqno:   -1
safe_to_bootstrap: 0

2. Tester la réplication

En théorie, notre cluster est en place. Pour le confirmer, vérifions avec des données. Commençons par créer une base de données nommée galeratest qui ne comporte qu’une seule table :

root@db01:~# mysql -u root -e "CREATE DATABASE galeratest;"
root@db01:~# mysql -u root galeratest -e "CREATE TABLE test(ID int NOT NULL AUTO_INCREMENT, data varchar(255) NOT NULL, PRIMARY KEY (ID) );"

Peuplons maintenant cette table avec un millier d’enregistrements :

root@db01:~# for i in {1..1000}; do   mysql -u root galeratest -e "INSERT INTO test (\`data\`) VALUES ('test$i');"; done

Et enfin, vérifions sur un autre serveur que nous retrouvons bien nos enregistrements :

root@db02:~# mysql -u root galeratest -e "SELECT COUNT(ID) FROM test;"
+-----------+
| COUNT(ID) |
+-----------+
|      1000 |
+-----------+

C’est parfait, nous avons bien nos 1000 lignes dans la table, les données sont donc bien répliquées.

3. Superviser l’état du cluster

En environnement de production, il convient de vérifier que chaque nœud est connecté aux autres membres du cluster et qu’il est bien répliqué. Ces opérations doivent être vérifiées a minima manuellement, et ce régulièrement et idéalement automatiquement, que ce soit via des scripts ou une intégration de ceux-ci avec une solution de supervision type Nagios.

Galera permet de récupérer localement sur chaque nœud l’état du cluster avec un certain nombre de variables de statut. Les différentes variables wsrep du provider Galera sont accessibles au travers de SHOW STATUS, de manière similaire aux variables de statut de MariaDB.

À l’issue du déploiement précédent, wsrep_ready permet déjà de visualiser si le nœud est en mesure d’accepter les requêtes. Si la réponse est OFF, le serveur renverra des erreurs aux clients.

MariaDB [mysql]> show status like 'wsrep_ready';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready   | ON    |
+---------------+-------+

Ensuite, wsrep_connected doit renvoyer ON. Si cette valeur renvoie OFF, alors le nœud n’est pas connecté au cluster. Cela peut être l’origine d’une erreur de configuration ou bien d’une anomalie réseau.

MariaDB [mysql]> show status like 'wsrep_connected';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| wsrep_connected | ON    |
+-----------------+-------+    

Enfin, wsrep_cluster_size doit renvoyer le nombre de nœuds présents dans le cluster. En état nominal, il s’agit logiquement du nombre de nœuds total du cluster. Toutefois cette valeur permet également de gérer les niveaux d’alertes pour lesquels le nombre minimum de nœuds peut rendre le service. En théorie, un nœud suffit à rendre le service, toutes considérations de performances mises à part :

MariaDB [(none)]> show status like 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+

Ensuite, il faut vérifier que chaque nœud est connecté au primary component et donc qu’il est dans un état sain et dans un état où il peut recevoir des mises à jour des autres nœuds.

MariaDB [(none)]> show status like 'wsrep_cluster_status';
+----------------------+---------+
| Variable_name        | Value   |
+----------------------+---------+
| wsrep_cluster_status | Primary |
+----------------------+---------+

Pour aller plus loin dans la visualisation de l’état du nœud au sein du cluster, la variable wsrep_local_state_comment donne davantage d’indications sur l’état du nœud. Cette variable possède quatre valeurs :

  • Joining : le nœud est en train de rejoindre le cluster ;
  • Donor/Desynced : le nœud est la source de réplication d'un autre nœud en train de rejoindre le cluster ;
  • Joined : le nœud a rejoint le cluster ;
  • Synced : le nœud est synchronisé avec le cluster.
MariaDB [(none)]> show status like 'wsrep_local_state_comment';
+---------------------------+--------+
| Variable_name             | Value |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+

Enfin, au-delà de l’état du cluster, il convient de superviser les performances de réplication. La variable wsrep_local_recv_queue_avg donne la longueur moyenne de la file d’attente de réplication. Si elle est supérieure à 0 cela signifie que le node a un peu de latence dans l’application des write-sets. Au-delà de 0,5, il faut vérifier qu’il n’y a pas de goulot d’étranglement. S’agissant d’une valeur moyenne, il convient d’observer dans ce cas-là les valeurs minimales et maximales, wsrep_local_recv_queue_min et wsrep_local_recv_queue_max afin d’avoir l’amplitude réelle.

MariaDB [(none)]> show status like 'wsrep_local_recv_queue_avg';
+----------------------------+----------+
| Variable_name              | Value    |
+----------------------------+----------+
| wsrep_local_recv_queue_avg | 0.142857 |
+----------------------------+----------+

Ces variables sont les principales à superviser. En pratique, il en existe une soixantaine visibles avec un show status like 'wsrep_%;. La plupart ne seront d’aucune utilité, mais celles présentées restent celles qu’il faut connaître impérativement.

4. Load balancing avec HAProxy

Notre cluster Galera est monté, les clients vont ensuite devoir s’y connecter. Si on configure les accès sur le nom d’hôte ou l’adresse IP d’un des nœuds, alors en cas d’indisponibilité de celui-ci les autres nœuds seront fonctionnels, mais inutilisés. Une méthode consiste à utiliser du round robin DNS. C’est-à-dire que pour un nom d’hôte, on va déclarer trois enregistrements type A pointant vers chacune des adresses IP des nœuds du cluster. Cependant, cette solution n’est pas idéale et peut renvoyer vers un nœud non fonctionnel.

Une meilleure solution consiste à utiliser un load balancer qui va exposer le service MariaDB, mais qui aura en backend les différents nœuds qui composent le cluster Galera. Le load balancer permet en outre de tester la disponibilité des backends et ne présente que les nœuds fonctionnels. HAProxy est le load balancer libre le plus populaire et le plus abouti pour gérer ces situations. Commençons par l’installation sur une VM dédiée :

root@lb:~# apt -y install haproxy
root@lb:~# systemctl enable haproxy

Ensuite, nous aurons besoin d’un utilisateur non privilégié qui permettra à HAProxy de s’authentifier pour vérifier qu’une connexion peut être établie :

MariaDB [mysql]> CREATE USER 'haproxy'@'192.168.69.70';

Enfin, nous allons configurer HAProxy dans le fichier /etc/haproxy/haproxy.cfg et ajouter ces lignes à la fin du fichier :

listen galera
    bind 192.168.69.70:3306
    balance leastconn
    mode tcp
    option tcpka
    option mysql-check user haproxy
    option tcplog
    server db1 192.168.69.81:3306 check weight 1
    server db2 192.168.69.82:3306 check weight 1
    server db3 192.168.69.83:3306 check weight 1

Le service Galera est exposé sur le port MariaDB classique, 3306/TCP via l’IP du load balancer 192.168.69.70. balance leastconn est l’algorithme de répartition de charge, dans ce cas, il s’agit d’envoyer les connexions vers le serveur qui en a le moins, nous pourrions tout autant utiliser du round robin ou du source IP. Le check mysql-check est utilisé avec l’utilisateur HAProxy créé précédemment. HAProxy enverra un paquet pour s’authentifier et un second pour mettre fin proprement à la connexion. Un simple check TCP engendrerait dans les logs une fin prématurée de la connexion. Enfin, on définit chaque nœud vers lequel on renvoie les connexions. check sert à indiquer qu’on teste la disponibilité du backend et weight 1 que les connexions seront distribuées de manière équitable entre les backends.

Un redémarrage du service plus tard et on peut contrôler l’état de nos backends avec hatop. Si au moins un backend apparaît comme UP, l’accès au travers de la VIP est possible.

root@lb:~# systemctl restart haproxy
root@lb:~# hatop -s /run/haproxy/admin.sock

Idéalement et dans un contexte de production, il faudrait également mettre en haute disponibilité le load balancer via une paire de load balancer HAProxy partageant une VIP en VRRP avec Keepalived par exemple. Ceci afin de ne pas créer de nouveau point unique de panne. Mais cela dépasse le cadre de cet article.

Conclusion

Galera est un moyen simple pour monter une solution de haute disponibilité pour vos bases de données MariaDB avec peu de contraintes d’exploitation. Il n’y a donc plus de raisons de réveiller les admins la nuit pour dépanner un serveur MariaDB en mono serveur dès lors que vous administrez des applications critiques. Pour aller plus loin, il s’agit d’un sujet qui se prête particulièrement bien à des déploiements orchestrés par Ansible ou Puppet.

Références

[BASEDOC] Base documentaire des Éditions Diamond : https://connect.ed-diamond.com/

[1] Documentation officielle de Galera :
https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/

[2] Aller plus loin avec MariaDB, Linux Pratique n°130, par Masquelin Mickaël :
https://connect.ed-diamond.com/linux-pratique/lp-130/aller-plus-loin-avec-mariadb

[3] À la découverte d’HAProxy, Linux Pratique Hors-série n°55, par Assmann Baptiste :
https://connect.ed-diamond.com/linux-pratique/lphs-055/a-la-decouverte-d-haproxy



Article rédigé par

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous