Sauvegarder et restaurer ses conteneurs docker
Note aux lecteurs
Cet article est vieillissant et son contenu doit être révisé. Attention donc : les explications, commandes systèmes et autres informations peuvent dorénavant être incorrectes ou inexactes.
Pourquoi exporter ses conteneurs ?
La conteneurisation c'est extrêmement pratique pour déployer des services presque "clé en main". Mais qu'en est-il de la sécurité des données et de la résilience des services lorsqu'il n'y a pas de cluster ?
Pour la petite histoire, j'ai mis en place pour un projet un forum basé sur NodeBB. Cela ne faisait que quelques mois que j'avais découvert docker et il m'a apparu évident de le déployer ainsi.
Tout fonctionne pour le mieux, jusqu'au jour où une mise à jour de l'image semble perturber le fonctionnement du forum. Celui-ci détecte à chaque lancement du conteneur docker qu'il s'agit d'une nouvelle installation et refuse de lire le fichier de configuration que je lui donne.
En résultat, je me suis retrouvé avec un forum qui n'arrivait pas à lire la base de données. J'ai alors été contraint de le réinstaller.
C'est à ce moment là que je me suis posé cette question : comment mettre en place un système de rallback, permettant d'annuler une mise à jour qui n'a pas fonctionné par exemple ?
Comment ?
1 - La théorie
J'ai travaillé sur un petit script qui peut doit être paufiné. Mais il s'agit là d'une base fonctionnelle.
L'idée est simple. Avant d'arrêter un conteneur, de le supprimer puis d'en créer un nouveau pour appliquer l'image précédemment téléchargée, il faut effectuer une suite d'actions permettant une restauration de l'état actuel après la mise à jour (en cas de problème).
commit
du conteneur- Exporter l'image créée pour la sauvegarder dans un dossier spécifique
- Sauvegarder le dossier local dans lequel se trouvent les données stockées par le conteneur
Après ces trois points, nous avons un dossier contenant l'image et ses données.
2 - La pratique
Je commence par récupérer la date date=$(date +"%d.%m.%y")
que j'utiliserai pour exporter mes conteneurs. Ensuite, j'utilise une fonction que j'ai créée pour sauvegarder le conteneur :
function commit {
if [ -z $1 ]; then
echo -e "\n\tProbleme avec la fonction commit : il manque les arguments"
return 0
else
container=$1
fi
if [ -z $2 ]; then
nom_save=$container
else
nom_save=$2
fi
docker commit --author="Louis MILCENT" --message="Sauvegarde du $date" $container save/$nom_save"_"$date
docker save save/$nom_save"_"$date > /home/1_save/images/$nom_save"_"$date".tar"
}
Les deux dernières lignes permettent de créer une nouvelle image à partir du conteneur actuel, puis d'exporter cette dernière au format tar dans un dossier.
J'utilise la date du jour au format jour.mois.année pour automatiquement dater les images.
J'utilise un système de log pour garder une trace des manipulations effectuées. touch
permet de créer le fichier s'il n'existe pas et de mettre à jour sa date de dernier accès.
log='/home/louis/logs/forum.log'
touch $log
Partie du script qu'il faut améliorer : la détection d'une mise à jour. Actuellement, je tente de récupérer la valeur de temps retournée par docker via docker images
en tronquant le résultat. Mais ce n'est pas assez précis. S'il y a des idées, je suis preneur !
└─ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
benlubar/nodebb latest 0c40656c90c7 6 days ago 771 MB
Avec mon système, j'obtiens au final le 6
. Mais il suffit que deux images soient récupérées via ce filtre et tout est faussé.
└─ $ docker images | grep benlubar | cut -c 82-83
6
Je mets ensuite l'image à jour (docker pull benlubar/nodebb:latest
). Puis je renouvelle ma précédente commande pour récupérer la valeur de temps, qui devrait avoir changé. Si c'est bien le cas, alors l'image a été modifiée.
J'appelle ma fonction pour commit
et exporter mon conteneur : commit <nom du conteneur> <nom donné à la sauvegarde>
. Si un seul paramètre est passé, le nom de sauvegarde sera celui du conteneur.
commit nodebb
Je sauvegarde ensuite le dossier des données, car j'utilise des volume
lorsque je crée mon image.
sudo cp -r /home/nodebb /home/1_save/nodebb_$date
Enfin, pour appliquer la mise à jour, j'arrête, je supprime puis je crée un nouveau conteneur.
docker stop nodebb
docker rm nodebb
sh lancement_nodebb.sh # Contient la commande docker run [...]
Pour terminer je vérifie si le service s'est correctement lancé avec un test de connectivité http
.
sleep 15 # 15 secondes d'attente pour que le service se lance correctement
http_forum=$(curl -sL -w "%{http_code}\\n" "https://forum.lmilcent.com/" -o /dev/null)
if [ "$http_forum" -ne "200" ]; then
echo -e "\n\t====================================================================================="
echo -e "\t/ ! \ ERREUR lors de la mise à jour du forum, celui ci ne semble plus répondre./ ! \ "
echo -e "\t====================================================================================="
echo -e "\n\tERREUR le forum ne répond pas" >> $log
else
echo -e "\n\tLe forum semble fonctionner correctement, la mise à jour c'est bien passée !"
echo -e "\t----------------------------------------------------------------------------"
fi
3 - Restaurer une image
Pour restaurer une image il suffit d'exécuter la commande suivante : docker load < IMAGE.tar
pour charger l'image sauvegardée dans un dossier quelconque dans docker.
Vous pouvez ensuite la lancer avec vos commandes habituelles. N'oubliez pas de restaurer le dossier contenant les données relatives à cette version ! Vous pouvez essayer de garder le dossier actuel, mais en fonction des services cela peut fonctionner ou non.
4 - Script complet
# Ce script va permettre de rechercher une mise à jour, l'appliquer
# et en cas de problème, les données précédentes (dont le container)
# pourra être restauré
#
# Algo :
# 1) Récupérer la date
# 2) Tester si une mise à jour est disponible
# 2.1) Télécharger la mise à jour
# 2.2) Commit les deux images REDIS et NODEBB avec
# la date en guise de versionning
# 2.3) Backup le dossier /home/nodebb avec la date
# 2.4) Supprimer et re créer les container
# pour appliquer les mises à jour
# 2.5) Tester la connectivité au forum pour
# vérifier s'il fonctionne bien
# 3) Afficher les résultats et/ou envoyer un mail
#
# Date de mise à jour : 28.04.2016
# Auteur : Louis MILCENT
#
#!/bin/bash
#
# Facultatif
#if (( $EUID != 0 )); then
# echo "===================================================="
# echo " Vous devez lancer le script avec les droits root !"
# echo "===================================================="
# exit
#fi
log='/home/louis/logs/forum.log'
date=$(date +"%d.%m.%y")
# ---------
# Fonctions
# ---------
# Commit
# Permet de commit un container donné
# Arguments d'entrée :
# - Nom du container
# - Nom de la sauvegarde (date ajoutée ensuite)
#
function commit {
if [ -z $1 ]; then
echo -e "\n\tProbleme avec la fonction commit : il manque les arguments"
return 0
else
container=$1
fi
if [ -z $2 ]; then
nom_save=$container
else
nom_save=$2
fi
docker commit --author="flexbrane" --message="Sauvegarde du $date" $container save/$nom_save"_"$date
docker save save/$nom_save"_"$date > /home/1_save/images/$nom_save"_"$date".tar"
# Pour récupérer une images : docker load < IMAGE.tar
}
# Mise à jour du fichier log ou création
touch $log
echo -e "\nLancement du script le $date" >> $log
# Test si mise à jour dispo des images
image=$(docker images | grep benlubar | cut -c 82-83)
image=$(printf %d "$image")
image_redis=$(docker images | grep alpine | cut -c 82-83)
image_redis=$(printf %d "$image_redis")
# On met à jour les images
docker pull benlubar/nodebb:latest
docker pull redis:alpine
image_maj=$(docker images | grep benlubar | cut -c 82-83)
image_maj=$(printf %d "$image_maj")
image_redis_maj=$(docker images | grep alpine | cut -c 82-83) # Alpine car c'est la seule image basé dessus pour le moment
image_redis_maj=$(printf %d "$image_redis_maj")
# Permet de ne sauvegarder le dossier qu'une seule fois si les deux images sont mises à jour
dossier_save=0
# On verifie si la valeur de temps de l'image a été modifiée
# Si oui, on lance le reste. Sinon on ne modifie rien.
if [ "$image" -ne "$image_maj" ]; then
echo -e "\n\tL'image benlubar/nodebb à été mise à jour"
echo -e "\t-----------------------------------------"
echo -e "Image benlubar/nodebb mise à jour" >> $log
# on sauvegarde cette version
echo -e "\n\tSauvegarde du container actuel"
echo -e "\t------------------------------"
commit nodebb
# on sauvegarde le dossier affilié
echo -e "\n\tSauvegarde du dossier nodebb"
echo -e "\t----------------------------"
sudo cp -r /home/nodebb /home/1_save/nodebb_$date
dossier_save=1
# on applique la mise à jour
echo -e "\n\tArrêt et supression de nodebb"
echo -e "\t-----------------------------"
docker stop nodebb
docker rm nodebb
echo -e "\n\tLancement de NodeBB"
echo -e "\t-------------------"
sh lancement_nodebb.sh
echo -e "\n\tTest du bon fonctionnement du forum"
echo -e "\t-----------------------------------"
echo -e "(Attente de 15 secondes avant de tester la connectivité du forum)"
sleep 15
http_forum=$(curl -sL -w "%{http_code}\\n" "https://forum.lmilcent.com/" -o /dev/null)
if [ "$http_forum" -ne "200" ]; then
echo -e "\n\t====================================================================================="
echo -e "\t/ ! \ ERREUR lors de la mise à jour du forum, celui ci ne semble plus répondre./ ! \ "
echo -e "\t====================================================================================="
echo -e "\n\tERREUR le forum ne répond pas" >> $log
else
echo -e "\n\tLe forum semble fonctionner correctement, la mise à jour c'est bien passée !"
echo -e "\t----------------------------------------------------------------------------"
fi
else
echo -e "\n\tL'image benlubar/nodebb est déjà à jour"
echo -e "\n\t---------------------------------------"
echo -e "\tImage benlubar/nodebb déjà à jour" >> $log
fi
if [ "$image_redis" -ne "$image_redis_maj" ]; then
echo -e "\nL'image redis a été mise à jour"
echo -e "\n\t-----------------------------"
echo -e "\tImage redis mise à jour" >> $log
# on sauvegarde cette version
echo -e "\n\tSauvegarde du container actuel"
echo -e "\t------------------------------"
commit nodebb_db
if [ "$dossier_save" -eq 0 ]; then
echo -e "\n\tSauvegarde du dossier nodebb"
echo -e "\t----------------------------"
sudo cp -r /home/nodebb /home/1_save/nodebb_$date
fi
echo -e "\n\tArrêt et supression de nodebb_db"
echo -e "\t--------------------------------"
docker stop nodebb_db
docker rm nodebb_db
echo -e "\n\tLancement du service"
echo -e "\t--------------------"
sh lancement_nodebb_db.sh
else
echo -e "\n\tL'image redis est déjà à jour"
echo -e "\t---------------------------"
echo -e "Image redis déjà à jour" >> $log
fi
echo -e "\nScript terminé\n" >> $log
exit
Conclusion
J'espère que ce script vous sera utile. Il permet d'éviter un bon nombre de soucis et procure un niveau de résilience de votre service un peu plus important. Prochainement je vous expliquerai simplement comment sauvegarder ces données vers un serveur SSH distant.
Image par Markus Distelrath de Pixabay