[MÉMO] Duplicity pour mes sauvegardes dans le cloud

Je suis en train de remplacer l’ensemble de mes systèmes de sauvegardes, du WordPress ici présent à mon Nextcloud, en passant par les bases de données des stations météo de Météo06 et tous un tas d’autres choses, par quelque chose de plus simple et plus souple.

A l’origine, j’utilisais principalement RSYNC, pour disperser les backups entre mes différents serveurs, espace de stockage FTP d’OVH, mon NAS QNAP, etc :

Depuis le temps il y a évidemment eu quelques adaptations par rapport à ce que j’avais pu écrire dans ces articles, mais globalement le principe était toujours le même. Sauf que mes serveurs se remplissent, mon NAS aussi, et j’ai donc besoin de les déposer ailleurs, sans pour autant sacrifier le principe du 3-2-1.

Cela fait quelques temps que j’entends parler du logiciel Duplicity. Il permet de faire des backups complets, puis incrémentales, et refaire un backup complet au bout de x jours, et sait aussi les chiffrer avec une clé GPG. C’est donc l’idéal pour envoyer mes sauvegardes vers de l’Object Storage, du Glacier, mais aussi et toujours vers mon NAS.

Il fallait que je m’y mette, j’avais déjà testé il y a quelques mois la sauvegarde de mes photos et mon catalogue Lightroom dans l’Object Storage de Scaleway, avec sa classe Glacier, l’équivalent de ce que l’on trouve chez AWS. Très pratique grâce à son API compatible S3. Et Duplicity sait se servir de S3 🙂

Et donc la semaine dernière nous avons tous appris, que oui, un datacenter entier pouvait partir… en fumée ! En 2017 j’écrivais qu’il fallait être un peu parano pour envoyer ses sauvegardes sur deux endroits géographiquement distincts. Finalement non, et c’est de toute façon une bonne pratique. On voit aujourd’hui que c’est indispensable.

Bref, pas de dégâts avec cet incident OVH de mon côté, par chance, mais aussi car j’avais déjà été assez prévoyant de ce côté là, avec des backups régulier, automatiques et hors-site. Ouf !

Mais cet incident aura au moins l’avantage d’une piqure de rappel, rien de tel pour m’y remettre !

Object Storage Scaleway
Source : https://www.scaleway.com/en/docs/store-object-with-duplicity/

En partant du tuto rédigé par Scaleway, c’est assez facile de déverser des centaines de gigas sur leur Object Storage.

Sur une de mes machines qui tourne encore sous Debian 9, j’ai du installer une ancienne version de Duplicity, la 0.8.15 du 27/07/2020. Les versions d’après posaient soucis lors de la restauration, elles n’arrivaient pas à dé-geler (unfreeze) les données contenues dans Glacier-C14.

Par contre sur ma dernière machine Kimsufi sous Debian 10, pas de problèmes avec la version 0.8.18.

Sauvegardes vers S3 et FTP du NAS

Fichier de configuration :

vim .scw-configrc

# Scaleway credentials keys
export AWS_ACCESS_KEY_ID="<SCALEWAY ACCESS KEY>"
export AWS_SECRET_ACCESS_KEY="<SCALEWAY SECRET ACCESS KEY>"
export SCW_BUCKET="s3://s3.fr-par.scw.cloud/<NAME OF YOUR BUCKET>"

# NAS QNAP
export FTP_PASSWORD="<FTP PASSWORD NAS QNAP"
export FTP_NAS="ftp://ftp-user-duplicity@hostname.nas:port.nas/repertoire"

# GPG Key information
export PASSPHRASE="<YOUR GPG KEY PASSPHRASE>"
export GPG_FINGERPRINT="<YOUR GPG KEY FINGERPRINT>"

# Folder to backup
export SOURCE="/"

# Will keep backup up to 1 month
export KEEP_BACKUP_TIME="1M"

# Will make a full backup every 10 days
export FULL_BACKUP_TIME="10D"

# Log files
export LOGFILE_RECENT="/var/log/duplicity/logfile-recent.log"
export LOGFILE="/var/log/duplicity/logfile.log"
export LOGFILETS="/var/log/duplicity/check.ts"

# MYSQL files
export MYSQL_USER='<USER>'
export MYSQL_PASS='<PWD>'
# MYSQL EXCLUSIONS
export MYSQL_EXCLUSIONS="(information_schema|performance_schema)"
# Répertoire de stockage MYSQL
export MYSQL_DIR="/var/backup/duplicity_mysql"

log () {
    date=`date +%Y-%m-%d`
    hour=`date +%H:%M:%S`
    echo "$date $hour $*" >> ${LOGFILE_RECENT}
}
export -f log

Script de backup :

vim scw-backups.sh

#!/bin/bash
source <DIRECTORY>/.scw-configrc

currently_backuping=$(ps -ef | grep duplicity  | grep python | grep ${SCW_BUCKET} | wc -l)

# Mail ERROR
function cleanup {
        cat ${LOGFILE_RECENT} | mail -s "BCKP description" -r "Backup<example@example.fr>" example@example.fr
        touch ${LOGFILETS}
}
trap cleanup EXIT




if [ $currently_backuping -eq 0 ]; then
        # Clear the recent log file
        cat /dev/null > ${LOGFILE_RECENT}

        # Create directory for backup SQL
        log ">>> CREATE DIRECTORY MySQL"
        mkdir ${MYSQL_DIR}

        # Dump MySQL
        log ">>> DUMP MySQL"
        # On place dans un tableau le nom de toutes les bases de données du serveur
        databases="$(mysql -u $MYSQL_USER -p$MYSQL_PASS -Bse 'show databases' | grep -v -E $MYSQL_EXCLUSIONS)"
        # Dump chaque BDD
        for database in ${databases[@]}
        do
                log ">>> Dump : $database"
                mysqldump -u $MYSQL_USER -p$MYSQL_PASS --quick --add-locks --lock-tables --extended-insert --ignore-table=mysql.event --databases $database > ${MYSQL_DIR}/${database}.sql
        done

        # Duplicity vers Scaleway C14
        log ">>> BACKUP TO OBJECT STORAGE SCALEWAY (COLD C14)"
        duplicity \
        incr --full-if-older-than ${FULL_BACKUP_TIME} \
          --asynchronous-upload \
          --volsize 512 \
          --s3-use-glacier \
          --encrypt-key=${GPG_FINGERPRINT} \
          --sign-key=${GPG_FINGERPRINT} \
          --exclude-filelist <DIRECTORY>/excludeList.txt \
          ${SOURCE} \
          ${SCW_BUCKET} >> ${LOGFILE_RECENT} 2>&1


        # Duplicity vers NAS QNAP
        log ">>> BACKUP TO NAS QNAP"
        duplicity \
        incr --full-if-older-than ${FULL_BACKUP_TIME} \
          --asynchronous-upload \
          --volsize 512 \
          --encrypt-key=${GPG_FINGERPRINT} \
          --sign-key=${GPG_FINGERPRINT} \
          --exclude-filelist <DIRECTORY>/excludeList.txt \
          ${SOURCE} \
          ${FTP_NAS} >> ${LOGFILE_RECENT} 2>&1


        # Remove old backups
        log ">>> REMOVING OLD BACKUPS SCW"
        duplicity remove-older-than ${KEEP_BACKUP_TIME} --force ${SCW_BUCKET} >> ${LOGFILE_RECENT} 2>&1
        log ">>> REMOVING OLD BACKUPS FTP NAS"
        duplicity remove-older-than ${KEEP_BACKUP_TIME} --force ${FTP_NAS} >> ${LOGFILE_RECENT} 2>&1


        # Remove old MYSQL dump
        log ">>> REMOVING OLD DUMP MySQL"
        rm -rf ${MYSQL_DIR}


        # Copie du dernier log dans le log général
        cat ${LOGFILE_RECENT} >> ${LOGFILE}
fi

Le fichier d’exclusion/inclusion de Duplicity :

vim excludeList.txt

**[Cc]ache*
**[Hh]istory*
**[Ss]ocket*
**[Tt]humb*
**[Tt]rash*
/var/www/mon/site/*/web/stats/**
+ /var/www/mon/site/example/web
+ /home/scripts
+ /home/user
+ /var/lib/munin
+ /etc
+ /var/spool/cron/crontabs
+ /var/backup/duplicity_mysql
**

Et enfin, le script de restauration :

vim scw-restore.sh

#!/bin/bash
source <DIRECTORY>/.scw-configrc

        if [ $# -lt 2 ]; then
                echo -e "Usage $0 <time or delta> [file to restore] <restore to>
        Exemple:
        \t$ $0 2018-7-21 recovery/  ## recovers * from closest backup to date
        \t$ $0 0D secret data/  ## recovers most recent file nammed 'secret'";
        exit; fi

        if [ $# -eq 2 ]; then
                duplicity \
                --time $1 \
                ${SCW_BUCKET} $2
        fi

        if [ $# -eq 3 ]; then
                duplicity \
                --time $1 \
                --file-to-restore $2 \
                ${SCW_BUCKET} $3
        fi

Backup NextCloud vers Object Storage S3

Pour le NextCloud, je suis encore obligé de faire une copie locale sur la machine, avant de l’exporter ailleurs. Sans ça, on rend l’instance indisponible trop longtemps . Obligation d’activer le mode maintenance pendant tout le process de backup pour que la BDD et les fichiers restent synchro.

Fichier de configuration :

vim .scw-configrc

# Scaleway credentials keys
export AWS_ACCESS_KEY_ID="<SCALEWAY ACCESS KEY>"
export AWS_SECRET_ACCESS_KEY="<SCALEWAY SECRET ACCESS KEY>"
export SCW_BUCKET="s3://s3.fr-par.scw.cloud/<NAME OF YOUR BUCKET>"

# GPG Key information
export PASSPHRASE="<YOUR GPG KEY PASSPHRASE>"
export GPG_FINGERPRINT="<YOUR GPG KEY FINGERPRINT>"

# Folder to backup
export SOURCE="/"

# Will keep backup up to 1 month
export KEEP_BACKUP_TIME="14D"

# Will make a full backup every 10 days
export FULL_BACKUP_TIME="7D"

# Log files
export LOGFILE_RECENT="/var/log/duplicity/nextcloud/logfile-recent.log"
export LOGFILE="/var/log/duplicity/nextcloud/logfile.log"
export LOGFILETS="/var/log/duplicity/nextcloud/check.ts"

# Nom base de données nextcloud
export NCBDD='<BDD_NAME>'
#Propriétaire base de données nextcloud
export NCUSER='<USER>'
# Mot de passe de la base de données nextcloud
export NCPWD='<PWD>'

# Répertoires
export NCDIR='/var/www/nextcloud'
export NCDIR_BCKP='/home/bckp_nextcloud/backup_dir/www'
export FILE_BDD='/home/bckp_nextcloud/backup_dir/bdd/nextcloud.sql'

log () {
    date=`date +%Y-%m-%d`
    hour=`date +%H:%M:%S`
    echo "$date $hour $*" >> ${LOGFILE_RECENT}
}
export -f log

Script de backup :

vim scw-backups.sh

#!/bin/bash
source <DIRECTORY>/.scw-configrc

currently_backuping=$(ps -ef | grep duplicity  | grep python | grep "${SCW_BUCKET}" | wc -l)

# Mail ERROR
function cleanup {
        cat ${LOGFILE_RECENT} | mail -s "BCKP NextCloud --> C14-S3" -r "BCKP-NC<backup@example.fr>" backup@example.fr
        touch ${LOGFILETS}
}
trap cleanup EXIT



if [ $currently_backuping -eq 0 ]; then

        # Clear the recent log file
        cat /dev/null > ${LOGFILE_RECENT}

        # Activation du mode maintenance Nextcloud
        log ">>> NC MAINTENANCE ON"
        su -m nextcloud -c 'php ${NCDIR}/occ maintenance:mode --on' >> ${LOGFILE_RECENT} 2>&1

        # Dump BDD
        log ">>> START DUMP BDD"
        mysqldump \
          --single-transaction \
          -u ${NCUSER} \
          -p${NCPWD} \
          $NCBDD \
          --default-character-set=utf8mb4 > ${FILE_BDD}
        log ">>> END DUMP BDD"


        # Copie locale du répertoire
        log ">>> START COPIE LOCALE RSYNC"
        su -m nextcloud -c 'rsync -Aax --info=progress2 --delete-after ${NCDIR}/ ${NCDIR_BCKP}/' >> ${LOGFILE_RECENT} 2>&1
        log ">>> END COPIE LOCALE RSYNC ######"

        # Désactivation du mode maintenance
        log ">>> NC MAINTENANCE OFF"
        su -m nextcloud -c 'php ${NCDIR}/occ maintenance:mode --off' >> ${LOGFILE_RECENT} 2>&1


        # Bckp duplicity to SCALEWAY C14
        log ">>> BACKUP TO OBJECT STORAGE SCALEWAY (COLD C14)"
        duplicity \
                incr --full-if-older-than ${FULL_BACKUP_TIME} \
                --asynchronous-upload \
                --volsize 512 \
                --s3-use-glacier \
                --encrypt-key=${GPG_FINGERPRINT} \
                --sign-key=${GPG_FINGERPRINT} \
                --include=${FILE_BDD} \
                --include=${NCDIR_BCKP} \
                --exclude=/** \
                ${SOURCE} \
                ${SCW_BUCKET} >> ${LOGFILE_RECENT} 2>&1


        # Remove old backup
        log ">>> REMOVING OLD BACKUPS"
        duplicity remove-older-than ${KEEP_BACKUP_TIME} --force ${SCW_BUCKET} >> ${LOGFILE_RECENT} 2>&1


        # Remove BDD bckp
        log ">>> REMOVE BCKP BDD LOCAL"
        rm -rf ${FILE_BDD}

        cat ${LOGFILE_RECENT} >> ${LOGFILE}
fi

Le script de restauration est identique.


Nous voilà relativement tranquille 😉


Petite précision : Les objets stockés dans l’Object Storage de Scaleway ne sont pas (encore ?) répliqués dans deux lieux géographiquement distincts (vous trouverez plus de détails techniques sur l’architecture des produits Scaleway sur leur excellent article de blog). On peut donc dire qu’il ne s’agit que d’une seule copie de vos fichiers. Il faut encore trouver au moins un autre « lieu » de stockage pour un second backup.