Le serveur hébergeant entre autres Moodle et GLPI est une machine virtuelle située dans notre établissement.

Voici notre politique de sauvegarde et les scripts associés.

Principe de la sauvegarde

Chaque nuit, les BDD et les répertoires critiques de mon serveur sont copiés sur un espace disque partagé et sécurisé. Il en résulte donc un répertoire par jour d’environ 200 Go avec une navigation rapide dans l’arborescence si nécessaire.

Chaque semaine, ces même sauvegardes (mais pas seulement) sont copiées sur un disque externe, puis mise dans un coffre fort situé dans un autre bâtiment. Deux disques jouent ce rôle l’un après l’autre.

Il en résulte donc 3 semaines de sauvegarde facilement accessibles et relativement bien protégées.

J’ai hérité de scripts de sauvegarde que j’ai adapté à mes besoins pour gagner en efficacité et rapidité de traitement.

Prérequis

Un répertoire réseau partagé

Les copies seront dans un premier temps placées dans un répertoire réseau  partagé avec accès par un utilisateur en écriture correspondant à la machine qui crée la sauvegarde.

Un utilisateur MySQL dédié

Par sécurité, chaque base de donnée à un accès en écriture par un utilisateur différent (je n’utilise pas l’utilisateur root…). Je crée donc un utilisateur « dump » dédié à la tache de copier la base et qui n’a besoin que d’un accès en lecture à chacune des base.

mysql -u root -p -e "CREATE USER 'dump'@'localhost' IDENTIFIED BY 'LEMOTDEPASSE';"
mysql -u root -p -e "GRANT SELECT , SHOW DATABASES , LOCK TABLES , SHOW VIEW ON * . * TO 'dump'@'localhost' IDENTIFIED BY 'LEMOTDEPASSE' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;"

Les fichiers qui vont bien

Script de sauvegarde

Petite explication des étapes :

  1. D’abord, on déclare les variables de travail.
  2. Je demande que les erreurs soient logguées dans un fichier error.log.
  3. D’un autre coté, je logue chaque étape dans un fichier backup.log avec les horaires correspondants.
  4. Je baisse la priorité de l’action de manière à ce que si des utilisateurs veulent accéder au serveur, il puisse répondre convenablement.
  5. Pour le dump des bases de données, la seule particularité réside dans le fait que je dump en local pour en faire une copie des fichiers ensuite : charge le serveur moins longtemps.
  6. Je monte ensuite le disque de manière faire mes copies.
  7. Pour la copie des fichiers, j’utilise rsync qui fait une copie « simple » lors de la première sauvegarde (4h30 pour 200 Go, soit les 7 premiers jours) mais qui ensuite ne fera qu’une synchronisation (durée ramenée à 30 minutes environ). Dans les options de rsync, j’utilise selon les cas :
    • –stats : affiche les statistiques de l’opération
    • –progress : affiche la progression de l’opération
    • –copy-links : copie les fichiers cibles des liens symbolique en tant que fichiers
    • –delete-after : qui supprime sur la destination les fichiers ayant été supprimés dans la source, en fin d’opération.
    • –exclude-from : qui indique un fichier texte dans lequel j’écris les répertoires et fichiers à ne pas synchroniser.
  8. Je copie les fichiers de log sur le répertoire distant.
  9. Je démonte le disque réseau.
  10. J’envoie un rapport par mail.

sauvegarde.sh

#!/bin/bash

# Création des variables
# Globales
MAIL='mail@domaine.fr'
HOSTNAME='computer name'
SAVE_DIR='//192.168.1.1/backup'
MOUNTED_DIR='/mnt/sauvegarde'
LOCAL_SQL_DIR='/home/scripts/dump_sql'
LOG_FILE='/home/scripts/backup.log'
ERROR_LOG_FILE='/home/scripts/error.log'
# User for external disk usage
EXT_USER='user'
EXT_PASS='password'
EXT_DOMAIN='domain'
# SQL user informations
SQL_USER='dump'
SQL_PASS='pass_sql'
SQL_EXCLUSION='(information_schema|performance_schema)'

# Dates
NOW=$(date +"%Y-%m-%d")
DAY=$(date +"%A")
MOUNTH=$(date +"%m")
WEEK=$(date +"%V")
YEAR=$(date +"%Y")

# Destination directory
DEST_DIR=$MOUNTED_DIR/$DAY 

# Error log
echo "Error log for $NOW :">$ERROR_LOG_FILE
exec 2>> $ERROR_LOG_FILE

# Low priority
ionice -c3 -p$$ &>/dev/null
renice -n 19 -p $$ &>/dev/null

## Let's go
echo "Backup for $NOW on $HOSTNAME :">$LOG_FILE
echo " ">>$LOG_FILE

# Local MySQL dump
echo "Local MySQL dump">>$LOG_FILE

# Make local directory if not exist
 mkdir -p $LOCAL_SQL_DIR;
 
 # Get bases name
 databases="$(mysql -u$SQL_USER -p$SQL_PASS -Bse 'show databases' | grep -v -E $SQL_EXCLUSION)"
 
 # Dump
 for database in ${databases} 
 do
 echo "$(date +"%H:%M:%S") : dump $database">>$LOG_FILE
 mysqldump -u$SQL_USER -p$SQL_PASS $database > $LOCAL_SQL_DIR/${database}.sql
 done

# End local MySQL dump
echo "$(date +"%H:%M:%S") : dump finished">>$LOG_FILE
echo "------------------------------------">>$LOG_FILE

# Directory sync 
echo "Files sync">>$LOG_FILE

# Make mounted directory if not exist
 mkdir -p $MOUNTED_DIR;

# Mount
 echo "$(date +"%H:%M:%S") : mount $MOUNTED_DIR">>$LOG_FILE
 mount -t cifs -o username=$EXT_USER,pass=$EXT_PASS,domain=$EXT_DOMAIN $SAVE_DIR $MOUNTED_DIR

# Make destination directory if not exist
 mkdir -p $DEST_DIR;

# Sync files
 echo "$(date +"%H:%M:%S") : sync $LOCAL_SQL_DIR">>$LOG_FILE
 rsync -a --stats --progress --delete-after $LOCAL_SQL_DIR $DEST_DIR

echo "$(date +"%H:%M:%S") : sync /var/www/html">>$LOG_FILE
 rsync -a --stats --progress --exclude-from=/home/scripts/excludes_html.txt --delete-after /var/www/html $DEST_DIR

echo "$(date +"%H:%M:%S") : sync /root">>$LOG_FILE
 rsync -a --stats --progress --copy-links --delete-after /root $DEST_DIR

echo "$(date +"%H:%M:%S") : sync /etc">>$LOG_FILE
 rsync -a --stats --progress --copy-links --delete-after /etc $DEST_DIR

echo "$(date +"%H:%M:%S") : sync /home/moodledata">>$LOG_FILE
 rsync -a --stats --progress --exclude-from=/home/scripts/excludes_moodledata.txt --delete-after /home/moodledata $DEST_DIR

# End directory sync
echo "$(date +"%H:%M:%S") : sync finished">>$LOG_FILE
echo "------------------------------------">>$LOG_FILE

# Synchronisation des buffers des disques 
sync

# Log file copy
echo "$(date +"%H:%M:%S") : copy log file">>$LOG_FILE
cp $LOG_FILE $DEST_DIR/
echo "$(date +"%H:%M:%S") : copy error file">>$LOG_FILE
cp $ERROR_LOG_FILE $DEST_DIR/

# Umount 
echo "$(date +"%H:%M:%S") : unmount $MOUNTED_DIR">>$LOG_FILE
umount $MOUNTED_DIR

# Ended
echo "$(date +"%H:%M:%S") : -- Backup done ! --">>$LOG_FILE

# Send mail report
mail -s "$HOSTNAME : $NOW backup report" $MAIL < $LOG_FILE
mail -s "$HOSTNAME : $NOW error log" $MAIL < $ERROR_LOG_FILE

 

Fichier d’exclusion pour le répertoire Moodledata

J’enlève les fichiers bak et log, les fichiers de sessions et temporaires de moodle (peut être un héritage des anciennes versions d’ailleurs).

excludes_moodledata.txt

*.bak
*.log
/moodledata/sessions
/moodledata/temp

Fichier d’exclusion pour le répertoire html

J’enlève les fichiers bak et log, les fichiers de sessions de GLPI, et quelques répertoires situés dans /var/www/html commençant par _.

excludes_html.txt

*.bak
*.log
/html/glpi/files/_sessions
/html/_*

Automatisation

Après essais et ajustement, et surtout vérification que les fichiers de la sauvegarde soient bien présents et utilisables, je demande à un cron d’exécuter ce script chaque nuit à 23h00.

00 23 * * * sh /home/scripts/sauvegarde.sh

Édition :

  • Edit suite à la remarque de Cascador : mkdir -p

Sources d’inspiration :

  • http://david.mercereau.info/script-de-sauvegarde-mysql-par-base-mysql_dump-sh/
  • http://linuxfocus.org/Francais/March2004/article326.shtml
Catégories : Administration

10 commentaires

Cascador · 22 mars 2017 à 9 h 04 min

Salute,

Ceci est inutile « if [ ! -d $LOCAL_SQL_DIR ]; then » puisque mkdir -p crée justement le répertoire si il n’existe pas et ne fait rien si il existe déjà.

-p, –parents : créer des répertoires parent (répertoires intermédiaires) si nécessaire, sans générer d’erreur s’ils existent

Tcho !

    Éric B. · 22 mars 2017 à 11 h 50 min

    Merci !
    Je prends et je simplifie mon script.
    Éric

xhark · 22 mars 2017 à 13 h 15 min

Pour avoir fait quelque chose un peu dans le même esprit je te mets l’URL au cas où 🙂 http://blogmotion.fr/systeme/backup-bm-blog-13132

Cascador · 22 mars 2017 à 14 h 47 min

Salute,

Si ça t’intéresse d’aller plus loin, il vaut mieux privilégier printf à echo :
http://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo
http://wiki.bash-hackers.org/commands/builtin/printf

J’ai l’impression que tu as des variables de dates qui ne te servent à rien donc besoin de faire le ménage. C’est une possibilité d’utiliser HOSTNAME=’computer name’ beaucoup utilisent HOSTNAME=’$(hostname)’ (qui est donc le résultat de la commande hostname).

Tcho !

    Éric B. · 22 mars 2017 à 16 h 51 min

    Yes, pour les dates… je voulais jouer aussi sur des sauvegardes semaines (4), mensuelles (12), annuelle (1)… Mais je souffre pour l’instant d’un manque de place pour mes sauvegardes 🙁
    Pour le coup du hostname, je vais prendre aussi, je pense…

NIco · 22 mars 2017 à 16 h 06 min

Alors moi au début c’était comme ça aussi… et puis j’ai décourvert backup-ninja !

Tetsumaki · 22 mars 2017 à 21 h 26 min

Salut,

J’utilise des scripts du même genre mais je pipe le dump avec gunzip afin d’obtenir un .sql.gz, cela fait gagner énormément d’espace en fonction du type de donnée que tes bases accueillent. Je déconseille d’essayer avec xz qui est beaucoup trop lent et ne fait pas gagner énormément d’espace pour ce type d’opération.

    Éric B. · 23 mars 2017 à 7 h 29 min

    Rsync peut compresser aussi, mais j’avoue ne pas avoir essayé.
    Pour l’instant, le serveur que j’ai est un peu à la ramasse, alors j’essaye surtout de sauvegarder sans trop surcharger !
    Mais pour les BDD ça peut le faire, j’ai pas vraiment besoin de fouiller dedans trop souvent…
    Éric

Laisser un commentaire

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

J’accepte les conditions et la politique de confidentialité