OpenMediaVault : Sauvegarde & Restauration des containers Docker

Lors de mon article sur l’organisation de mes sauvegardes sur mon NAS OpenMediaVault, il me manquait la bonne façon de sauvegarder les containers Docker.

Je sauvegardais avec rsync le répertoire où se trouve Docker, il y avait des milliers de fichiers, et je ne pense pas que j’aurais pu faire grand chose de cette sauvegarde en cas de problème ! 🙁

J’ai donc cherché du côté de Docker, et sans surprise Docker propose sa propre solution pour la sauvegarde des containers. Les principales commandes sont expliquées sur ce schéma :

Les principales commandes pour la sauvegarde & la restoration

L’idée est donc de créer une image de chaque container (commande COMMIT), puis de les sauvegarder au format .tar (commande SAVE). On voit sur l’image les commandes LOAD et RUN qu’il faudra utiliser dans l’autre sens pour la restauration.

Suite à un crash disque, j’ai été amené à restaurer ces images Docker, et comprendre qu’ils ne contiennent en fait que les binaires permettant de créer des containers à l’identique (même version), compatibles avec nos fichiers de configuration existants.
J’ajoute donc une partie « Restauration » à cet article (avec explications et exemple pas-à-pas), qui ainsi sera plus complet.

Ensuite, il fallait que je copie ces fichiers .tar sur mon PC à l’aide de rsync, puisque j’ai déjà un serveur rsync qui tourne sur ce dernier. Il ne restait plus qu’à faire un peu de ménage avec ces images et fichiers créés sur le NAS ou sur le PC pour avoir quelque chose de propre.

J’ai donc préparé un script qui fait tout ça, que j’ai ensuite ajouté en tâche crontab au système. Ainsi mes containers sont sauvegardés chaque semaine.

Voyons voir un peu tout cela en détail. On aura ainsi un script automatique pour la sauvegarde de images, puis comment les restaurer et recréer les containers docker à partir de ceux-ci.

Dans l’explication ci-dessous, je ne traite que d’un seul container comme exemple (Nextcloud), il suffit d’appliquer les mêmes actions pour chaque container utilisé sur votre serveur que vous souhaitez sauvegarder.

Nous avons d’abord besoin d’identifier le nom de l’image de notre container. Pour cela, on va utiliser la commande docker images et noter le REPOSITORY soit linuxserver/nextcloud ci-dessous :

pascal@odroidhc2:~$ sudo docker images
REPOSITORY                           TAG       IMAGE ID       CREATED        SIZE
linuxserver/nextcloud                latest    70e33dd606b8   3 months ago   330MB

Dans le cas où le REPOSITORY est vide (ça m’est arrivé après une restauration je pense), taper sudo docker ps et noter le NAME (soit nextcloud ici) :

pascal@odroidhc2:~$ sudo docker ps
CONTAINER ID   IMAGE          ...       NAMES
d1ae35bfa2f1   ad54fea6e081   ...       nextcloud                               

Script – première partie

Dans un premier temps, on va définir quelques variables qui nous seront utiles dans le script : d’abord un répertoire où créer les fichiers image (disque externe du NAS OMV), ainsi qu’une variable pour ajouter un « time-tamp » aux fichiers que l’on va créer ; puis le nom de l’image (le REPOSITORY noté ci-dessus), qui nous permettra d’aller chercher le container ID, et enfin le nom de l’image.

# init des variables
backup_dir="/srv/dev-disk-by-label-DATA/Docker-Containers-Backup/"
now=$(date +%d-%m-%Y-%H-%M-%S)
nextcloud_imgname="linuxserver/nextcloud"
nextcloud_imgid=$(docker ps -a -f "ancestor=$nextcloud_imgname" -q -l)
bck_nextcloud_imgname=backup_nextcloud

Si on utilise le NAME, alors nextcloud_imgname="nextcloud" et on remplace ancestor par name :

# init des variables
backup_dir="/srv/dev-disk-by-label-DATA/Docker-Containers-Backup/"
now=$(date +%d-%m-%Y-%H-%M-%S)
nextcloud_imgname="nextcloud"
nextcloud_imgid=$(docker ps -a -f "name=$nextcloud_imgname" -q -l)
bck_nextcloud_imgname=backup_nextcloud

Une fois ceci fait, on peut créer une image du container :

# Création de l'image
docker commit -p "$nextcloud_imgid" "$bck_nextcloud_imgname"

Puis on copie cette image au format .tar dans un dossier du NAS défini plus haut :

# Sauvegarde de l'image sur le disque externe du NAS
docker save -o  "$backup_dir""$bck_nextcloud_imgname"_"$now".tar "$bck_nextcloud_imgname"

Puis on compresse l’archive :

# Compression des archives
gzip "$backup_dir"*.tar

Il ne reste plus qu’à envoyer l’archive sur le PC, où tourne un serveur rsync.

Serveur SSH sur PC

Pour éviter d’avoir à entrer un mot de passe lorsque le script arrive à cette étape, il m’a fallu activer le serveur SSH sur le PC :

pascal@SH87R:~$ sudo apt install openssh-server

Pour en limiter l’accès, j’ai modifié le fichier /etc/deny.hosts du PC en y ajoutant la ligne suivante (l’adresse IP étant celle du NAS):

pascal@SH87R:~$ sshd: ALL EXCEPT 192.168.1.30

Et je redémarre le service sshd dans la foulée :

pascal@SH87R:~$ sudo systemctl restart sshd

Côté NAS, il faut maintenant générer la clef pour permettre le login automatique (taper Entrée pour toutes les questions) :

pascal@odroidhc2:~$ ssh-keygen

Puis on copie la clef sur le PC avec l’utilisateur rsync (l’adresse IP est cette fois celle du PC) :

pascal@odroidhc2:~$ ssh-copy-id -i /home/pascal/.ssh/id_rsa.pub rsync@192.168.1.20

Voilà, la commande rsync que l’on va utiliser dans le script pourra désormais se logguer sans avoir à saisir de mot de passe.

Script – deuxième partie

Retour au script donc, où il s’agit de copier le(s) fichier(s) images compressés vers le PC :

# Copie des images compressées vers le PC
rsync -a --delete "$backup_dir" rsync@192.168.1.20::omv-containers-rsync/

NOTES

  • il ne faut pas utiliser de « wildcards » comme le caractère « * » dans la commande rsync si l’on veut que l’option --delete fonctionne !
  • J’utilise cette option --delete afin d’effacer de la destination (sur le PC donc) les fichiers qui ne sont pas présents sur la source. L’idée est de ne garder qu’une seule version des containers, chaque nouvelle sauvegarde effacera la sauvegarde précédente. Il doit être possible d’améliorer le script pour établir un système plus sophistiqué de sauvegardes, mais pour l’instant cela me convient tel quel.
  • Pour la syntaxe de type rsync@adresse_IP::nom_module utilisée, voir l’article des sauvegardes.

Il ne reste plus qu’à faire le ménage sur le NAS, à savoir effacer le fichier archive (désormais copié sur le PC) mais aussi effacé l’image docker que l’on peut voir sur une commande docker images :

pascal@odroidhc2:~$ sudo docker images
REPOSITORY                           TAG       IMAGE ID       CREATED          SIZE
backup_nextcloud                     latest    73e18450d11a   4 seconds ago   331MB
linuxserver/nextcloud                latest    70e33dd606b8   3 months ago     330MB

On efface donc fichier et image docker en ajoutant ces lignes au script :

# Nettoyage des fichiers et images
docker rmi "$bck_nextcloud_imgname"
rm "$backup_dir"*.gz

Et voilà, notre script est prêt. Il ne reste plus qu’à l’ajouter au crontab du compte root, afin qu’il ait les droits d’exécuter les commandes docker. On exécute donc sudo crontab -e pour ajouter la tâche, et voici ce que ça donne pour une exécution tous les dimanches à 13h30 :

sudo crontab -l
# m h  dom mon dow   command
30 13 * * 0 /home/pascal/docker-container-backup.sh

Voilà ce que donne le script avec les trois containers nécessaires à Nextcloud (Nextcloud, Mariadb et Swag) :

cat docker-container-backup.sh 
#!/bin/bash
##########################################################
# Script de backup des containers Docker. 
##########################################################

# init des variables
backup_dir="/srv/dev-disk-by-label-DATA/Docker-Containers-Backup/"
now=$(date +%d-%m-%Y-%H-%M-%S)

nextcloud_imgname="linuxserver/nextcloud"
nextcloud_imgid=$(docker ps -a -f "ancestor=$nextcloud_imgname" -q -l)
bck_nextcloud_imgname=backup_nextcloud

swag_imgname="linuxserver/swag"
swag_imgid=$(docker ps -a -f "ancestor=$swag_imgname" -q -l)
bck_swag_imgname=backup_swag

mariadb_imgname="linuxserver/mariadb"
mariadb_imgid=$(docker ps -a -f "ancestor=$mariadb_imgname" -q -l)
bck_mariadb_imgname=backup_mariadb

# Création des images
docker commit -p "$nextcloud_imgid" "$bck_nextcloud_imgname"
docker commit -p "$swag_imgid" "$bck_swag_imgname"
docker commit -p "$mariadb_imgid" "$bck_mariadb_imgname"

# Sauvegarde des images sur le disque externe du NAS backup_dir
docker save -o  "$backup_dir""$bck_nextcloud_imgname"_"$now".tar "$bck_nextcloud_imgname"
docker save -o  "$backup_dir""$bck_swag_imgname"_"$now".tar "$bck_swag_imgname"
docker save -o  "$backup_dir""$bck_mariadb_imgname"_"$now".tar "$bck_mariadb_imgname"

# Compression des archives
gzip "$backup_dir"*.tar

# Copie des images compressées vers le PC
rsync -a --delete "$backup_dir" rsync@192.168.1.20::omv-containers-rsync/

# Nettoyage des images
docker rmi "$bck_nextcloud_imgname"
docker rmi "$bck_swag_imgname"
docker rmi "$bck_mariadb_imgname"

# Nettoyage des fichiers
rm "$backup_dir"*.gz

#Fin du script

Et voilà mes sauvegardes sur le PC :

Les sauvegardes des trois containers, au format gzip, compressées.

Je peux désormais dormir tranquille ! 😎

Problème rencontré

Suite à un plantage du NAS qui ne répondait plus, et que j’ai donc du redémarrer en débranchant/rebranchant l’alimentation, tout est bien reparti, mais tous mes containers avaient pour REPOSITORY un simple « none » :

$ sudo docker images
REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
<none>                latest    70e33dd606b8   7 months ago   240MB

Ce qui empêchait le script de backup de fonctionner ; c’est comme ça que je me suis rendu compte du problème d’ailleurs, puisque sinon tout fonctionnait !

J’ai pu les renommer avec leur ancien nom avec la commande docker tag :

$ sudo docker tag 70e33dd606b8 linuxserver/nextcloud
$ sudo docker images
REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
<none>                latest    70e33dd606b8   7 months ago   240MB
linuserver/nextcloud  latest    70e33dd606b8   7 months ago   240MB

Cette commande crée en fait un nouveau tag (un alias en fait) pointant sur la même image (70e33dd606b8), il faut donc ensuite utiliser la commande docker rmi pour supprimer le tag inutile :

$ sudo docker rmi mauvais/tag:latest

Je n’ai pas vraiment analysé ce qui c’était passé lors de ce plantage. Apparemment, OMV a bien redémarré mais a créé un nouveau container pour portainer :

Deux containers existant pour Portainer…

Bon, je n’ai pas creusé plus loin, mais cela explique sans doute le problème de tag… L’essentiel est que tout ait redémarré sans autre problème. Je peux effacer le container « unused ».

Restauration des images

Comme je le disais plus haut, suite à un crash disque, j’ai été amené à restaurer ces images : il y a quelques trucs à comprendre avant de se lancer.

Principes

Ces images Docker ne sont en fait que les binaires de votre container. Puisque l’on a conserve (ou que l’on restaure) toute la config des containers dans les sous-dossier /srv/dev-disk-by-label-DATA/AppData, il est important de recréer le container avec le même binaire.

Typiquement, si l’on utilisait une version plus récente, il est possible que des changements aient été apportés : nouveaux paramètres, fichier config modifiés, arborescence, etc… On s’exposerait alors à une incompatibilité, et donc à un dysfonctionnement. D’où l’intérêt de restaurer ces images, puis de lancer le container à partir de cette image : on garde ainsi le même « couple » binaire & config, et on s’assure de tout pouvoir retrouver à l’identique.

Tout cela paraît très logique et très simple, mais il vaut mieux le savoir avant de se lancer !

Mise en œuvre

Prenons le container Plex comme exemple. On commence donc par récupérer le fichier backup préparé comme indiqué plus haut sur le NAS, puis on le décompresse :

sudo gunzip backup_plex_16-05-2021-13-30-01.tar.gz

On obtient alors un fichier .tar, qu’il suffit de charger, soit en mode commande :

sudo docker load -i backup_plex_16-05-2021-13-30-01.tar
f7d9c37ee65c: Loading layer  416.8kB/416.8kB
Loaded image: backup_plex:latest

On vérifie qu’il est bien présent :

sudo docker images
REPOSITORY                           TAG       IMAGE ID       CREATED       SIZE
backup_plex                          latest    fdb5ba792f9e   3 days ago    272MB

À ce stade, l’image est chargée, et on peut la voir dans Portainer/Images, mais en status « unused » :

L’image docker est présente, mais marquée « non utilisée ».

On peut bien sûr changer le nom du repository. Comme expliqué plus haut, il suffit de créer un nouvel alias puis de supprimer l’ancien :

sudo docker tag backup_plex:latest plex:latest
sudo docker images
REPOSITORY                           TAG       IMAGE ID       CREATED        SIZE
backup_plex                          latest    fdb5ba792f9e   4 days ago     272MB
plex                               latest    fdb5ba792f9e   4 days ago     272MB
sudo docker rmi backup_plex:latest
sudo docker images
REPOSITORY                           TAG       IMAGE ID       CREATED        SIZE
plex                               latest    fdb5ba792f9e   4 days ago     272MB

Mais restons avec backup_plex pour la suite de cet exemple, ce sera plus clair.

Avant de recréer le container, on vérifie que l’on a toujours les fichiers de config et de media de notre serveur Plex, que l’on a défini lors de la création du container dans la section « Volumes » :

   volumes:
      - /srv/dev-disk-by-label-DATA/AppData/Plex:/config
      - /srv/dev-disk-by-label-DATA/OMV-DATA/Series:/Series
      - /srv/dev-disk-by-label-DATA/OMV-DATA/Films:/Films
      - /srv/dev-disk-by-label-DATA/OMV-DATA/Docus:/Docus
      - /srv/dev-disk-by-label-DATA/Download:/Qbittorrent

On peut alors recréer le container : soit par la commande docker run, soit avec l’outil docker-compose (source). Autant alors le faire en mode docker-compose via Portainer, c’est plus simple que la commande docker run à laquelle il faudrait passer plein d’options.

Et comme j’ai précieusement conservé mes « stacks » d’origine, rien de plus facile que de recréer le container, en faisant attention toutefois à donner le nom de l’image que l’on vient de créer :

On crée la stack via Docker Compose avec le nom de l’image créée.

On clique sur le bouton « Deploy the Stack » de Portainer, et le container est alors créé puis lancé. On peut vérifier que l’on a bien utilisé notre image avec la commande suivante :

sudo docker container ls
CONTAINER ID   IMAGE          COMMAND     CREATED      STATUS               PORTS      NAMES
7504da49ff62   fdb5ba792f9e   "/init"     39 seconds ago      Up 37 seconds                   plex

L’identifiant de l’image pour ce container est bien le même : fdb5ba792f9e… Et voilà, l’instance Plex tourne, et l’on retrouve tout de suite toute la médiathèque à l’adresse http://192.168.1.X:32400/web/index.html.

Conclusion

Si la partie « backup » a été assez facile à mettre au point, la partie « restore » à été plus laborieuse, puisque je n’avais pas vraiment compris ce que contenaient ces images.

En fait je mélangeais allègrement les concepts d’images et de containers. Or une image n’est que la partie binaire (version X du soft Y au format Docker). À partir de cette image, on crée un container qui aura ses fichiers de configuration etc…

Voilà l’utilité de ces sauvegardes d’images Docker : s’assurer de garder les mêmes binaires à disposition, afin de conserver leur compatibilité avec mes fichiers de configuration.

C’est ainsi que l’on retrouve son container « à l’identique ». 😎

Laisser un commentaire

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