De l’importance de la sauvegarde dans l’auto-hébergement

Le 29 au soir, mon Pi a fait une petite chute d’une vingtaine de centimètre lorsque le câble d’alimentation s’est pris dans l’un des pieds de ma chaise de bureau. J’ai d’abord cru que la chute avait été sans conséquences. Mais l’allumage d’une LED rouge fixe et l’extinction de la LED verte m’ont rapidement convaincu du contraire.

Après une tentative infructueuse de relancer le serveur, je retire la carte SD du Pi afin de l’examiner et constate une très légère pliure à sa surface. J’extrais la carte, l’insère dans un lecteur afin de vérifier son contenu. Pas de partitions détectées et montées automatiquement. C’est mauvais signe. Je teste un montage manuel, la réponse est sans appel : « mauvais type de système de fichiers, option erronée, superbloc erroné sur /dev/sdg, page de code ou programme auxiliaire manquant, ou autre erreur.« . Par ailleurs, la carte SD est particulièrement chaude au toucher lorsque je la retire du lecteur. Une seule conclusion s’impose, la carte SD est morte.

Je vérifie le contenu de mon fichier .hubic_credentials en local et demande l’état de la chaîne de sauvegarde :

duplicity collection-status cf+hubic://<container> 

Une sauvegarde complète date de la veille (28 janvier), je me lance dans une première récupération locale des données avec :

duplicity restore --force -t now cf+hubic://<container> data

Une fois en possession d’une nouvelle carte SD, j’ai donc réinstallé Raspbian pour pouvoir réinstaller mon serveur. A ce stade, j’ai la chance d’avoir passé plusieurs heures à écrire un script Ansible me permettant de déployer automatiquement mes différents services à partir de la sauvegarde effectuée par duplicity. Tout fonctionne plutôt bien, jusqu’à ce qu’on arrive à la récupération des fichiers sur hubic, où un nombre non négligeable de requête termine en erreur 404 et oblige à relancer le script jusqu’à obtenir une réponse. Pas vraiment de solution de ce côté-là à part changer d’endroit pour le stockage de la sauvegarde. Les requêtes en erreur finissant en général par répondre après un laps de temps aléatoire pouvant aller jusqu’à plusieurs jours. Bref, cette solution n’est pas satisfaisante du tout !

Après pas loin d’une dizaine d’heure de tentative, l’ensemble des fichiers nécessaires à duplicity est finalement disponible en local sur le système d’exploitation tout neuf. C’est le moment de tester les différents services. Wallabag et Shaarli fonctionne directement avec l’ensemble des données accessible. Ce n’est malheureusement pas le cas de Gitea, FreshRSS et Nextcloud.

Petit tour dans les logs de FreshRSS : « Access to database is denied« . J’arrête le service mysql, le redémarre et rafraîchis la page. Bingo ! L’interface s’affiche.

Pour Gitea, le problème se situe également du côté de la base de donnée et le ticket correspondant sur le projet est le numéro 2979 . Après connexion à la base, un simple

 set global innodb_large_prefix = `ON`; 

résous le problème. L’ensemble des données est présent. Pas de perte.

En ce qui concerne Nextcloud, le problème est à chercher du côté de la configuration de la sauvegarde. En effet, en y regardant de plus près, il semble que pour limiter la taille de la sauvegarde, je n’avais configuré que la sauvegarde du dossier data, au moment de la mise en place. En fait, après analyse, il s’avère que c’est bien pire, le dossier semble être présent dans la sauvegarde, mais pas son contenu. Étrange. Rien de catastrophique néanmoins, les données présentes sur mon instance Nextcloud sont toutes synchronisées soit sur mon ordinateur, soit sur mon téléphone, et par ailleurs, l’instance ne contient aucune donnée critique qui n’ait elle-même fait l’objet d’une sauvegarde sur un autre support.

Côté réinstallation, j’ai configuré Nextcloud pour utiliser la base existante réimportée à partir de la sauvegarde. Au passage, j’en ai profité pour faire la mise à jour vers la version 15. Les contacts, les calendriers et les tâches sont revenus à partir de la sauvegarde, j’ai perdu au passage l’enregistrement d’un événement, perte sans conséquence. La sauvegarde étant journalière, les pertes sont très limitées. Pour la partie fichier, c’est plus compliqué, puisque le dossier nextcloud du serveur n’est pas présent dans la sauvegarde. Les fichiers étant présent en totalité sur mon disque dur local, j’ai donc supprimé l’intégralité du contenu sur le serveur via l’interface web et j’ai forcé la synchronisation à partir du contenu local via le client Nextcloud.

Cette mésaventure m’a permis de tester ma procédure de sauvegarde et de déploiement automatisé du serveur et de ses services en conditions réelles. J’ai identifié quelques faiblesses, notamment du côté du système de stockage utilisé par duplicity, à savoir hubic et un problème de configuration du côté de la sauvegarde de Nextcloud. Au passage, pas de perte de données ou d’informations, ce qui est un point plus que positif. A cette occasion, j’en ai également profité pour mettre à jour certaines parties dépréciées de mon script Ansible. Des améliorations sont donc prévues, en particulier pour trouver une alternative correcte à hubic pour stocker les données de sauvegarde.

Procédure de restauration de sauvegarde… Surprise !

Depuis plusieurs mois donc, mes services auto-hébergés sont sauvegardés quotidiennement de façon automatique et incrémentale. Je suis en théorie protégé contre la perte de mes données en cas de panne matérielle du support de stockage de mon serveur. Ça, c’est la théorie, il me restait en l’occurrence à valider le processus de sauvegarde en m’assurant de la façon de restaurer les données.

J’ai donc commencé ces derniers jours l’écriture d’un script ansible permettant de redéployer automatiquement l’ensemble de mes services sur un nouveau serveur si besoin. L’occasion rêvée de vérifier que la restauration de sauvegarde fonctionne correctement.

Après configuration de duplicity sur la nouvelle machine, je tente donc de restaurer un fichier pris au hasard:

duplicity restore --file-to-restore path/to/wallabag/vendor/jdorn/sql-formatter/LICENSE.txt -t now cf+hubic:// test/LICENSE-restored.txt

J’obtiens une erreur dès les premières tentatives : No backup chains found, et lis au détour d’une page web qu’il faut à priori effectuer un list-current-files au préalable. Il faudra que je vérifie cette information lors du test réel de mon script sur un système vierge. Je découvre donc que duplicity récupère dans un premier temps tous les fichiers manifest. La récupération des fichiers se poursuit, et c’est le drame :

Giving up after 1 attempts. NoSuchObject: Object 'duplicity-inc.20180212T113006Z.to.20180213T113007Z.manifest.gpg' doesn't exist (HTTP 404)

L’un des fichiers n’est pas renvoyé par hubiC. Il est néanmoins visible dans l’interface, mais impossible de le récupérer, la requête effectuée par l’interface web retourne elle aussi une mauvaise erreur 404. Ce problème de manifeste manquant concerne une chaîne secondaire, mais impacte malheureusement l’ensemble de la collection. Impossible de restaurer les données via duplicity…

Continuer la lecture de « Procédure de restauration de sauvegarde… Surprise ! »

Sauvegarde distante avec duplicity

Suite à la migration de la majorité de mon nuage de services vers mon réseau en passant à l’auto-hébergement, la question de la sauvegarde des données est devenue cruciale. Côté cahier des charges, les principaux critères retenus étaient les suivants :

  • Sauvegarde externe géographiquement
  • Automatique
  • Chiffrée

Après de nombreuses recherches, j’ai décidé d’utiliser duplicity pour réaliser la sauvegarde et le service hubiC d’OVH pour le stockage des données sauvegardées. Voici donc la procédure utilisée pour mettre en place une sauvegarde incrémentale quotidienne, avec sauvegarde complète à intervalle régulier.

Pour commencer, il convient d’installer les composants nécessaires:

sudo aptitude install duplicity python-pip python-dev gcc
sudo pip2 install pyrax

Ayant décidé d’envoyer les sauvegardes dans mon espace hubiC, et afin de permettre à duplicity de communiquer avec hubiC, il faut au préalable autoriser l’application dans l’interface web du service. On va donc créer une nouvelle application, renseigner un nom, duplicity par exemple, et un domaine de redirection valant http://localhost/. On note ensuite les paramètres « Client ID » et « Client secret » qui serviront pour paramétrer la connexion à hubiC sur la machine source.

Le paramétrage s’effectue dans le fichier ~/.hubic_credentials :

[hubic]
email = your_email
password = your_password
client_id = api_client_id
client_secret = api_secret_key
redirect_uri = http://localhost/

On limite ensuite les droits liés au fichier:

chown 600 ~/.hubic_credentials

A ce stade, on peut faire un premier test afin de valider la configuration, par exemple, sauvegarder le répertoire test dans le conteneur test qui sera accessible à l’adresse suivante: https://hubic.com/home/browser/#test.

duplicity test cf+hubic://test

Pour lister le contenu du répertoire distant, la commande dédiée est:

duplicity list-current-files cf+hubic://test

Continuer la lecture de « Sauvegarde distante avec duplicity »

Sauvegarde de base de données avec mysqldump et logrotate

Cela faisait plus d’un an que j’avais vaguement mentionné la question de la sauvegarde d’Unicoda et des autres services que j’héberge. Jusqu’à présent, je m’étais contenté de quelques sauvegardes manuelles effectuées de temps à autre, lorsque le contenu avait bien évolué ou que la sauvegarde précédente commençait à dater. Je me suis donc intéressé aux différentes solutions qu’on rencontre sur le net : rsync, rclone, duplicity, script bash, bacula, pour ne citer que quelque-uns des outils. Si l’automatisation de la sauvegarde ne m’avait jamais semblé essentielle, c’est que j’étais conscient des risques et me reposait sur la duplication RAID de l’hébergeur (pour le risque de défaut matériel), mais surtout, parce que la majorité des informations était généralement présente en local sur l’une ou l’autre de mes machines. Après les récents changements de serveurs et le passage à l’auto-hébergement de nombreux services, la sauvegarde devient critique. Commençons donc par nous pencher sur la sauvegarde des données du serveur SQL.

Pour exporter les données d’un serveur MySQL, la commande mysqldump est tout indiquée. Pour sauvegarder l’intégralité des bases du serveur avec l’utilisateur root, on pourra par exemple utiliser :

mysqldump -u root -p --all-databases > all_databases.sql

De la même manière, il est possible de n’extraire qu’une seule base de données :

mysqldump -u [user] -p maBase > maBDD.sql

Il est possible de spécifier le mot de passe de l’utilisateur directement dans la commande via -p[motDePasse] ou encore -p'[motDePasse]’.  Une fois en possession d’une copie de notre base, l’import s’effectue simplement en utilisant :

mysql -u [user] -p maNouvelleBase < maBDD.sql

J’ai eu l’occasion de mettre à l’épreuve cette méthode lors des différentes migrations d’Unicoda; toujours avec succès et sans jamais rencontrer de problème.

Maintenant que nous avons une façon de sauvegarder les données de MySQL, il est temps de s’intéresser à l’automatisation du processus.  Afin de régler la question de l’accumulation des fichiers de sauvegarde, j’ai décidé d’utiliser logrotate, en détournant quelque peu son usage pour l’appliquer à mes sauvegardes SQL et non à des fichiers de logs. Avant toutes choses, je crée un nouvel utilisateur au niveau de MySQL en le limitant aux droits Lock Tables et Select sur la base que je veux sauvegarder.

GRANT LOCK TABLES, SELECT ON maBase.* TO '<user>'@'localhost' IDENTIFIED BY '<password>';

On passe ensuite à la configuration de logrotate pour notre fichier de sauvegarde. J’ajoute donc un fichier dans /etc/logrotate.d/sauvegarde-base-sql avec le contenu suivant :

/var/backups/mysql/maBDD.sql.gz {
  daily
  dateext
  rotate 21
  nocompress
  create
  postrotate
  mysqldump --single-transaction --add-drop-table -u user -pmonMotDePasse baseASauvegarder > /var/backups/mysql/maBDD.sql
  gzip -9f /var/backups/mysql/maBDD.sql
  chown user:user /var/backups/mysql/maBDD.sql.gz
  chmod 640 /var/backups/mysql/maBDD.sql.gz
  endscript
}

Côté paramètres :

  • L’exécution est journalière.
  • La date est ajoutée au fichier lors de la rotation.
  • On conserve 21 fichiers.
  • Pas de compression
  • Le nouveau fichier est créé avec les mêmes permissions et le même propriétaire que le précédent (create).
  • On indique les commandes à exécuter après rotation (postrotate) à savoir :
    • Extraction des données de la base
    • Compression du fichier
    • Modification du propriétaire
    • Modification des droits

Enfin, on prépare l’état initial du script en ajoutant un fichier vide de départ :

sudo touch /var/backups/mysql/maBDD.sql.gz

Il est alors possible de tester l’exécution avec l’instruction suivante (débug avec -d et mode verbeux avec -v) :

sudo logrotate -f /etc/logrotate.d/sauvegarde-base-sql

La restauration de la base s’effectue avec la commande d’import décrite plus haut, il faudra bien sûr au préalable décompresser le fichier de sauvegarde :

gunzip maBDD.sql.gz

Grâce à cette configuration, je dispose désormais de 21 jours de sauvegarde de ma base SQL, le tout entièrement automatisé. Logrotate me permet de bénéficier simplement de la rotation de mes fichiers de sauvegarde. Il aurait été aussi possible d’utiliser un script bash et une tâche cron pour un résultat similaire, mais cela aurait nécessité de s’occuper de la partie gestion des anciens fichiers; ce que logrotate fait très bien pour moi. Dans le prochain article dédié à la sauvegarde, je décrirai la solution que j’ai retenue pour la sauvegarde distante des fichiers du serveur, dont ces exports SQL.

Par ailleurs, n’hésitez pas à partager votre manière de sauvegarder votre base de données dans les commentaires.

Source : scottlinux, LeaseWeb Labs, Linode.

Sauvegarde du contenu d’une carte SD

J’ai réalisé dernièrement quelques essais d’applications audio compatible Raspberry Pi. En cas de test non satisfaisant, je voulais être en mesure de redéployer aisément l’ancien système. Le site officiel du Raspberry Pi fournit pour ce cas précis de bonnes explications.

Je retiens donc les commandes suivantes permettant de copier le contenu d’une carte SD et de pouvoir le réécrire à l’identique, avec au passage, compression des données pour gagner en espace disque.

Pour sauvegarder :

sudo dd bs=4M if=/dev/mmcblk0 | gzip > pi.img.gz

Et pour déployer la sauvegarde :

gunzip --stdout pi.img.gz | sudo dd bs=4M of=/dev/mmcblk0

En remplaçant bien évidemment /dev/mmcblk0 par le chemin vers la carte SD sur le système hôte.