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 :
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.
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 :
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 :
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 » :
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 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 ». 😎