Gestion, sauvegarde et mise à jour de vos conteneurs Docker

Mettre en place des conteneurs Docker est relativement facile et rapide mais il faut bien entendu penser à réaliser des sauvegardes, que ce soit des données, de la configuration ou même des containers eux-mêmes.

Gestion, sauvegarde et mise à jour de vos conteneurs Docker

Toujours dans la série des tutoriels utiles pour essayer de vous apprendre à mieux gérer vos conteneurs Docker (pour rappel, les articles suivants peuvent vous aider à vous lancer), nous allons voir, au travers de cet article, les bases et bonnes pratiques de la gestion des volumes, sauvegardes et mises à jour.


La gestion des données

Après avoir créé vos premiers conteneurs Docker, vous vous êtes peut-être posés la question de savoir ce qu'il advient des données utilisées par ces derniers. Alors par défaut, les données contenues dans le conteneur disparaissent tout simplement avec lui lors de sa destruction. Cela peut vous convenir et être voulu dans certains cas, mais il est parfois nécessaire que les données soient stockées de façon permanente, pour les retrouver plus tard, on va dire que les données sont persistantes. Prenons l'exemple d'une application ayant une base de données utilisateurs, elle ne doit pas être supprimée si on détruit le conteneur contenant le moteur de la base de données !

Docker offre plusieurs manières de rendre persistantes les données utilisées par les conteneurs : les volumes de données, que nous allons voir en détails, les conteneurs de données, ou tout simplement un répertoire sur votre hôte.

L’idée est de stocker les données du conteneur dans un répertoire spécifique du serveur qui héberge votre instance Docker. Ce stockage , appelé volume de données, permet de rendre les données indépendantes du conteneur. Le volume permet également de partager les données entre plusieurs conteneurs.

Les fonctionnalités apportées par les volumes de données dans Docker sont les suivantes :

  • La création d'un conteneur initialise le volume. Dans le cas où l’image depuis laquelle vous créez votre conteneur contient déjà des données à l’initialisation du volume, alors elles sont copiées dans le volume créé.
  • Un volume de données peut être utilisé par plusieurs conteneurs ainsi que par le système hôte Docker.
  • Les volumes de données sont persistants même si les conteneurs sont supprimés.
  • Il est facile de sauvegarder un volume de données.

Nous allons voir les différentes méthodes pour créer un volume de données.

Tout d'abord, si vous utilisez Docker en CLI, vous pouvez utiliser la commande suivante pour créer un volume :

docker volume create volume-test

Vous pouvez lister les volumes existants :

docker volume ls
DRIVER              VOLUME NAME
local               volume-test

Et inspecter un volume pour avoir plus de détails sur celui-ci :

docker volume inspect volume-test
[
    {
        "CreatedAt": "2020-08-11T00:40:16+02:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/volume1/@docker/volumes/volume-test/_data",
        "Name": "volume-test",
        "Options": {},
        "Scope": "local"
    }
]

Et voici la commande pour supprimer un volume (après vous être assuré qu'il n'était plus utilisé par un conteneur bien entendu) :

docker volume rm volume-test

Si vous créez un conteneur avec un volume qui n'existe pas, Docker le crée automatiquement pour vous. L'exemple suivant monte le volume volume-test sur le répertoire /app dans le conteneur.

docker run -d --name devtest -v volume-test:/app nginx:latest

Et si vous faites une inspection du conteneur avec la commande docker inspect devtest , vous pourrez voir dans la section Mounts la Source et la Destination, qui correspondent aux répertoires du volume sur l'hôte Docker et dans le conteneur, et que le volume est en lecture-écriture (RW).

"Mounts": [
            {
                "Type": "volume",
                "Name": "volume-test",
                "Source": "/volume1/@docker/volumes/volume-test/_data",
                "Destination": "/app",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

Vous pouvez arrêter puis supprimer le conteneur. Pour rappel, le volume n'est pas supprimé à la suppression du conteneur, vous devez le supprimer manuellement.

docker container stop devtest
docker container rm devtest
docker volume rm volume-test

Si vous créez un conteneur avec un volume, et que le répertoire du conteneur qui est monté sur ce volume contient déjà des données, ces données seront copiées sur le volume.

Prenons pour exemple le même conteneur nginx, qui dans son répertoire /usr/share/nginx/html contient une page d'accueil  index.html.

docker run -d --name=nginxtest -v nginx-vol:/usr/share/nginx/html nginx:latest

Un petit coup de docker inspect nginx-vol vous permet de voir le répertoire sur l'hôte Docker où est stocké le volume.

docker volume inspect nginx-vol
[
    {
        "CreatedAt": "2020-08-11T01:17:35+02:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/volume1/@docker/volumes/nginx-vol/_data",
        "Name": "nginx-vol",
        "Options": null,
        "Scope": "local"
    }
]

Et vous pouvez voir que des fichiers existent déjà alors que nous venons tout juste de créer le conteneur :

ls /volume1/@docker/volumes/nginx-vol/_data
50x.html  index.html

Les commandes suivantes arrêteront le conteneur, le supprimeront, et enfin supprimerons le volume :

docker container stop nginxtest
docker container rm nginxtest
docker volume rm nginx-vol

Nous allons rapidement évoquer l'utilisation de répertoires sur votre hôte, ce qui est parfois plus pratique pour accéder aux données de vos conteneurs depuis votre hôte, notamment pour modifier des fichiers de configuration.

La commande pour lancer un conteneur avec un répertoire en tant que volume est la même qu'avec un volume Docker, c'est au niveau du nom de volume que se fera la différence, puisque nous spécifierons le chemin complet (ou relatif) du répertoire à utiliser. Il faudra penser à créer ce répertoire avant de lancer le conteneur.

mkdir -p /home/docker/nginx
docker run -d --name devtest -v /home/docker/nginx:/app nginx:latest

Si on inspecte le conteneur ainsi créé avec la commande docker inspect devtest, on peut voir que le type de point de montage est bind, soit un lien, et non pas un volume comme dans le premier cas :

"Mounts": [
            {
                "Type": "bind",
                "Source": "/home/docker/nginx-test",
                "Destination": "/app",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }

Vous savez maintenant gérer les volumes Docker en CLI ! Voyons maintenant comment faire via Portainer, le portail de gestion de vos containers que je vous avais présenté.

Améliorer la gestion de vos containers Docker avec Portainer
Après les 2 articles vous permettant de vous familiariser avec Docker et sescontainers (Bitwarden[/bitwarden-le-gestionnaire-de-mots-de-passe-montant-presentation-et-installation/] pour le premier, et l’automatisation du téléchargement de vos séries et films[/initiation-a-docker-sur-synology-et-…

La création d'un volume est très simple. Rendez-vous dans le menu Volumes, puis cliquez sur Créer un volume. Donnez un nom à celui-ci, puis valider la création.

Vous reviendrez alors sur la page listant les volumes existants et retrouverez celui que vous venez de créer.

Pour supprimer un ou plusieurs volume(s), sélectionnez le(s) et cliquer sur "Supprimer".


La sauvegarde des données

Maintenant que vous avez vu comment créer et utiliser les volumes Docker avec vos conteneurs, il vous sera plus aisé de procéder à leur sauvegarde. Vous pouvez utiliser la crontab pour lancer un script qui copiera une archive des volumes de données, un outil de sauvegarde comme Duplicati (lui-même lancé dans un conteneur), ou encore utiliser l'outil Hyper Backup sur Synology. Dans tous les cas, il est recommandé de réaliser ses sauvegardes sur un support externe, à minima un disque dur externe branché sur votre NAS, voire même sur un autre NAS Synology qui est hébergé chez un membre de votre famille, ou encore vers un serveur externe.

En premier lieu, nous allons étudier ce que recommande Docker dans sa documentation, en créant un nouveau conteneur qui monte les volumes à sauvegarder avec l'option --volume-from. Docker recommande dans ce cas de stopper le ou les conteneur(s) d'origine qui utilise(nt) ce volume. Il est ensuite possible de faire une archive du conteneur ainsi créé, ce qui vous permettra de la copier sur une destination externe.

Prenons en exemple le conteneur Bitwarden (voir l'article pour mettre en place ce gestionnaire de mot de passe). Je ne vois pas l'intérêt de créer un nouveau conteneur pour monter le volume, on peut directement faire une archive du répertoire du volume, il suffit d'en connaitre l'emplacement (et pour ça vous devez utiliser la commande docker volume inspect data-bitwarden).

docker container stop bitwarden-server
tar -cvf /volume1/docker/backups/backup_data-bitwarden.tar /volume1/@docker/volumes/data-bitwarden/_data/
docker container start bitwarden-server

Une fois les archives de vos volumes réalisées, vous devez bien entendu les externaliser, et cela peut être fait avec un des 2 outils que je vous présente juste après, Duplicati ou Hyper Backup selon votre environnement.

Enfin, la méthode que j'utilise est de sauvegarder tout le contenu du dossier partagé docker avec l'outil Hyper Backup, sur un disque USB, un second NAS Synology chez moi, et 2 autres NAS Synology qui sont chez des membres différents de ma famille.
Pour installer Hyper Backup sur votre NAS Synology, rendez-vous dans le Centre de Paquets, et cherchez le paquet Hyper Backup, puis cliquez sur Installer. Vous retrouverez ensuite l'icône d'Hyper Backup dans le menu des applications.

Au premier lancement, Hyper Backup vous proposera de créer une première tâche, ce que nous allons faire !

  • Sélectionnez la destination de la sauvegarde (Disque dur & USB ici) puis cliquez sur Suivant. Je suis du genre à multiplier les sauvegardes, j'en fait 1 sur un disque dur branché en USB, 1 sur un second Nas chez moi, et une dernière sur un Nas hébergé dans de la famille, à plus de 50km de mon domicile. On peut croire que ce n'est pas utile, jusqu'au jour où on perd des données...
  • Sélectionnez le dossier cible de la sauvegarde, et indiquez le nom du répertoire qui sera créé pour la contenir, puis cliquez sur Suivant.
  • Sélectionnez le répertoire que vous voulez sauvegarder, docker dans notre cas, puis cliquez sur Suivant.
  • Ce panneau permet de sauvegarder la configuration des applications de votre NAS Synology. Il n'y a pas d'application à sauvegarder dans ce cas, cliquez juste sur Suivant.
  • Donnez un nom à la tâche de sauvegarde, réglez la fréquence et l'heure de la sauvegarde, puis cliquez sur Suivant. Des options sont disponibles pour compresser ou non la sauvegarde, planifier une vérification de l'intégrité de celle-ci ou encore activer le chiffrement côté client.
  • Dans le panneau sur les paramètres de rotation, vous devez définir si vous souhaitez garder toutes les sauvegardes effectuées, mais cela prendrait trop de place dans le temps ou si Hyper Backup doit garder uniquement un nombre défini de versions de la sauvegarde. J'ai choisi d'utiliser la méthode de recyclage "Smart Recycle", qui adopte une certaine intelligence dans la gestion de la rotation, en conservera les sauvegardes des dernières 24 heures, heure par heure, celles du dernier mois, quotidiennement, et hebdomadairement pour tout ce qui remonte à plus d'un mois. En choisissant 50 versions, cela permet d'avoir une rotation sur environ 6 mois.
  • Cliquez sur Appliquer pour finaliser la création de la tâche de sauvegarde. A la question du lancement immédiat de la sauvegarde, cliquez sur Oui.

Avoir une sauvegarde est une bonne chose, mais il est encore mieux de valider que cette-ci fonctionne correctement. Je vous recommande dans un premier temps d'activer les notifications en cas d'erreur dans les tâches Hyper Backup. Et enfin, faites un test de restauration de ces données !

Une sauvegarde n'a de valeur que si elle a été testée avec succès.

Mettre à jour ses conteneurs

Que ce soit pour profiter de nouvelles fonctionnalités, corriger des bugs, appliquer des patchs de sécurité, vous pouvez être amenés à mettre à jour vos conteneurs Docker. Je dirais même qu'il faut les mettre à jour réguièrement. Le processus de mise à jour d'un conteneur Docker est relativement simple et rapide.

Il vous faudra tout d'abord télécharger la nouvelle image correspondante à votre conteneur, soit en spécifiant une version précise, soit en utilisant le tag latest pour obtenir la dernière version disponible.

docker pull nginx:latest

Vous pouvez ensuite arrêter le conteneur, et même le supprimer. Comme on l'a vu juste avant, vous ne perdrez rien si vous utilisez un volume pour stocker les fichiers qui doivent être persistants.

docker container stop nginxtest
docker container rm nginxtest

Enfin, recréez votre conteneur avec son volume et la nouvelle image.

docker run -d --name=nginxtest -v nginx-vol:/usr/share/nginx/html nginx:latest

Un petit nettoyage s'impose pour ne être pollué par un tas d'images anciennes et que vous n'utiliserez plus. Il est de toute façon possible de télécharger à nouveau une image d'une ancienne version, en précisant celle-ci lors du téléchargement.

docker image prune

Dans le cas d'un docker-compose, la procédure est similaire. Vous commencez par télécharger la ou les nouvelle(s) image(s) souhaitée(s) ou vous pouvez mettre à jour toutes les images d'un docker-compose. Pour rappel, il faut être dans le répertoire où se situe le fichier docker-compose.yml.

docker pull nginx:latest
docker-compose pull

Relancez vos containers. Et procédez au nettoyage des images obsolètes.

docker-compose up -d
docker image prune

Les étapes dans Portainer sont les même que celles décrites au-dessus, mais graphiquement.

La mise à jour des conteneurs Docker peut également être automatisée, à l'aide de Watchtower. Ce dernier est un logiciel libre, disponible en tant que conteneur (oui, lui aussi) qui va vérifier à intervalle régulier si des nouvelles versions de vos conteneurs sont disponibles, les télécharger, et relancer vos conteneurs avec les mêmes paramètres, volumes et variables d'environnement, qu'ils soient autonomes ou fassent partie d'un docker-compose. Attention, faire des mises à jour en automatique sans connaitre au préalable les modifications apportées peut être risqué.

Pour le mettre en place et donc automatiser la mise à jour de vos conteneurs, commencez par télécharger l'image adéquate.

docker pull containrrr/watchtower

Puis on crée le conteneur depuis l'image, en montant un volume spécifique, le démon Docker, ce qui permet à Watchtower d'interagir avec les conteneurs, les démarrer ou les stopper, supprimer les images...

docker run \
    --name watchtower \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    containrrr/watchtower

Plusieurs options peuvent être utilisées, --cleanup pour le nettoyage des images obsolètes, --schedule pour planifier, avec un cron (sur 6 caractères), la vérification des mises à jour.

docker run \
    --name watchtower \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --cleanup \
    --schedule "0 0 4 * * *" \
    containrrr/watchtower

Si votre besoin est ponctuel, vous pouvez utiliser l'option --run-once, et également spécifier le nom du container à mettre à jour.

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once \
  nginxtest

Vous pouvez ensuite vérifier les logs de Watchtower.

docker logs watchtower
time="2020-08-11T17:52:00Z" level=info msg="Running a one time update."
time="2020-08-11T17:52:06Z" level=info msg="Checking containers for updated images"
time="2020-08-11T17:52:06Z" level=info msg="Found new nginx:latest image (sha256:6f93e08450c460b73fb3c2818b5b8407789d0fbb714afc4f48f66711d8ac76c2)"
time="2020-08-11T17:52:12Z" level=info msg="Stopping /nginxtest (92c7d6c012081d33a8b6622e6b3ed8f46cc344a4709f42e5fb0583324ea4197a) with SIGTERM"
time="2020-08-11T17:52:13Z" level=info msg="Creating /nginxtest"

Il vous est également possible d'utiliser des labels pour indiquer quelles images vous autorisez Watchtower à mettre à jour. Cela permet de garder un certain contrôle en limitant les mises à jour automatiques sur les conteneurs que vous jugez moins sensibles ou pour lesquels vous avez des attentes particulières.

Il y a 2 manières de procéder. Pour la première, vous choisissez de spécifier que certains de vos conteneurs doivent être ignorés par Watchtower. Pour cela, il vous suffira d'ajouter dans chacun des conteneurs concernés le label com.centurylinklabs.watchtower.enable avec la valeur false.

La deuxième possibilité est d'indiquer à Watchtower, avec l'option --label-enable, qu'il doit surveiller les conteneurs contenant le label com.centurylinklabs.watchtower.enable avec la valeur true. C'est ce choix que j'ai préféré, cela me permet en cas d'oubli de configuration du label, d'être protégé d'une mise à jour involontaire.

Enfin, voici le docker-compose que j'utilise pour Watchtower, où vous retrouverez les différentes options que je vous ai expliqué :

version: "2"

services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    hostname: watchtower
    environment:
      - PUID=1026
      - PGID=100
      - TZ=Europe/Paris
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_REMOVE_VOLUMES=true
      - WATCHTOWER_SCHEDULE=5 5 5 * * *
      - WATCHTOWER_LABEL_ENABLE=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

Conclusion

J'espère que vous en avez appris un peu plus sur les volumes au sein de Docker, leur utilité, l'importance de les sauvegarder régulièrement, ainsi que sur la méthodologie de mise à jour des conteneurs.

Vous pourrez retrouver plus d'informations sur la gestion des volumes et leur sauvegarde dans la documentation officielle Docker ou encore les options possibles de Watchtower.