Gérer les volumes
Objectif de cet atelier pratique
- Gérer les volumes avec Docker et leur montage dans un ou plusieurs conteneurs
Quel est l'intérêt des volumes ?
En règle générale, les images sont construites de manière à ce que les conteneurs basés sur elles soient compacts, portables et jetables.
Les images ne contiennent généralement que les paquets indispensables pour fournir le service prévu par l'image. Les petits fichiers de configuration qui changent rarement sont également inclus dans ces images.
Il vaut mieux pouvoir se débarrasser d'un conteneur sans pour autant craindre de perdre les données importantes. Dans la mesure du possible, il faut éviter que les données d'un conteneur – la partie data si l'on veut – résident uniquement dans le conteneur.
Dans ce cas, si vous voulez rendre persistantes – ou sauvegarder – les données générées ou utilisées par un conteneur, il faudra utiliser un volume.
Si vous souhaitez partager des données entre plusieurs conteneurs, un volume est également le meilleur choix pour ce genre de cas.
Deux approches différentes
Voyons de plus près les différents types de volumes gérés par Docker.
Dans notre atelier pratique sur l'exposition des ports nous avons utilisé
l'option -v avec la commande docker run pour définir l'utilisation d'un
volume avec le conteneur :
$ mkdir -v ~/pagesweb
mkdir: création du répertoire '/home/kikinovak/pagesweb'
$ echo '<h1>Le site web de Nico</h1>' > ~/pagesweb/index.html
$ docker run --name autre_nginx -d -p 8080:80 \
  -v ~/pagesweb:/usr/share/nginx/html:ro nginx
6b456addfb69fb9fe5acdec3dd4e5e7fbad0808edaac0ac28293979b036e4c0d
Option courte vs. option longue
L'option courte -v est l'équivalent exact de l'option longue
--volume :
La nouvelle manière préférée de monter des volumes dans un conteneur est
l'option --mount. Docker recommande d'utiliser --mount plutôt que -v ou
--volume. Quoi qu'il en soit, l'une ou l'autre méthode fonctionne
parfaitement.  Puisque --mount est la méthode recommandée à l'avenir, c'est
celle que nous allons voir plus en détail.
Créer un volume
On utilisera la sous-commande docker volume pour gérer les volumes :
$ docker volume --help
Usage:  docker volume COMMAND
Manage volumes
Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
Créez un premier volume :
Vérifiez s'il a bien été créé :
C'est quoi tous ces volumes bizarres ?
Si vous voyez une liste de volumes avec des noms à coucher dehors, invoquez
la commande docker volume prune pour faire un peu de ménage. Cette
commande supprime tous les volumes locaux qui ne sont pas en cours
d'utilisation.
Supprimez le volume testdata :
À présent, créez un nouveau volume mesdata1 :
Pour en savoir plus sur un volume, utilisez la commande inspect :
$ docker volume inspect mesdata1
[
  {
    "CreatedAt": "2023-07-07T07:20:35+02:00",
    "Driver": "local",
    "Labels": null,
    "Mountpoint": "/var/lib/docker/volumes/mesdata1/_data",
    "Name": "mesdata1",
    "Options": null,
    "Scope": "local"
  }
]
Notez que nous voyons ici l'emplacement réel du volume dans l'arborescence du
système de fichiers de l'hôte. En l'occurrence, les données stockées dans le
volume mesdata1 sont enregistrées dans le répertoire
/var/lib/docker/volumes/mesdata1/_data du système hôte.
Attacher un volume
La prochaine étape consiste à démarrer un conteneur en attachant un volume à ce conteneur de manière à ce que le conteneur ait accès aux données du volume :
$ docker run -d --name avecvolume --mount \
  source=mesdata1,destination=/root/volume nginx
b15b836717e9d4462ffbebcdbe6fb111f86490a0bedc9b21ad25aab142091f8a
Syntaxe
Veillez à ne pas utiliser d'espaces dans les arguments à --mount !
Inspectez le conteneur et jetez un œil à la section Mounts :
$ docker inspect avecvolume | grep -A 10 Mounts
...
  "Mounts": [
    {
      "Type": "volume",
      "Name": "mesdata1",
      "Source": "/var/lib/docker/volumes/mesdata1/_data",
      "Destination": "/root/volume",
      "Driver": "local",
      "Mode": "z",
      "RW": true,
      "Propagation": ""
    }
- 
La section Mountsnous affiche que/root/volumeest la destination du volume à l'intérieur du conteneur, avec des droits en lecture/écriture comme indiqué par la ligne"RW": true.
- 
Le chemin d'accès /var/lib/docker/volumes/mesdata1/_datacorrespond au chemin du volume que Docker a mis en place sur le disque du système hôte. C'est donc la source.
Travailler moins pour taper plus
Au lieu d'utiliser source= et destination=, nous pouvons utiliser les
formes brèves src= et dst= :
On peut également utiliser target= au lieu de destination= ou dst=.
Supprimer un volume
Lorsque vous arrêtez un conteneur, le volume reste intact, et vous devez le
supprimer séparément si vous le souhaitez. Pour ce faire, vous utiliserez la
commande docker volume rm avec le nom du volume en argument.
Ajouter un fichier depuis l'hôte
Ajoutez un fichier au volume depuis le système hôte :
$ echo 'Coucou depuis le volume mesdata1 !' |
> sudo tee /var/lib/docker/volumes/mesdata1/_data/index.html
Coucou depuis le volume mesdata1 !
Utilisez la commande exec pour vous connecter au conteneur et vérifiez si le
fichier index.html se trouve bien à l'emplacement attendu :
$ docker exec -it avecvolume bash
root@480fadc6bbb2:/# cd /root/volume
root@480fadc6bbb2:~/volume# ls -l
total 4
-rw-r--r--. 1 root root 35 Jul  7 05:35 index.html
root@480fadc6bbb2:~/volume# cat index.html 
Coucou depuis le volume mesdata1 !
root@480fadc6bbb2:~/volume# exit
Un volume pour plusieurs conteneurs
Docker permet de monter le même volume sur plusieurs conteneurs. Essayez :
$ docker run -d --name avecvolume2 --mount \
  src=mesdata1,dst=/root/volume nginx
046c17291d106720eca84ab5bbf60370406c676a884b7a5eb852d7dde8b8307b
$ docker exec -it avecvolume2 bash
root@046c17291d10:/# cat /root/volume/index.html
Coucou depuis le volume mesdata1 !
root@046c17291d10:/# exit
Un volume en lecture seule
Admettons que vous ne voulez pas que le conteneur puisse modifier le contenu d'un volume donné. Autrement dit, vous souhaitez que le conteneur accède au volume en lecture seule :
$ docker run -d --name conteneurenlecture --mount \
  src=nouveauvolume,dst=/usr/share/nginx/html,ro nginx
8b5210c998c4d33a80cc1cfbc4a85510cfc2cb4b454ce8e209e487a353703fc9
Si le volume n'existe pas lors du lancement du conteneur, Docker se charge de le créer automatiquement :
Inspectez la section Mounts de ce conteneur :
$ docker inspect conteneurenlecture | grep -A 10 Mounts
...
  "Mounts": [
    {
      "Type": "volume",
      "Name": "nouveauvolume",
      "Source": "/var/lib/docker/volumes/nouveauvolume/_data",
      "Destination": "/usr/share/nginx/html",
      "Driver": "local",
      "Mode": "z",
      "RW": false,
      "Propagation": ""
    }
Notez bien le paramètre "RW": false qui indique que le conteneur est en
lecture seule. L'utilisation de cette option fait partie des bonnes pratiques
en matière de sécurité lorsque l'accès en écriture n'est pas requis. Ainsi,
lorsqu'un conteneur est compromis dans le contexte d'une attaque informatique,
les données du ou des volumes correspondants restent intactes.
Lecture seule vs. lecture/écriture
Vous pouvez très bien autoriser certains conteneurs à accéder en lecture/écriture à un volume donné, alors que d'autres conteneurs n'y accèderont qu'en lecture seule. En effet, la possibilité d'écrire sur un volume est définie au niveau du conteneur et non pas au niveau du volume.
Vérifions si notre volume est effectivement monté en lecture seule à l'intérieur du conteneur :
$ docker exec -it conteneurenlecture bash
root@8b5210c998c4:/# touch /usr/share/nginx/html/test
touch: cannot touch '/usr/share/nginx/html/test': Read-only file system
root@8b5210c998c4:/# exit
Utiliser un volume éphémère
Lorsqu'on utilise des volumes qui sont censés être détruits après utilisation,
on peut très bien utiliser ce qu'on appelle les volumes éphémères de type
tmpfs. Voici à quoi cela peut ressembler :
$ docker run -dit --name ephemere --mount \
  type=tmpfs,dst=/root/volume nginx
943e634f246a34c97c6e4b239204232239598d8c947aa7668319b41673e13af3
Jetez un œil dans la section Mounts de ce conteneur et notez bien le type
tmpfs :
$ docker inspect ephemere | grep -A 10 Mounts
...
  "Mounts": [
    {
      "Type": "tmpfs",
      "Source": "",
      "Destination": "/root/volume",
      "Mode": "",
      "RW": true,
      "Propagation": ""
    }
Un volume éphémère à taille prédéfinie
Vous pouvez prédéfinir une taille statique pour un volume éphémère, ce qui vous permet de garder le contrôle sur l'espace disque utilisé :
$ docker run -dit --name ephemere2 --mount \
  type=tmpfs,tmpfs-size=256M,dst=/root/volume nginx
a7bf7dec6fb5da992800d18db81ba2229fe0a29c7d1205b0f31314d26b32a1a9
Vérifions si l'espace disque de notre volume éphémère est effectivement limité à 256 Mo :
$ docker exec -it ephemere2 df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         1.8T  457G  1.3T  27% /
tmpfs            64M     0   64M   0% /dev
tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
shm              64M     0   64M   0% /dev/shm
/dev/md127      1.8T  457G  1.3T  27% /etc/hosts
tmpfs           256M     0  256M   0% /root/volume
tmpfs           7.8G     0  7.8G   0% /proc/asound
tmpfs           7.8G     0  7.8G   0% /proc/acpi
tmpfs           7.8G     0  7.8G   0% /proc/scsi
tmpfs           7.8G     0  7.8G   0% /sys/firmware
Du point de vue du conteneur, nous ne pouvons pas ajouter plus de 256 Mo de
données dans /root/volume.
L'utilisation de volumes éphémères est considéré comme une bonne pratique lorsqu'on a besoin d'espace de stockage temporaire pour un conteneur et qu'on souhaite faire le ménage une fois que le conteneur s'arrête.
Utiliser l'approche classique
Même si l'option --mount est la méthode recommandée pour gérer les volumes,
l'option -v reste une syntaxe valide, et vous la verrez régulièrement dans la
documentation :
$ docker run -dit -p 8080:80 --name nginx-avec-vol \
  -v ~/pagesweb:/usr/share/nginx/html:ro nginx
f3eedd7b4ba996626aa096a8608ca536ab613b5bdadbcaaa28572f10602da1d8
Inspectez la section Mounts de ce conteneur :
$ docker inspect nginx-avec-vol | grep -A 10 Mounts
...
  "Mounts": [
    {
      "Type": "bind",
      "Source": "/home/kikinovak/pagesweb",
      "Destination": "/usr/share/nginx/html",
      "Mode": "ro",
      "RW": false,
      "Propagation": "rprivate"
    }
Notez le type de montage bind qui correspond à l'ancienne méthode pour
accéder aux répertoires du système hôte. Ce genre de volume offre moins de
fonctionnalités que l'option --mount.
Un brin de ménage
Arrêtez tous les conteneurs en état de marche :
$ docker stop nginx-avec-vol ephemere ephemere2 \
  conteneurenlecture avecvolume avecvolume2
nginx-avec-vol
ephemere
ephemere2
conteneurenlecture
avecvolume
avecvolume2
Supprimez tous ces conteneurs :
$ docker rm nginx-avec-vol ephemere ephemere2 \
  conteneurenlecture avecvolume avecvolume2
nginx-avec-vol
ephemere
ephemere2
conteneurenlecture
avecvolume
avecvolume2
Affichez les volumes disponibles sur notre système :
Supprimez ces volumes :
À vous de jouer !
Challenge n° 1
- 
Créez un volume nommé volumelocal.
- 
Inspectez le volume et déterminez l'emplacement exact où seront stockées les données du volume sur le système hôte. 
- 
Créez un fichier texte fichier.txtdans le volume, avec le contenuCe fichier existe.
- 
Démarrez un conteneur détaché nommé montagevolumebasé sur l'image du serveur web Apachehttpd. Montez le volumevolumelocaldans le répertoire/datadu conteneur.
- 
Connectez-vous au conteneur montagevolumeavec un shell Bash.
- 
Vérifiez si le volume est disponible à l'intérieur du conteneur. 
- 
Vérifiez si le fichier fichier.txtexiste.
- 
Affichez le contenu de ce fichier. 
- 
Depuis le conteneur, créez un fichier texte conteneur.txtavec le contenuCoucou depuis le conteneur.
- 
Toujours depuis le conteneur, vérifiez si le fichier a été créé correctement. 
- 
Détachez-vous du conteneur. 
- 
Vérifiez l'existence et le contenu du fichier conteneur.txtdepuis le système hôte.
Challenge n° 2
- 
Lancez un conteneur détaché nommé ephemereet basé sur l'image du serveur Web Apachehttpd. Attachez un volume éphémère d'une taille de 128 Mo au répertoire/tempdatadu conteneur.
- 
Inspectez le conteneur en affichant les caractéristiques du volume éphémère. 
- 
Connectez-vous au conteneur et affichez la taille de /tempdata.
