From 4074d0a6adbdef0591578f477d9068a07ba5fd94 Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Thu, 18 Nov 2021 15:14:25 +0100 Subject: Sauvegardes avec duplicity --- content/Informatique/2022-09-cold.rst | 372 +++++++++++++++++++++++++++ content/images/ovh_cold/config.png | Bin 0 -> 13672 bytes content/images/ovh_cold/degel.png | Bin 0 -> 14892 bytes content/images/ovh_cold/user.png | Bin 0 -> 28968 bytes content/resources/backup.sh | 100 ------- content/resources/backup/gen_config.sh | 88 +++++++ content/resources/backup/gen_config_local.sh | 58 +++++ 7 files changed, 518 insertions(+), 100 deletions(-) create mode 100644 content/Informatique/2022-09-cold.rst create mode 100644 content/images/ovh_cold/config.png create mode 100644 content/images/ovh_cold/degel.png create mode 100644 content/images/ovh_cold/user.png delete mode 100644 content/resources/backup.sh create mode 100755 content/resources/backup/gen_config.sh create mode 100755 content/resources/backup/gen_config_local.sh diff --git a/content/Informatique/2022-09-cold.rst b/content/Informatique/2022-09-cold.rst new file mode 100644 index 0000000..00c265c --- /dev/null +++ b/content/Informatique/2022-09-cold.rst @@ -0,0 +1,372 @@ +.. -*- mode: rst -*- +.. -*- coding: utf-8 -*- + +==================================== +Les sauvegarde avec ovh et duplicity +==================================== + +:date: 3020-09-02 +:tags: sauvegarde, gpg +:status: draft + +Le problème avec les sauvegardes est de savoir où les mettre. La plupart du +temps nous n’en avons pas besoin (et le plus longtemps possible souhaitons le…) +et les supports sur lesquels nous les entreposons vieillissent. À noter qu’il +est également recommandé de réaliser doubler les sauvegardes (principe 3-2-1), +ce qui s’avère fastidieux si l’on fait cela nous même à la maison. + +Une solution est de mettre les sauvegarde dans le cloud, mais cela implique +alors un problème de sécurité : tout ce qui est en ligne est publique. De plus, +cela met dans la balance la question du cout des sauvegardes, toutes les +solutions ne se valent pas, et le volume n’et pas négligeable… + +Une solution pour cela est d’utiliser le principe des sauvegardes à froid : les +sauvegardes sont en quelque sorte *givrées* et ne sont pas disponibles de +suite en cas de besoin (quelques heures sont nécessaires pour les récupérer). +Par contre, l’avantage est le cout qui est beaucoup moins onéreux qu’un espace +de stockage disponible en ligne. Voici par exemple les tarifs pour le stockage +à froid chez OVH : + +=========================== ======================== +Action Prix +=========================== ======================== +Stockage froid sécurisé 0,002 € HT/mois/Go +Trafic entrant 0,01 € HT/Go +Trafic sortant 0,01 € HT/Go +=========================== ======================== + +L'inconvénient étant une non-disponibilité immédiate des fichiers archivés. +Cela en soit ne pose pas de problème pour sauvegarder ses photos, mais cela +empêche de mettre en place une sauvegarde incrémentale, puisqu'il est +nécessaire pour l'outil de sauvegarde de pouvoir comparer la liste des fichiers +archivés afin de savoir lesquels mettre à jour. + +Il faut donc trouver une solution qui, en plus de chiffrer les données avant de +les envoyer en ligne, conserve un moyen de mettre à jour les sauvegardes sans +avoir besoin de les dégeler avant. + +.. contents:: + :depth: 1 + +Présentation de la solution +=========================== + +Utiliser le stockage à froid pour y mettre les archives, et le stockage à chaud +pour toutes les données d'index. De cette manière l'application est capable de +récupérer les métadonnées afin d'identifier les données à mettre à jour en +temps réel, et la récupération des sauvegardes, elle, se fera uniquement sur +les données ayant été dégelées. + +Voir le guide d'OVH disponible à l'adresse suivante : https://docs.ovh.com/gb/en/storage/pca/duplicity/ + +Création des accès +================== + +Création +-------- + +Commencer par se créer un compte sur l'environnement d'OVH + +.. image:: {static}/images/ovh_cold/user.png + :class: floatright + :alt: Création d’un nouvel utilisateur + +Lors de la création de l'utilisateur, s'assurer que le role `ObjectStore +Operator` est bien activé. Il n'est pas nécessaire que notre utilisateur ait +tous les roles (nous voulons juste faire une sauvegarde). + +Identifiant +----------- + +.. image:: {static}/images/ovh_cold/config.png + :class: floatleft + :alt: Récupération de l’identifiant + +L'ensemble des informations du compte peuvent ensuite être récupérées dans un +fichier texte (sauf le mot de passe qui est à noter), ce fichier va nous servir +à générer la configuration de duplicity + +Configuration +============= + +Pour générer la configuration, le script `gen_config.sh` va produire le fichier +json avec les valeurs nécessaires (URL, mot de passe, identifiant etc) + +.. admonition:: Sécurité + :class: danger + + Attention ! Le fichier json contient le mot de passe en clair, attention à ne + pas conserver le fichier, ou restreindre les droits afin d’empêcher que + celui-ci ne soit exposé. + +.. figure:: {static}/images/mimetypes/application-x-executable.png + :alt: get the file + :align: center + :target: {static}/resources/backup/gen_config.sh + + Télécharger + +Il faut lui donner en paramètre le fichier téléchargé, et la configuration à +lancer : + +.. code-block:: console + + $ ./gen_config.sh ./user_duplicity.sh config.json backup_name + Please enter your OpenStack Password: XXXXX + $ + +Notre fichier json va devenir la destination de nos sauvegardes. Puisqu’il +contient toutes les directives nécessaires, il suffit d’indiquer à duplicity de +l’utiliser comme emplacement de sauvegarde et l’application sera capable +d’orienter les fichiers vers le stockage à froid ou non automatiquement. + +Sauvegarder et consulter les sauvegardes +======================================== + +Sauvegarde +---------- + +Une fois le script généré, on peut lancer une syncronisation complète avec la +commande suivante : + +.. code-block:: bash + + duplicity \ + --encrypt-key ${enc_key} \ + --sign-key ${sign_key} \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + full ${source} \ + "multi:$(realpath config.json)?mode=mirror&onfail=abort" + +Les mises à jour (delta) uniquement sont lancé en remplaçant :literal:`full` par +:literal:`incremental` : + +.. code-block:: bash + + duplicity \ + --encrypt-key ${enc_key} \ + --sign-key ${sign_key} \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + incremental ${source}" \ + multi:$(realpath config.json)?mode=mirror&onfail=abort" + +Lister les sauvegardes +---------------------- + +Une fois la sauvegarde fait, nous avons la possibilité de consulter les +sauvegarde réalisées en accédant juste aux métadonnées avec la commande +:literal:`collection-status` : + +.. code-block:: console + + $ duplicity \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + collection-status \ + "multi://$(realpath config.json)?mode=mirror&onfail=continue" + MultiBackend: pca://duplicity: 7 files + MultiBackend: swift://duplicity_hot: 14 files + Last full backup date: Wed Sep 2 09:36:13 2020 + Collection Status + ----------------- + Connecting with backend: BackendWrapper + Archive dir: … + + Found 2 secondary backup chains. + … + + Found primary backup chain with matching signature chain: + ------------------------- + Chain start time: Wed Sep 2 09:36:13 2020 + Chain end time: Wed Sep 2 12:01:17 2020 + Number of contained backup sets: 5 + Total number of contained volumes: 0 + Type of backup set: Time: Num volumes: + Incremental Wed Sep 2 09:38:09 2020 0 + Incremental Wed Sep 2 11:32:19 2020 0 + Incremental Wed Sep 2 12:00:56 2020 0 + Incremental Wed Sep 2 12:01:17 2020 0 + ------------------------- + No orphaned or incomplete backup sets found. + +Contrôles et restauration +========================= + +Pour les opérations de contrôles et de restauration, je propose de commencer +par récupérer les fichiers gelés et les conserver en local. De cette manière +nous pourrons : + +1. Contrôler l’intégriter de la sauvegarde +2. Procéder à une restauration de test de celle-ci. + +Dégeler les fichiers en masse +----------------------------- + +L’interface d’OVH permet de dégeler un fichier, mais celle-ci n’est pas +pratique pour procéder à cette opération en masse. Pour ce faire, nous allons +nous connecter en SFTP directement, pour lancer l’opération de récupération. + +.. image:: {static}/images/ovh_cold/degel.png + :class: floatright + :alt: Attente du dégel des données + +Il faut se connecter au serveur suivant : + +.. code-block:: bash + + . user_duplicity.sh + sftp pca@gateways.storage.${OS_REGION_NAME}.cloud.ovh.net + +Le mot de passe de l'utilisateur est composé ainsi : + +.. code-block:: bash + + ${OS_TENANT_NAME}.${OS_USERNAME}.${OS_PASSWORD} + +.. admonition:: Compte d’accès + :class: note + + L’utilisateur est toujours :literal:`pca`, c’est à travers le mot de passe + qu’OVH identifie les ressources auxquelles nous sommes censer accéder. + +Une fois connecté en sftp, il est possible de lancer le dégel de l'ensemble des +fichiers dans un répertoire donné sans passer par l'interface. Cela peut être +utile pour préparer une restauration (duplicity stocke ses fichiers en lots de +200Mo) + +.. code-block:: console + + + > cd backup_name + > get * + +La commande va échouer (impossible de récupérer les fichiers car ceux-ci sont +gelés), mais la demande sera transmise, et les fichiers vont passer en +préparation pour restauration. + +Création d’une configuration locale +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +De la même manière que nous avions préparé un script pour générer un fichier de +configuration pour duplicity afin de sauver nos données, nous allons générer un +fichier qui va créer une configuration *locale* dans laquelle l’ensemble des +données seront chargées à partir du répertoire dans lequel nous aurons sauvé +nos fichiers. + +De cette manière, nous nous affranchissons des contraintes de temps (et du +réseau) pour toutes les opérations qui ont besoin de charger chaque archive. + +.. figure:: {static}/images/mimetypes/application-x-executable.png + :alt: get the file + :align: center + :target: {static}/resources/backup/gen_config_local.sh + + Télécharger + +Afficher les fichiers sauvegardés +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pour lancer cette commande, il est nécessaire que les fichiers soient dégelés, +ou travailler sur les fichiers locaux. + +.. code-block:: console + + $ duplicity \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + list-current-files \ + "multi:$(realpath config.json)?mode=mirror" + MultiBackend: pca://duplicity: 7 files + MultiBackend: swift://duplicity_hot: 14 files + Synchronizing remote metadata to local cache... + GnuPG passphrase for decryption: + MultiBackend: pca://duplicity: 7 files + MultiBackend: swift://duplicity_hot: 14 files + Last full backup date: Wed Sep 2 09:36:13 2020 + Wed Sep 2 09:28:55 2020 . + Sat Jan 27 17:44:34 2018 fichier1 + Sat Jan 27 17:44:34 2018 fichier2 + +.. admonition:: Mot de passe GPG + :class: note + + Lors de la restauration, duplicity demande le mot de passe GPG. Nous pouvons + valider dans rien saisir à ce moment là car nous utilisons la clef GPG pour + déchiffrer le contenu des archives. + +Il est possible d'ajouter une version spécifique en précisant l'heure de la +sauvegarde souhaitée : + +.. code-block:: console + + $ duplicity \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + list-current-files --time 20200902T072859Z \ + "multi:$(realpath config.json)?mode=mirror" + +Restauration +------------ + +Sans paramètre donnés, c’est la restauration de la dernière sauvegarde qui +est exécutée : + +.. code-block:: console + + $ duplicity \ + --encrypt-key ${enc_key} \ + --sign-key ${sign_key} \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + "multi:$(realpath config.json)?mode=mirror&onfail=continue" \ + $(path/to/restore) + +.. admonition:: Restauration d’une version donnée + :class: note + + Là encore, si nous ajouter la date de la sauvegarde, nous allons récupérer + une version donnée : + + .. code-block:: console + + $ duplicity \ + --encrypt-key ${enc_key} \ + --sign-key ${sign_key} \ + --file-prefix-manifest 'hot_' \ + --file-prefix-signature 'hot_' \ + --file-prefix-archive 'cold_' \ + --time 20200902T072859Z \ + "multi:$(realpath config.json)?mode=mirror&onfail=continue" \ + $(path/to/restore) + + +Utiliser duply +============== + +Duply est une application de sauvegarde basée sur duplicity. Elle permet de +faciliter les commandes et la configuration en fournissant une interface plus +simple — basée sur des fichiers de configuration plutôt que des lignes de +commande à rallonge. Les commandes qui sont présentes ci-dessus restent +valide ! + +Duply permet par contre de sauver sa configuration dans un fichier unique, qui +sera complété à l'aide de variables d'environnement dans bash. Cela permet donc +de le configurer de manière assez simple. + +Restauration +------------ + +Pour restaurer, il suffit de récupérer les fichiers *gelés* les fichiers +d'index peuvent rester sur le cloud. + +Le script de génération des fichiers de configuration a un cas particulier lors +de la commande `RESTORE`, il va en effet génerer un fichier allant chercher les +fichier en local plutôt que sur le serveur d'OVH. diff --git a/content/images/ovh_cold/config.png b/content/images/ovh_cold/config.png new file mode 100644 index 0000000..27e7288 Binary files /dev/null and b/content/images/ovh_cold/config.png differ diff --git a/content/images/ovh_cold/degel.png b/content/images/ovh_cold/degel.png new file mode 100644 index 0000000..73a9fca Binary files /dev/null and b/content/images/ovh_cold/degel.png differ diff --git a/content/images/ovh_cold/user.png b/content/images/ovh_cold/user.png new file mode 100644 index 0000000..c52a7d4 Binary files /dev/null and b/content/images/ovh_cold/user.png differ diff --git a/content/resources/backup.sh b/content/resources/backup.sh deleted file mode 100644 index aaf16f4..0000000 --- a/content/resources/backup.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh -########################################################### -# This script uses rsync to backup directories on a media -# (ex: USB disk) with a copy and incremental method. -########################################################### -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -############################################################ -# -# Written by Spip -# Adapted by Chimrod -# added script path detection -# delete with find -# Version 1.0 -# -############################################################ -#to be nice... -ionice -c3 -p$$ -renice +15 -p $$ -THEDATE=`date +%F_%Hh%M` -#FILE=`dirname $0` -FILE=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"` -LOGFILE=$FILE'/log/sauvegarde' - -SPLIT='=====================================================================' -#check logfile directory - -if ! [ -e $LOGFILE ] -then - /bin/mkdir -p $LOGFILE -fi - -#check if the media is mounted - -if ! [ -e $FILE ] -then - echo "$THEDATE : péripherique non connecté. Pas de sauvegarde possible" >> "$LOGFILE/save.log" - echo $SPLIT >> "$LOGFILE/save.log" - exit 0 -fi - -#prevent a second task -LOCKFILE="/var/lock/sauvegarde.lock" -[ -f $LOCKFILE ] && exit 0 - -#if the script are stoped, remove lockfile -trap "rm -f $LOCKFILE" EXIT -touch $LOCKFILE -echo "$THEDATE : Début de sauvegarde" >> "$LOGFILE/save.log" -# $1:time to keep files; $2: name of the backup; $3: target - -function save() { - - echo "$SPLIT" >> "$LOGFILE/$2.log" - echo "$THEDATE" >> "$LOGFILE/$2.log" - - #Incremental & copie - INC="$FILE/$2/INC/$THEDATE" - BAK="$FILE/$2/BAK" - TOSAVE="$3" - mkdir -p $INC - - if ! [ -e "$BAK" ] - then - /bin/mkdir -p "$BAK" - fi - - #a: archivage :recurcif, preserve dates, persmissions, groupes... - #v: verbose mode - #delete : supprime les fichiers n'etant plus chez l'émeteur >> copie conforme. - /usr/bin/rsync -a --stats --delete --backup --backup-dir="$INC" "$TOSAVE" "$BAK" >> "$LOGFILE/$2.log" - - cd $FILE/$2/INC - tar -czvf "$INC.tar.gz" "$THEDATE" - echo tar -czvf "$INC.tar.gz" "$FILE/$2/INC/$THEDATE" - rm -rf "$THEDATE" - #Remove the file older than $1 days - find "$FILE/$2/INC" -mtime +$1 -delete >> "$LOGFILE/save.log" - -} - -#List here all repositories to backup - -save 90 etc /etc/ - -#to log the end of the backup -THEDATE=`date +%F_%Hh%M` -echo "$THEDATE : Sauvegarde effectuée" >> "$LOGFILE/save.log" -exit 0 diff --git a/content/resources/backup/gen_config.sh b/content/resources/backup/gen_config.sh new file mode 100755 index 0000000..8091bed --- /dev/null +++ b/content/resources/backup/gen_config.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +. ./$1 +OUTFILE=$2 +BACKUP_NAME=$3 + +if test "x${PCA_OS_REGION_NAME}" = x; then + PCA_OS_REGION_NAME=${OS_REGION_NAME} +fi + +if test "x${HOT_OS_REGION_NAME}" = x; then + HOT_OS_REGION_NAME=${OS_REGION_NAME} +fi + +envsubst > "${OUTFILE}" << EOF +[ + { + "description": "Cold storage", + "url": "pca://${BACKUP_NAME}", + "env": [ + { + "name": "PCA_AUTHURL", + "value": "${OS_AUTH_URL}" + }, + { + "name": "PCA_AUTHVERSION", + "value": "${OS_IDENTITY_API_VERSION}" + }, + { + "name": "PCA_PROJECT_DOMAIN_NAME", + "value": "Default" + }, + { + "name": "PCA_TENANTID", + "value": "${OS_TENANT_ID}" + }, + { + "name": "PCA_USERNAME", + "value": "${OS_USERNAME}" + }, + { + "name": "PCA_PASSWORD", + "value": "${OS_PASSWORD}" + }, + { + "name": "PCA_REGIONNAME", + "value": "${PCA_OS_REGION_NAME}" + } + ], + "prefixes": ["cold_"] + }, + { + "description": "Hot storage", + "url": "swift://${BACKUP_NAME}_indexes", + "env": [ + { + "name": "SWIFT_AUTHURL", + "value": "${OS_AUTH_URL}" + }, + { + "name": "SWIFT_AUTHVERSION", + "value": "${OS_IDENTITY_API_VERSION}" + }, + { + "name": "SWIFT_PROJECT_DOMAIN_NAME", + "value": "${OS_PROJECT_DOMAIN_NAME}" + }, + { + "name": "SWIFT_TENANTID", + "value": "${OS_TENANT_ID}" + }, + { + "name": "SWIFT_USERNAME", + "value": "${OS_USERNAME}" + }, + { + "name": "SWIFT_PASSWORD", + "value": "${OS_PASSWORD}" + }, + { + "name": "SWIFT_REGIONNAME", + "value": "${HOT_OS_REGION_NAME}" + } + ], + "prefixes": ["hot_"] + } +] +EOF diff --git a/content/resources/backup/gen_config_local.sh b/content/resources/backup/gen_config_local.sh new file mode 100755 index 0000000..4e6c0dc --- /dev/null +++ b/content/resources/backup/gen_config_local.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +. ./$1 +OUTFILE=$2 +BACKUP_NAME=$3 + +if test "x${PCA_OS_REGION_NAME}" = x; then + PCA_OS_REGION_NAME=${OS_REGION_NAME} +fi + +if test "x${HOT_OS_REGION_NAME}" = x; then + HOT_OS_REGION_NAME=${OS_REGION_NAME} +fi + +envsubst > "${OUTFILE}" << EOF +[ + { + "description": "Cold storage", + "url": "file://${BACKUP_NAME}", + "prefixes": ["cold_"] + }, + { + "description": "Hot storage", + "url": "swift://${BACKUP_NAME}_indexes", + "env": [ + { + "name": "SWIFT_AUTHURL", + "value": "${OS_AUTH_URL}" + }, + { + "name": "SWIFT_AUTHVERSION", + "value": "${OS_IDENTITY_API_VERSION}" + }, + { + "name": "SWIFT_PROJECT_DOMAIN_NAME", + "value": "${OS_PROJECT_DOMAIN_NAME}" + }, + { + "name": "SWIFT_TENANTID", + "value": "${OS_TENANT_ID}" + }, + { + "name": "SWIFT_USERNAME", + "value": "${OS_USERNAME}" + }, + { + "name": "SWIFT_PASSWORD", + "value": "${OS_PASSWORD}" + }, + { + "name": "SWIFT_REGIONNAME", + "value": "${HOT_OS_REGION_NAME}" + } + ], + "prefixes": ["hot_"] + } +] +EOF -- cgit v1.2.3