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 :
- D'abord, on déclare les variables de travail.
- Je demande que les erreurs soient logguées dans un fichier error.log.
- D'un autre coté, je logue chaque étape dans un fichier backup.log avec les horaires correspondants.
- 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.
- 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.
- Je monte ensuite le disque de manière faire mes copies.
- 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.
- Je copie les fichiers de log sur le répertoire distant.
- Je démonte le disque réseau.
- 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