0

Restauration point-in-time avec MariaDB Galera Cluster et mariabackup

twitterlinkedinmail

Hello à toutes / tous,

Restaurer tout un cluster actif-actif comme Galera cluster à un point dans le temps peut être une opération fastidieuse, et sous la pression on peut rapidement s’emmêler les pinceaux. C’est donc une bonne idée de pouvoir pratiquer cet exercice au calme, en utilisant un bac à sable, via des conteneurs par exemple. Si vous souhaitez refaire l’exercice, vous pourrez retrouver la base sakila utilisée dans cet article ici.

On se propose aujourd’hui de présenter une méthode de restauration d’objet dans un cluster MariaDB Galera à 3 noeuds:

Un arbitre sera ajouté mais ne sera activé que temporairement afin de permettre de maintenir un nombre de votants impair lors du retrait d’un des noeuds.

Le scénario: un utilisateur supprime les données de la table sakila.rental. On souhaite restaurer cette table uniquement. Noter que l’on pourrait restaurer toute la base pour limiter les risques de violation de contraintes lors de la restauration, mais pour simplifier partons du principe que la table vidée par erreur est isolée du reste du modèle de données. La méthode pour restaurer toute la base serait similaire.

Le principe :

1) Activer l’arbitre et stopper le nœud 3, afin que l’équilibre des votes soit maintenu.
2) Redémarrer le nœud 3 en standalone sans configuration cluster.
3) Rechercher dans les journaux binaires soit en ligne soit sauvegardés la position correspondant à la transaction de suppression des données de rental.
4) Restaurer la sauvegarde mariabackup complète puis les journaux dans l’ordre jusqu’à cette position (non inclue).
5) Après vérification, réexporter simplement la table rental.
6) La réimporter dans un des deux nœuds actifs, les modifications seront immédiatement synchronisées avec le nœud restant.
7) Réintégrer le nœud 3 dans le cluster, ce qui redéclenchera une resynchronisation totale via SST.
8) Couper l’arbitre qui ne nous sert plus.

Prérequis : les logs binaires

Evidemment pour pouvoir restaurer les modifications intermédiaires, il faut que les logs binaires soient activés. Il y a une petite subtilité sur Galera, comme les modifications peuvent être faites en local mais aussi venir de n’importe quel autre noeud du cluster (on rappelle que Galera est un cluster actif/actif en lecture et en écriture), il faut pouvoir enregistrer tous les ordres de modification quelle que soit leur provenance. Pour cela on activera en plus de log-bin et log-bin-index, le paramètre log-slave-updates=1.

root@galera3:~#  vi /etc/mysql/my.cnf
(…)
log_bin			= /var/log/mysql/mariadb-bin
log_bin_index		= /var/log/mysql/mariadb-bin.index
log_slave_updates	= 1
expire_logs_days	= 10

Le rôle de l’arbitre :

L’arbitre est un composant spécial dans un cluster Galera (on retrouve le même principe dans d’autres clusters comme les Replica Sets MongoDB, etc…) c’est un noeud qui ne peut pas héberger de données mais peut participer au vote du quorum. En général comme les clusters nécessitent un nombre impair de votants pour limiter les risques de split-brain, on l’utilise lorsque l’on ne peut pas déployer plus de 2 noeuds de données par faute de moyens , volumétrie, etc…

Il ne contient pas la distribution MariaDB + Galera mais seulement un binaire garbd installable via un package sur la plupart des distributions :

root@galerarb:~# apt-get install galera-arbitrator-3

Son fichier de configuration est minimaliste, il contient simplement le nom du cluster Galera et la liste des noeuds:

root@galerarb:~# vi garbd.conf 
address = "gcomm://10.186.157.38,10.186.157.242,10.186.157.196"
group = "TestCluster"

Il peut se démarrer via un service systemd qu’il faudra créer à la main, mais dans notre cas on ne l’activera qu’à la demande donc on ne lancera le processus garbd qu’à la main, en mode daemon, de la façon suivante:

root@galerarb:~# /usr/bin/garbd --cfg /root/garbd.conf \ 
--daemon --log /var/log/garbd.log

Etape 1 : choix d’un noeud de restauration et évinction manuelle du cluster.

On pourrait choisir d’utiliser une burn instance, c’est à dire une instance qu’on ne déploierait que pour restaurer, réexporter, puis qui serait supprimée ensuite (par exemple sous forme d’un conteneur Docker ou LXD) . Dans notre cas, nous allons sacrifier un noeud du cluster, et nous allons lui substituer l’arbitre pour maintenir l’équilibre des votants.

Donc en premier lieu, nous allons démarrer l’arbitre comme vu plus haut, et stopper le noeud galera3 qui sera notre noeud de restauration:

Sur galerarb:

root@galerarb:~# /usr/bin/garbd --cfg /root/garbd.conf \ 
--daemon --log /var/log/garbd.log

et vérifier qu’il est bien vu comme intégré au cluster dans le fichier de log d’un autre noeud:

(...)
2022-02-16 15:46:54 0 [Note] WSREP: Member 3.0 (garb) synced with group.
(...)

Sur galera3 :

root@galera3:~# systemctl stop mariadb.service

Même chose, toujours valider dans le log d’un autre noeud que le cluster voit bien les modifications à chaque fois:

2022-02-17 10:34:27 0 [Note] WSREP:  cleaning up b0ccc293 (tcp://10.186.157.196:4567)

Je conseille de dédier une fenêtre XTERM à un tail -f du fichier de log sur l’un des 2 autres noeuds du cluster, dans notre cas galera1 ou galera2.

Nous allons ensuite sortir manuellement galera3 du cluster en commentant sa configuration wsrep:

root@galera3:~# vi /etc/mysql/mariadb.conf.d/60-galera.cnf 
(...)
# Cluster Configuration
# wsrep_provider           = /usr/lib/galera/libgalera_smm.so
# wsrep_cluster_address    = gcomm://10.186.157.38,10.186.157.242,10.186.157.196
# wsrep_cluster_name       = TestCluster
# wsrep_on                 = ON

root@galera3:~# systemctl start mariadb.service

Il faudra penser à désactiver des tâches automatiques, en crontab par exemple, notamment des tâches de MCO comme des backups de logs binaires qui pourraient perturber notre opération de restauration.

Déterminer la chaîne complète de fichiers à restaurer

A partir de là, notre service est toujours accessible, partiellement car une des tables est vide. En fonction de la criticité de la table, cela peut avoir des conséquences importantes sur la disponibilité des applications, donc il ne faut pas traîner.

Tout d’abord, notre chaîne de backup commence par un backup complet mariabackup, et est complété régulièrement par des sauvegardes de journaux binaires. Voyons ce que nous avons en local:

root@galera3:~/SCRIPTS# ls -lrat /mariabackup
-rw-r--r--  1 root  root   3666109 Feb 17 10:05 MARIABACKUP__20220217_100457.xb.gz
drwxr-xr-x  2 mysql mysql     4096 Feb 17 10:24 .
-rw-r--r--  1 root  root  44573785 Feb 17 10:24 BINLOGS_20220217_102428.tar.gz
-rw-r--r--  1 root  root     203021 Feb 17 11:07 BINLOGS_20220217_110741.tar.gz

Nous avons un backup complet et deux backups de journaux en ligne. Il faut commencer par extraire le backup complet qui a été généré sous forme de stream et vérifier à partir de quel binlog et quelle position commence la chaîne de backup. Nous allons créer un sous répertoire ~RESTORE dans lequel nous pourrons reverser tous les fichiers nécessaires à la restauration.

root@galera3:/mariabackup# mkdir /mariabackup/RESTORE
root@galera3:/mariabackup# chown -R mysql:mysql /mariabackup/RESTORE

root@galera3:/mariabackup# gzip -d MARIABACKUP__20220217_100457.xb.gz
root@galera3:/mariabackup# cd RESTORE/
root@galera3:/mariabackup/RESTORE# mbstream -x < ../MARIABACKUP__20220217_100457.xb

root@galera3:/mariabackup/RESTORE# ls -lrat 
total 77900
drwxr-xr-x 3 mysql mysql     4096 Feb 17 10:40 ..
-rw-r----- 1 root  root  79691776 Feb 17 10:40 ibdata1
drwx------ 2 root  root      4096 Feb 17 10:40 performance_schema
drwx------ 2 root  root      4096 Feb 17 10:40 mysql
drwx------ 2 root  root      4096 Feb 17 10:40 sakila
-rw-r----- 1 root  root     16384 Feb 17 10:40 aria_log.00000001
-rw-r----- 1 root  root       578 Feb 17 10:40 xtrabackup_info
-rw-r----- 1 root  root        41 Feb 17 10:40 xtrabackup_galera_info
-rw-r----- 1 root  root        79 Feb 17 10:40 xtrabackup_checkpoints
-rw-r----- 1 root  root        31 Feb 17 10:40 xtrabackup_binlog_info
-rw-r----- 1 root  root       389 Feb 17 10:40 mariadb-bin.000049
-rw-r----- 1 root  root      2560 Feb 17 10:40 ib_logfile0
-rw-r----- 1 root  root      4874 Feb 17 10:40 ib_buffer_pool
-rw-r----- 1 root  root       324 Feb 17 10:40 backup-my.cnf
-rw-r----- 1 root  root        52 Feb 17 10:40 aria_log_control
drwxr-xr-x 5 mysql mysql     4096 Feb 17 10:40 .

Le fichier xtrabackup_binlog_info contient les informations de binlog et position recherchées (l’équivalent du –master-data de mysqldump)

root@galera3:/mariabackup/RESTORE# cat xtrabackup_binlog_info
mariadb-bin.000049	389	0-1-329

OK donc on sait qu’il faudra restaurer au moins à partir du log binaire mariadb-bin.000049, mais dans notre cas il n’est plus en ligne :

MariaDB [(none)]> show binary logs ;
+--------------------+-----------+
| Log_name           | File_size |
+--------------------+-----------+
| mariadb-bin.000055 |    403132 |
| mariadb-bin.000056 |      2740 |
| mariadb-bin.000057 |       599 |
| mariadb-bin.000058 |       599 |
| mariadb-bin.000059 |      1094 |
| mariadb-bin.000060 |       412 |
| mariadb-bin.000061 |       344 |
+--------------------+-----------+
7 rows in set (0.000 sec)

Il faudra donc le restaurer à partir d’une sauvegarde :

root@galera3:~ # tar -ztvf /mariabackup/BINLOGS_20220217_102428.tar.gz | grep 'mariadb-bin.000049'
-rw-rw---- mysql/adm  30817312 2022-02-17 10:16 mariadb-bin.000049

Nous avons le backup complet et le début de la chaîne de sauvegarde, maintenant il faut trouver le point d’arrêt de la restauration, c’est à dire la transaction qui précède le delete sur sakila.rental. C’est la partie un peu fastidieuse du processus, où il faut partir à la pêche au bon fichier et à la bonne position:

root@galera3:/mariabackup/RESTORE# tar -zxf ../BINLOGS_20220217_102428.tar.gz 

root@galera3:/mariabackup/RESTORE# ls -lrat 
total 117136
-rw-rw---- 1 mysql adm     402493 Feb 16 16:25 mariadb-bin.000033
-rw-rw---- 1 mysql adm        438 Feb 16 16:25 mariadb-bin.000034
-rw-rw---- 1 mysql adm        412 Feb 16 16:27 mariadb-bin.000035
-rw-rw---- 1 mysql adm    4591827 Feb 16 16:51 mariadb-bin.000036
-rw-rw---- 1 mysql adm        367 Feb 16 16:51 mariadb-bin.000037
-rw-rw---- 1 mysql adm    4592833 Feb 16 16:53 mariadb-bin.000038
-rw-rw---- 1 mysql adm        367 Feb 17 07:39 mariadb-bin.000039
-rw-rw---- 1 mysql adm        770 Feb 17 07:58 mariadb-bin.000040
-rw-rw---- 1 mysql adm        438 Feb 17 08:16 mariadb-bin.000041
-rw-rw---- 1 mysql adm        438 Feb 17 08:20 mariadb-bin.000042
-rw-rw---- 1 mysql adm        438 Feb 17 08:22 mariadb-bin.000043
-rw-rw---- 1 mysql adm        438 Feb 17 08:24 mariadb-bin.000044
-rw-rw---- 1 mysql adm        438 Feb 17 08:25 mariadb-bin.000045
-rw-rw---- 1 mysql adm        412 Feb 17 09:33 mariadb-bin.000046
-rw-rw---- 1 mysql adm        367 Feb 17 09:33 mariadb-bin.000047
-rw-rw---- 1 mysql adm    2413649 Feb 17 10:05 mariadb-bin.000048
-rw-rw---- 1 mysql adm   30817312 Feb 17 10:16 mariadb-bin.000049
-rw-rw---- 1 mysql adm   31345403 Feb 17 10:19 mariadb-bin.000050
-rw-rw---- 1 mysql adm   11424743 Feb 17 10:19 mariadb-bin.000051
-rw-rw---- 1 mysql adm   11424743 Feb 17 10:19 mariadb-bin.000052
-rw-rw---- 1 mysql adm   11424743 Feb 17 10:19 mariadb-bin.000053
-rw-rw---- 1 mysql adm   11424743 Feb 17 10:19 mariadb-bin.000054
drwxr-xr-x 3 mysql mysql     4096 Feb 17 10:40 ..
drwxr-xr-x 2 mysql mysql     4096 Feb 17 10:57 .

root@galera3:/mariabackup/RESTORE# for file in mariadb-bin.000049 mariadb-bin.000050 mariadb-bin.000051 mariadb-bin.000052 mariadb-bin.000053 mariadb-bin.000054
> do 
> echo "${file}" 
> mysqlbinlog ${file} --base64-output=decode-rows | grep -in "delete from rental"
> done
mariadb-bin.000049
212555:#Q> delete from rental_1466 where return_date > '2005-07-01 00:00:00'
487940:#Q> delete from rental_1471 where return_date > '2005-07-01 00:00:00'
mariadb-bin.000050
496895:#Q> delete from rental_1475 where return_date > '2005-07-01 00:00:00'
mariadb-bin.000051
141460:#Q> delete from rental_1477 where return_date > '2005-07-01 00:00:00'
mariadb-bin.000052
141460:#Q> delete from rental_1476 where return_date > '2005-07-01 00:00:00'
mariadb-bin.000053
141460:#Q> delete from rental_1479 where return_date > '2005-07-01 00:00:00'
mariadb-bin.000054
141458:#Q> delete from rental_1478 where return_date > '2005-07-01 00:00:00'

On voit ici que les deletes concernent des tables différentes de la table rental, ce sont des tables dérivées mais pas la table d’origine, il faut continuer les recherches dans le second backup:

root@galera3:/mariabackup/RESTORE# tar -zxf ../BINLOGS_20220217_110741.tar.gz
root@galera3:/mariabackup/RESTORE# for file in mariadb-bin.000055 mariadb-bin.000056 mariadb-bin.000057 mariadb-bin.000058 mariadb-bin.000059 mariadb-bin.000060 
> do 
> echo "${file}" 
> mysqlbinlog ${file} --base64-output=decode-rows | grep -in "delete from rental"
> done
mariadb-bin.000055
31:#Q> delete from rental
mariadb-bin.000056
mariadb-bin.000057
mariadb-bin.000058
mariadb-bin.000059
mariadb-bin.000060

On a donc trouvé, le delete se trouve dans le binlog mariadb-bin.000055, reste à trouver la position:

root@galera3:/mariabackup/RESTORE# mysqlbinlog mariadb-bin.000055 | more
(...)
# at 299
#220217 10:19:09 server id 1  end_log_pos 344 CRC32 0x3eb16ee1 	Binlog checkpoint mariadb-bin.000054
# at 344
#220217 10:19:09 server id 1  end_log_pos 389 CRC32 0xefa5a7e6 	Binlog checkpoint mariadb-bin.000055
# at 389
#220217 10:26:22 server id 1  end_log_pos 431 CRC32 0x628184fc 	GTID 0-1-344 trans
/*!100101 SET @@session.skip_parallel_replication=0*//*!*/;
/*!100001 SET @@session.gtid_domain_id=0*//*!*/;
/*!100001 SET @@session.server_id=1*//*!*/;
/*!100001 SET @@session.gtid_seq_no=344*//*!*/;
START TRANSACTION
/*!*/;
# at 431
# at 472
#220217 10:26:22 server id 1  end_log_pos 472 CRC32 0xd222415b 	Annotate_rows:
#Q> delete from rental
#220217 10:26:22 server id 1  end_log_pos 532 CRC32 0x34accc81 	Table_map: `sakila`.`rental` mapped to number 64 (has triggers)
# at 532
#220217 10:26:22 server id 1  end_log_pos 594 CRC32 0xd312376b 	Table_map: `sakila`.`payment` mapped to number 63 (has triggers)
(...)

Notre transaction est à la position 389 dans le fichier mariadb-bin.000055.

En conclusion, notre séquence de restauration sera la suivante:
a. Restauration du backup complet
b. Restauration du journal mariadb-bin.000049 à partir de la position 389
c. Restauration des journaux mariadb-bin.000050 à mariadb-bin.000054 complets
d. Restauration du journal mariadb-bin.000055 jusqu’à la position 389 non comprise.

Restauration de la séquence de backup sur le noeud 3 et réexport de la table rental

Pour restaurer le backup complet mariabackup, il faut couper le noeud 3 et vider son datadir:

root@galera3:/mariabackup/RESTORE# systemctl stop mariadb.service
root@galera3:/mariabackup/RESTORE# cd /var/lib/mysql
root@galera3:/var/lib/mysql# rm -rf *

Puis préparer le backup et restaurer:

root@galera3:/var/lib/mysql# mariabackup --prepare \ 
--target-dir=/mariabackup/RESTORE/ 
mariabackup based on MariaDB server 10.3.34-MariaDB debian-linux-gnu (x86_64)
[00] 2022-02-17 10:53:28 cd to /mariabackup/RESTORE/
(...)
00] 2022-02-17 10:53:29 Last binlog file /var/log/mysql/mariadb-bin.000048, position 402837
[00] 2022-02-17 10:53:29 completed OK!

root@galera3:/var/lib/mysql# mariabackup --copy-back \
--target-dir=/mariabackup/RESTORE/ \
--user=mariabackup --password=********
mariabackup based on MariaDB server 10.3.34-MariaDB debian-linux-gnu (x86_64)
[01] 2022-02-17 10:52:21 Copying ib_logfile0 to /var/lib/mysql/ib_logfile0
[01] 2022-02-17 10:52:21         ...done
[01] 2022-02-17 10:52:21 Copying ibdata1 to /var/lib/mysql/ibdata1
(...)
[01] 2022-02-17 10:52:22 Copying ./xtrabackup_info to /var/lib/mysql/xtrabackup_info
[01] 2022-02-17 10:52:22         ...done
[00] 2022-02-17 10:52:22 completed OK!

Ne pas oublier de bien réattribuer les droits en fin de séquence, avant de redémarrer le service:

root@galera3:/var/lib/mysql# chown -R mysql:mysql *
root@galera3:/var/lib/mysql# systemctl start mariadb.service

Ensuite nous pouvons restaurer notre séquence de logs binaires:

root@galera3:/mariabackup/RESTORE# mysqlbinlog \
/mariabackup/RESTORE/mariadb-bin.000049 \
--start-position=389 | mysql

root@galera3:/mariabackup/RESTORE# for file in /mariabackup/RESTORE/mariadb-bin.000050 /mariabackup/RESTORE/mariadb-bin.000051 /mariabackup/RESTORE/mariadb-bin.000052 /mariabackup/RESTORE/mariadb-bin.000053 /mariabackup/RESTORE/mariadb-bin.000054
> do
>	echo "Restoring ${file}..."
>	mysqlbinlog ${file} | mysql
> done

root@galera3:/mariabackup/RESTORE# mysqlbinlog \
/mariabackup/RESTORE/mariadb-bin.000055 \
--stop-position=389 | mysql

Et vérifier que les données de la table rental ont bien été récupérées, puis les réexporter:

root@galera3:/mariabackup/RESTORE# mysql \
--execute="select count(1) from sakila.rental;"
+----------+
| count(1) |
+----------+
|    16044 |
+----------+

root@galera3:/mariabackup/RESTORE# mysqldump \
--user=root --socket=/var/run/mysqld/mysqld.sock –password=***** \
sakila rental > /mariabackup/rental.sql

Réimport de la table dans un autre noeud du cluster

N’importe quel noeud en Primary / Synced fera l’affaire. La réexecution du fichier SQL sera ensuite répliquée en synchrone sur l’autre noeud.

root@galera1:/mariabackup# mysql sakila < rental.sql

Vérifier que les données sont bien présentes sur tous les noeuds galera1, galera2, avant de réintégrer galera3.

Réintégration de galera3 dans le cluster et arrêt de l’arbitre

Il suffit simplement de décommenter les paramètres wsrep dans le fichier de configuration de MariaDB sur galera3, et redémarrer le service :

root@galera3:~# vi /etc/mysql/mariadb.conf.d/60-galera.cnf 
(...)
 Cluster Configuration
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_address    = gcomm://10.186.157.38,10.186.157.242,10.186.157.196
wsrep_cluster_name       = TestCluster
wsrep_on                 = ON
wsrep_sst_method = mariabackup
wsrep_sst_donor = galera1, galera2
wsrep_sst_auth = mariabackup:capdata
(...)

root@galera3:/var/lib/mysql# systemctl restart mariadb.service

Un SST sera déclenché pour remettre le noeud galera3 à jour depuis un donor disponible (ici galera1):

2022-02-17 13:21:03 0 [Note] WSREP: Member 0.0 (galera3) synced with group.

Et enfin nous n’avons plus besoin de l’arbitre, nous sommes revenus en nominal:

root@galerarb:~# killall garbd

Penser à bien réactiver les tâches commentées sur galera3 pendant la phase de recherche et restauration des données. Et le tour est joué !

A+ pour d’autre articles !
~David

Continuez votre lecture sur le blog :

twitterlinkedinmail

David Baffaleuf

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.