Stratégie de sauvegarde d’un site Web (Linux – mdadm, rsync et mysqldump)

Lorsque nous sommes sur un hébergement mutualisé, nous avons tendance à négliger la partie sauvegarde de notre site Web. À tort ou à raison, on se dit que l’hébergeur gère, jusqu’au jour où c’est la plantade générale chez eux et que leurs backups sont morts, mais c’est une autre histoire. Sur un serveur dédié, c’est une toute autre histoire, le prestataire ne gère pas vos sauvegardes, il vous propose seulement des services pour le faire (backup quotidien, snapshot, FTP de backup…). Etant en plein dans le sujet en ce moment pour faire passer jeuxpcmag.com sur un dédié, je me suis dit que cela pouvait être intéressant de partager mon expérience sur le sujet.

Pour information, je suis sur un serveur Soyoustart (OVH), E3-SSD-3, qui me fait faire un énorme bond en avant par rapport au mutualisé actuel soit dit en passant. Côté stockage, nous sommes sur 3 SSD de 120Go. Je le précise car la première étape à la « sécurisation » des fichiers contenus sur votre serveur, avant la notion de backup, est la redondance.

La redondance

Pour résumer, il y a redondance quand, lorsque votre serveur flanche, un deuxième serveur est capable de prendre le relais en toute transparence pour les utilisateurs. Ce qui laisse le temps au sysadmin de corriger le problème sur le premier serveur. Bon, ça c’est super, encore faut-il avoir les moyens de se payer plusieurs serveurs. Ce n’est pas mon cas alors je ne m’étalerai pas sur ce sujet qui mériterait de toute façon un article à part entière.

Il existe cependant une autre forme de redondance lorsque votre serveur dispose de plusieurs disques durs. En production, je vous conseille très fortement d’avoir plusieurs disques sur votre serveur et de privilégier un RAID 1 (disques en miroir), sans notion d’agrégation ni de parité. Chaque disque est une copie conforme de l’autre, certes, c’est moins performant mais tellement plus sécurisant. Dans mon cas, avec 3 SSD, je peux donc supporter deux pannes de disques avant d’être en panique totale. Lorsque ces pannes arrivent, c’est transparent pour les utilisateurs, le serveur switch sur un disque sain à la volée.

Monitoring de votre RAID avec mdadm

Pour savoir quand un disque rend l’âme, il existe des outils d’administration d’ensembles RAID (logiciel), pour le cas de mon SoYouStart, il s’agit de mdadm. Si vous avez déjà mis en place un système d’envoi de mail (ex : postfix), vous n’aurez besoin que de 30s pour configurer le monitoring de votre grappe RAID. Pour recevoir des alertes par email en cas de défaillance, il suffit de réaliser les manipulations suivantes que j’ai effectué sur une Debian 8 mais ça devrait passer tel quel sur la plupart des Linux :

Dans /etc/default/mdadm, vérifier que START_DAEMON est sur « true » sinon modifier ou ajouter. C’est pour que le service démarre au lancement du serveur.

# START_DAEMON:
#   should mdadm start the MD monitoring daemon during boot?
START_DAEMON=true

Dans /etc/mdadm/mdadm.conf, vous devez mettre à jour l’adresse email pour les alertes.

# instruct the monitoring daemon where to send mail alerts
MAILADDR votre_adresse@email

Notez que ce système ne vous préserve pas d’une erreur de manipulation mais d’une défaillance technique au niveau des disques. Si vous plantez une application par inadvertance, elle sera plantée sur tous les disques et si c’est le processeur qui claque, vous aurez toujours vos données mais le serveur sera inopérationnel.

Test du monitoring

Il y a deux méthodes pour vérifier que vous recevez bien les emails, la méthode douce :

/sbin/mdadm --monitor --scan --test

Vous pourriez avoir « mdadm: Only one autorebuild process allowed in scan mode, aborting » en retour. Dans ce cas, vous pouvez stopper le service (service mdmonitor stop) puis refaire le test. Il faudra alors quitter le test (CTRL+C) et relancer le service (service mdmonitor start). Si vous avez reçu un email avec pour objet « TestMessage », c’est que tout est ok.

Si la méthode douce ne fonctionne pas, il y a la méthode brute qui consiste à simuler la défaillance. Pour pouvoir réaliser cette manipulation, il faut connaître les identifiants de vos partitions et disques.

cat /proc/mdstat

Vous obtiendrez un résultat similaire à celui-ci :

Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4] [multipath] [faulty]
md1 : active raid1 sdb1[1] sda1[0] sdc1[2]
5241792 blocks [3/3] [UUU]

md2 : active raid1 sdb2[1] sda2[0] sdc2[2]
94671808 blocks [3/3] [UUU]

md5 : active raid1 sdb5[1] sda5[0] sdc5[2]
15726528 blocks [3/3] [UUU]

md6 : active raid1 sdb6[1] sda6[0] sdc6[2]
1046400 blocks [3/3] [UUU]

unused devices: <none>

J’ai quatre partitions (md1, md2, md5 et md6 — le swap n’apparaîtra pas) en raid 1 sur trois disques : sda, sdb et sdc. Nous allons mettre en faute une partition sur l’un de ces disques. Nous ne sommes jamais à l’abris d’un vrai soucis pile au même moment (mais vous seriez un gros poissard) donc ayez déjà un backup sous le coude ou attaquez vous à une partition sans risque, dans mon cas, je choisis la md6 qui est ma partition /tmp (vous pouvez les reconnaître au nombre de blocks) et le troisième disque (sdc).

mdadm --manage /dev/md6 --fail /dev/sdc6

Vous recevrez un email de ce genre :

This is an automatically generated mail message from mdadm
running on domain.tld

A Fail event had been detected on md device /dev/md6.

It could be related to component device /dev/sdc6.

Faithfully yours, etc.

P.S. The /proc/mdstat file currently contains the following:

Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4] [multipath] [faulty]
md1 : active raid1 sdb1[1] sda1[0] sdc1[3]
5241792 blocks [3/3] [UUU]

md2 : active raid1 sdb2[1] sda2[0] sdc2[3]
94671808 blocks [3/3] [UUU]

md5 : active raid1 sdb5[1] sda5[0] sdc5[3]
15726528 blocks [3/3] [UUU]

md6 : active raid1 sdb6[1] sda6[0] sdc6[3](F)
1046400 blocks [3/2] [UU_]

unused devices: <none>

Immédiatement, nous repérons le (F) pour fault au niveau de md6. Aussi symbolisé par [UU_] où le underscore représente un disque (le troisième) faisant défaut dans la grappe. Si vous ne recevez pas d’email, vous pouvez vérifier que votre simulation a bien fonctionné avec la commande suivante (déjà vue) :

cat /proc/mdstat

Si c’est le cas, c’est que votre serveur mail coince certainement quelque part ou que vous avez fait une erreur en renseignant votre adresse email.

Une fois l’email reçu, il ne faut pas oublier de reconstruire la grappe ! C’est très simple, on retire le disque de la partition avant de le remettre :

mdadm --manage /dev/md6 --remove /dev/sdc6
Puis
mdadm --manage /dev/md6 --add /dev/sdc6

N’oubliez pas de vous adapter à votre contexte !

Vous pouvez utiliser cat /proc/mdstat pour suivre la reconstruction. Tout est ok quand il n’y a plus d’underscore au niveau de [UUU], pour chaque partition.

Les backups

Déjà 1 000 mots et nous n’avons pas encore parlé des backups ! Livré à vous-mêmes, il est inconcevable de ne pas avoir un système de backup. Il existe plusieurs formes, l’archive que l’on envoie sur un autre serveur ou que l’on télécharge, le dump de base de données ou carrément de la réplication. Je vous présente ma méthode pour un serveur seul (pas de redondance / failover et donc exit la réplication de BDD SQL, ça ne servirait à rien).
Le point sur mon environnement d’exemple, nous avons :

  • Le serveur principal sous Debian 8 jessie avec MariaDB en système de gestion de bases de données (ça marchera avec du MySQL classique)
  • Un premier serveur de sauvegarde que je nomme S1 en RAID aussi (j’ai pris l’offre Dedishop VPS Backup pour info — je n’ai pas d’avis sur le service pour le moment mais je le trouve assez contraignant, surtout niveau monitoring…)
  • Un deuxième serveur de sauvegarde, un simple Kimsufi sans RAID (où je peux faire ce que je veux en revanche)
    • Il n’est pas dans le même datacenter que mon SYS, c’est toujours plus sécurisant

Je vais sauvegarder mes bases de données et mes applications Web (un WordPress pour l’essentiel). Tout ce petit monde est dans ma partition /var, si vous avez séparé vos applications dans /home, il faudra adapter. La sauvegarde s’exécute via un script bash que l’on glisse en tâche CRON (sauvegarde quotidienne dans mon cas). Voici la procédure que j’ai mise en place :

  • Je réalise d’abord un dump de mes bases de données
    • Pour accéder à mes bases, je passe par un utilisateur spécial qui a accès à toutes les bases seulement avec les privilèges « lock » et « select »
  • Puis je les envoies au serveur de sauvegarde S1
  • Puis je les envoies au serveur de sauvegarde S2
  • Puis je fais un RSync de mes sites vers S1
  • Puis je fais un RSync de mes sites vers S2
  • J’envoie un mail pour me prévenir si tout est ok ou si la procédure a rencontré des problèmes avec les logs qui vont bien

Personnellement, je trouve la notification mail essentielle, que cela se passe bien ou non. Pourquoi je voudrai savoir que tout se passe bien ? Car l’absence de notification ne signifie pas que tout se passe bien. Comment ça je ne suis pas clair ?! C’est très simple, ce mail est pour moi une garantie que mon script a fait le job. Si vous n’êtes prévenu que lorsqu’il y a un pépin, vous pourriez penser que tout est ok car vous ne recevez aucun email, mais si le script ne se lance tout simplement pas ? Votre pensée est alors fausse.

Pour éviter cette dramatique erreur (car cela signifie que vous n’avez pas de backups !), cet email de « succès » va me permettre de déduire qu’il y a un problème dès lors que je ne le reçois tout simplement pas et j’en connaîtrai la cause probable (la tâche CRON).

Le script pour le backup

Bref, passons aux choses sérieuses. Je place mon script dans /usr/local/bin/, les avis divergent là-dessus, tant que vous ne le mettez pas n’importe où, vous pouvez conserver vos habitudes. Vous utiliserez plusieurs dossiers qui n’existent pas pour le moment, il faudra les créer (mkdir). Avant de vous jeter sur la suite, lisez bien et adaptez-vous à votre contexte.

nano /usr/local/bin/monsuperbackup.sh

Et voici ce qu’il y a à l’intérieur, je commente tout pour que vous compreniez bien :

#!/bin/sh
# Récupération de l’heure en seconde pour l’email de notification, vous allez voir c’est super 🙂
START_TIME=$SECONDS #$SECONDS est une variable de shell, c’est l'heure du serveur en secondes

#Paramétrage de l’accès aux BDD (mysqldump ne fonctionne pas sur un serveur distant)
HOSTNAME='localhost'
USER='monutilisateurbackup'
PASSWD='monsupermotdepasse'

# Chemin de sauvegarde des BDD - pensez à créer le dossier au besoin
BACKUP_FOLDER='/var/saves'

# Récupération de la date pour l’utiliser dans le nom des dumps SQL et les logs
DATE=`date +%Y-%m-%d`

# Je récupère les bases de données disponibles - j’exclue celles internes au serveur que je backup manuellement - puis pour chacune, je réalise mes actions 
for i in $(echo 'SHOW DATABASES;' | mysql --user $USER -p$PASSWD -h $HOSTNAME | grep -v '^Database$' | grep -v 'schema' | grep -v '^mysql$' ); do
# Exportation de chaque base que j’enregistre dans un dossier tempdb à créer au préalable. Les logs vont dans un dossier dump dans /var/log, à créer aussi.
if (mysqldump --user $USER -p$PASSWD -h $HOSTNAME --log-error="/var/log/dump/dump_"$i"_"$DATE".log" --opt $i > $BACKUP_FOLDER"/tempdb/"$DATE"_"$i".sql"); then
# Si le dump a fonctionné, je le compresse sous gzip. Vous devez le faire en deux temps pour pouvoir capter le statut du dump (échec ou succès)
gzip -9c $BACKUP_FOLDER"/tempdb/"$DATE"_"$i".sql" > $BACKUP_FOLDER"/db/"$DATE"_"$i".sql.gz";
# J’enregistre le statut pour le mail, ici c’est réussi
DUMP_STATUS=$DUMP_STATUS'Dump de la BDD '$i' › succès\n';
# Suppression des bases SQL gzippées qui ont plus de 7 jours si et seulement si le dernier dump a fonctionné
# Et oui, ce serait dommage de les supprimer si vos dumps déconnent ! Autant garder ce qui a marché même si c’est vieillot
# Si vous avez beaucoup d’espace disque, vous pouvez en conserver beaucoup plus, je réserve ça à mes serveurs de sauvegardes qui les conservent 30j et 60j
# Je télécharge ces backups régulièrement pour les conserver chez moi et sur une clé USB que j’ai toujours avec moi
find $BACKUP_FOLDER/db/*$i.sql.gz -mtime +7 -exec rm {} \;
else
# C’est l’échec, j’enregistre le statut pour le mail
DUMP_STATUS=$DUMP_STATUS'Dump de la BDD '$i' › échec\n'
# J’enregistre un statut échec pour l’objet et la phrase d’intro du mail
DUMP_PASS='échec';
# Je prépare l’envoi du ou des logs dans le mail
DUMP_MAIL_LOG=$DUMP_MAIL_LOG" -A /var/log/dump/dump_"$i"_"$DATE".log"
fi
# Purge des bases SQL temporaires
rm $BACKUP_FOLDER/tempdb/*$i.sql ;
done;

# Envoi de mes BDD sur mon serveur S1 - Là encore des dossiers à créer
# J’utilise une clé SSH pour me connecter, sans passphrase car moins emmerdant, évidemment mon utilisateur n’a aucun droit root
if scp -i "/root/.ssh/maclessh" -r /var/saves/db/ monutilisateurnonroot@ipserveur1:/home/monutilisateurnonroot/saves/mesapplis/ ; then
# J’enregistre le statut pour le mail
SCP_PASS_S1='succès';
else
# J'enregistre le statut pour le mail
SCP_PASS_S1='échec';
fi

# Envoi de mes BDD sur mon serveur S2
if scp -i "/root/.ssh/maclessh" -r /var/saves/db/ monutilisateurnonroot@ipserveur2:/home/monutilisateurnonroot/saves/mesapplis/ ; then
# J’enregistre le statut pour le mail
SCP_PASS_S2='succès';
else
# J’enregistre le statut pour le mail
SCP_PASS_S2='échec';
fi

# RSync de mes sites sur mon serveur S1 - Log dans un dossier rsync situé dans /var/log, à créer
# Je ne détaille pas le dossier de destination, à vous d’adapter à vos besoins
# Le point sur les paramètres
# a pour activer le mode archive
# c pour utiliser la somme de contrôle plutôt que la date ou la taille — c’est mieux
# t pour conserver les dates de modifications
# r pour une copie récursive (prend aussi les sous-dossiers)
# v pour avoir des logs qui ressemble à quelque chose
# z pour compresser les données envoyées
if rsync -actrvz --log-file="/var/log/rsync/rsync_ws_s1_"$DATE".log" -e "ssh -i /root/.ssh/maclessh" /var/www/ monutilisateurnonroot@ipserveur1:/home/monutilisateursnonroot/saves/... ; then
# J’enregistre le statut pour le mail
RSYNC_PASS_S1='succès';
else
# J’enregistre le statut pour le mail
RSYNC_PASS_S1='échec';
# Je prépare l’envoi du log dans le mail
RSYNC_MAIL_LOG=" -A /var/log/rsync/rsync_ws_s1_"$DATE".log";
fi

# RSync de mes sites sur mon serveur S2
if rsync -actrvz --log-file="/var/log/rsync/rsync_ws_s2_"$DATE".log" -e "ssh -i /root/.ssh/maclessh" /var/www/ monutilisateurnonroot@ipserveur2:/home/monutilisateurnonroot/saves/... ; then
# J’enregistre le statut pour le mail
RSYNC_PASS_S2='succès';
else
# J’enregistre le statut pour le mail
RSYNC_PASS_S2='échec';
# Je prépare l’envoi du log dans le mail
RSYNC_MAIL_LOG=$RSYNC_MAIL_LOG" -A /var/log/rsync/rsync_ws_s2_"$DATE".log";
fi

if [ "$DUMP_PASS" != 'échec' -a "$SCP_PASS_S1" = 'succès' -a "$SCP_PASS_S2" = 'succès' -a "$RSYNC_PASS_S1" = 'succès' -a "$RSYNC_PASS_S2" = 'succès' ] ; then
#Le statut du process pour l’objet du mail si tout a fonctionné
STATUS='Succès';
#La phrase d’introduction
SENTENCE='Le processus de sauvegarde s’est parfaitement déroulé.';
else
#Le statut du process pour l’objet du mail si un truc a foiré
STATUS='Echec';
#La phrase d’introduction
SENTENCE='Il y a eu une erreur durant le processus de sauvegarde.';
fi

#Purge des logs vieux de 3 jours, vous les avez par mail si besoin
find /var/log/rsync/* -mtime +3 -exec rm {} \;
find /var/log/dump/* -mtime +3 -exec rm {} \;

#Je récupère à nouveau $SECONDS auquel je soustrais le premier
#Dans le mail, je peux alors indiquer la durée du processus à titre indicatif
ELAPSED_TIME=$(($SECONDS - $START_TIME))

# Envoi du mail de notification
printf "Bonjour,\n\n\
$SENTENCE\n\n\
Résumé du processus\n\n\
$DUMP_STATUS\n\
Copie des BDD sur S1 › $SCP_PASS_S1\n\
Copie des BDD sur S2 › $SCP_PASS_S2\n\n\
Rsync sur S1 › $RSYNC_PASS_S1\n\
Rsync sur S2 › $RSYNC_PASS_S2\n\n\
Durée du processus : $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec.\n\n\
Monsieur Root" | \
mail -s "[$STATUS] Sauvegarde du serveur" -aFrom:Backup\<adresse@email.fr\> $DUMP_MAIL_LOG$RSYNC_MAIL_LOG destinaire@email.fr;

On permet au fichier de s’exécuter puis on le lance :

chmod +x /usr/local/bin/monsuperbackup.sh
/usr/local/bin/monsuperbackup.sh

Voici un exemple du mail de notification :

Objet : [Succès] Sauvegarde du serveur
Message :
Bonjour,

Le processus de sauvegarde s’est parfaitement déroulé.

Résumé du processus

Dump de la BDD NomBDD1 › succès
Dump de la BDD NomBDD2 › succès
Dump de la BDD NomBDD3 › succès

Copie des BDD sur S1 › succès
Copie des BDD sur S2 › succès

Rsync sur S1 › succès
Rsync sur S2 › succès

Durée du processus : 16 min 45 sec.

Monsieur Root

L’intérêt de mesurer la durée de la procédure est de voir si tout se passe bien au quotidien. Par exemple, je sais que ce temps de 16 min est dû essentiellement à mes rsync qui avaient 5Go à synchroniser (deux fois). Progressivement, vous aurez des repères de temps qui vous permettront de fouiller quand cette durée vous semble étrange (excessive). Ce qui pourrait signifier une lenteur entre deux serveurs ou un rsync qui fait des siennes ou des BDD qui gonflent et gonflent pour lesquelles vous seriez tentés de passer à de la réplication.

Il ne vous reste plus qu’à créer la tâche CRON, je vous épargne les détails, il y a plein de tutos pour ça.

Un backup mais pour en faire quoi ?

Au-delà de faire des sauvegardes, il faut se préparer à les utiliser en cas de pépin. Il est donc important, d’abord de tester vos sauvegardes, je le fais sur un quatrième serveur mais vous pouvez faire ça en local. Mais aussi d’organiser une procédure pour une réinstallation complète de votre serveur sur un environnement susceptible de diverger (hardware essentiellement) de celui défaillant. Pensez aussi à surveiller vos serveurs de backup.