Ça fait un petit moment maintenant que l’on entend un peu partout parler de la plateforme de virtualisation révolutionnaire qu’est Docker. Tellement qu’OVH va même la proposer sur son cloud (ou la propose déjà ?) via sa filiale RunAbove. En tant que passionné je ne pouvais pas passer à côté de cette techno, et je ne pouvais pas non plus m’empêcher de partager tout cela avec vous.
Je vous propose donc de découvrir Docker et ce que nous allons pouvoir faire avec, et on commence aujourd’hui avec un peu de théorie.
A la découverte de Docker
Docker est donc une plateforme de virtualisation par conteneur qui, contrairement à la virtualisation sur hyperviseur où vous devez créer de nouvelles machines complètes pour isoler celles-ci les unes des autres et s’assurer de leur indépendance, va vous permettre de créer des conteneurs qui vont uniquement contenir votre ou vos applications. Empaquetées sous forme de conteneurs, ces applications pourront ainsi être facilement déployées sur tout hôte exécutant Docker, chaque conteneur restant parfaitement indépendant !
Le nom et le vocabulaire de cette technologie n’ont pas été choisis au hasard : tout comme les conteneurs en métal, toujours fabriqués selon les mêmes standards, peuvent être pris en charge par n’importe quel transporteur (et peu importe ce qu’il y a dedans), une application « Dockerisée » doit pouvoir être exécutée sur n’importe quel hôte faisant tourner Docker, peu importe son contenu !
Avec la virtualisation par hyperviseur de type 2 (VMware Workstation, VirtualBox…), chaque hôte invité virtualise un environnement complet, ce qui permet notamment de pouvoir exécuter un système virtualisé différent du système hôte (typiquement du Windows sur une Red Hat par exemple).
La virtualisation par conteneur telle que Docker la propose permet à un système Linux de contenir un ou plusieurs processus dans un environnement d’exécution indépendant : indépendant du système hôte et des conteneurs entre eux.
Dans ce cas, conteneur virtualisé et système hôte sont fortement liés et c’est pour cela que cette technologie, qui existe pourtant depuis longtemps (OpenVZ a déjà presque 10 ans !), était peu répandue jusqu’à maintenant. Certains appellent d’ailleurs ce type de virtualisation la para-virtualisation… Mais alors comment ça marche ?
Docker simplifie l’utilisation d’outils existants
Et c’est sa principale force. S’il est possible d’isoler des conteneurs : processus, interfaces réseau, points de montage, c’est grâce à l’utilisation des namespaces Linux. Un processus qui s’exécute dans un espace de nom correspondant à un conteneur ne sera visible que par ce conteneur et le système hôte exécutant ce conteneur, point. Les deux précédents schémas vous feront comprendre aussi que la virtualisation par conteneur est plus rapide que la virtualisation par hyperviseur car les conteneurs ont directement accès aux ressources matérielles de l’hôte. La consommation de ces ressources pouvant être gérées par l’hôte en question grâce aux control groups (cgroups) du noyau Linux.
Tout ceci fait donc de Docker un chroot dopé aux stéroïdes.
À ses débuts Docker était une couche d’abstraction à LXC car il permettait de s’affranchir de la complexité de ce dernier en gérant pour nous un certain nombre d’opérations, plus facilement. Aujourd’hui Docker va beaucoup plus loin que LXC puisqu’il dispose de sa propre plate-forme : Libcontainer (bien que LXC soit toujours proposé sous forme de plugin).
Et ce qui démarque Docker des autres, c’est la rapidité des flux de travail que vous allez pouvoir mettre en place : tout est fait pour réduire le temps entre le développement, les tests, le déploiement et l’utilisation de votre application en production. Et pour cela, on trouve les images.
Une image est le composant de base de Docker. C’est un instantané d’un système d’exploitation (par exemple Debian). Ces images sont disponibles sur un dépôt public, géré par Docker, appelé le registre. Schématisons ceci de manière plus précise…

L’exemple ci-dessus se compose de deux conteneurs, parfaitement isolés (d’où les couleurs différentes). On trouve à la base les composants nécessaires à Docker, et qui sont fournis par le noyau Linux de l’hôte. On trouve ensuite deux images : Debian et BusyBox.
Une image ne peut être modifiée directement, elle reste toujours en lecture seule.
Remarquez qu’un environnement d’exécution peut nécessiter l’empilement de plusieurs images (ici emacs, puis Nginx), rendu possible par les fonctionnalités de copy-on-write des modules de stockages utilisés par Docker (AuFS, Btrfs).
Enfin, on trouve la couche supérieure, accessible en écriture, qui contiendra toutes les modifications apportées à l’application.
Pour résumer : lorsque nous allons lancer un conteneur pour exécuter une application à partir d’une image, Docker va monter le système de fichier de l’image souhaitée en lecture seule, et ajouter une couche accessible en écriture par dessus, via UnionFS.
On fait donc de sacrées économies de consommation puisque plusieurs conteneurs utilisant la même image de base utiliseront le même système de fichiers !
Le Hub, un GitHub pour vos images Docker
Si vous souhaitez conserver les modifications que vous avez apporté à votre application, vous pouvez enregistrer celles-ci dans un nouvelle image qui sera uniquement composée des différences apportées. Cette image sera identifiée par Docker grâce à un numéro unique, et vous pourrez pousser (push) et stocker cette image vers le dépôt public Docker.
À travers le Docker Hub, vous aller pouvoir gérer vos images de la même manière que vous gérer votre code avec Git. Le vocabulaire est le même…
Pour conclure cette première approche…
J’espère que je n’ai perdu personne… Docker repose sur des concepts qui ne sont pas forcément évidents au premier abord. Vous verrez néanmoins que toutes ces explications seront plus claires lorsque nous commencerons à manipuler images et conteneurs.
Installation de Docker
Ici pas de secret, il suffit de suivre les instructions proposées par le site officiel. On nous informe que Docker a besoin du noyau Linux 3.8 ou supérieur pour fonctionner, et que Debian Stable est toujours sur le noyau 3.2. On va donc devoir activer les dépôts backports afin d’aller y récupérer la dernière version du noyau Linux.
Normalement vous êtes sur une Debian récente donc vous n’avez pas besoin des étapes suivantes
Pour cela il suffit d’éditer le fichier /etc/apt/sources.list, et d’y ajouter :
deb http://http.debian.net/debian wheezy-backports main
On enregistre, puis :
sudo apt-get update sudo apt-get -t wheezy-backports install linux-image-amd64
Redémarrons pour que le nouveau noyau soit chargé. Nous pouvons enfin récupérer le script d’installation de Docker 🙂
$ curl -sSL https://get.docker.com/ | sh
Sous Debian 10, vous n’avez pas besoin de faire cela !
Exécuter Docker sans les droits root
La documentation nous indique que le daemon Docker tourne toujours avec l’utilisateur root, et qu’il se connecte à un socket Unix en lieu et place d’un socket TCP. L’utilisateur root est par défaut propriétaire de ce socket Unix, et ne peut donc être accessible qu’avec la commande sudo
.
Pour éviter d’utiliser sudo
à tort et à travers, nous
allons pouvoir créer un groupe Unix nommé ‘docker’ pour nous y ajouter.
Ainsi, les propriétés du daemon docker seront modifiées pour que
celui-ci soit accessible en lecture / écriture pour les membres du
groupe docker. Certes le daemon doit toujours fonctionner en tant que
root, mais si nous utilisons le client docker en tant que membre du
groupe docker, alors nous n’aurons plus besoin d’utiliser sudo
.
C’est du bonus hein… Cela permet aussi de gérer la surface d’attaque de Docker, mais vous pouvez très bien sauter cette étape et continuer à utiliser Docker avec sudo
.
# On ajoute le groupe docker s'il n'existe pas déjà.
$ sudo groupadd docker
# On ajoute l'utilisateur courant (nous) "${USER}" au groupe.
# On peut y mettre n'importe quel utilisateur
# Il faudra peut-être de reconnecter pour que les modifications prennent effet.
$ sudo gpasswd -a ${USER} docker
# On redémarre le daemon.
$ sudo service docker restart
Alors, c’était dur ?
docker version
Client:
Version: 18.09.7
API version: 1.39
Go version: go1.10.1
Git commit: 2d0083d
Built: Wed Jul 3 12:13:59 2019
OS/Arch: linux/amd64
Experimental: false
Server:
Engine:
Version: 18.09.7
API version: 1.39 (minimum version 1.12)
Go version: go1.10.1
Git commit: 2d0083d
Built: Mon Jul 1 19:31:12 2019
OS/Arch: linux/amd64
Experimental: false
Commençons à jouer avec les images
Avant de pouvoir commencer à jouer avec les conteneurs, il va falloir récupérer une image.
Je vous avais expliqué que les images Docker sont des instantanés de systèmes d’exploitation sur lesquels nous allons pouvoir travailler et manipuler des conteneurs.
Les images Docker
Tout un tas d’images sont maintenues et disponibles sur le registre officiel : le Docker Hub.
Imaginons que je souhaite récupérer une image d’Ubuntu. Je vais alors utiliser la commande search
pour retrouver toutes les images Ubuntu du registre :
$ docker search ubuntu
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
ubuntu Official Ubuntu base image 1159 [OK]
dockerfile/ubuntu Trusted automated Ubuntu (http://www.ubunt... 39 [OK]
ansible/ubuntu14.04-ansible Ubuntu 14.04 LTS with ansible 30 [OK]
dockerfile/ubuntu-desktop Trusted automated Ubuntu Desktop (LXDE) (h... 20 [OK]
ubuntu-upstart Upstart is an event-based replacement for ... 17 [OK]
[...]
Vous verrez qu’un grand nombre de résultats sont proposés, c’est pourquoi j’ai volontairement tronqué la sortie. On observe que le premier résultat indique une image d’Ubuntu officielle, ce qui nous assure qu’elle a été validée par l’équipe de Docker. Les images gérées par l’équipe de Docker se remarquent par le format de leur nom, qui est de la forme nom_image alors que les non-officielles sont plutôt de la forme utilisateur/nom_image.
Je décide de récupérer l’image Ubuntu officielle :
$ docker pull ubuntu:14.04
Vous noterez l’ajout du :14.04
au nom de l’image. Ceci afin d’éviter de récupérer toutes les version d’Ubuntu présentes dans le dépôt Ubuntu. Après quelques secondes de téléchargement (l’image fait 200Mo), la nouvelle image est prête !
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04 b39b81afc8ca 13 hours ago 188.3 MB
Heu on pourrait avoir des infos sur ce qu’est un Tag ou un ID ?
La sortie de la commande précédente indique que nous avons récupéré l’image portant l’ID b39b81afc8ca dans le dépôt intitulé « ubuntu ». Un ID permet d’identifier de manière unique la version d’une image. Et oui, un dépôt contient plusieurs versions d’une image, sous forme de couches de systèmes de fichiers. Je vous en avais parlé précédemment.
Ensuite, comme un ID n’est pas forcément explicite pour désigner une version, on lui affecte un ou plusieurs tags, ici « 14.04 ». Plusieurs tags peuvent effectivement désigner la même image, mais chaque version d’image ne possède qu’un ID.
Cet empilement de version et de systèmes de fichier peut être affiché avec l’option --all
$ docker images --all
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04 b39b81afc8ca 13 hours ago 188.3 MB
<none> <none> 615c102e2290 13 hours ago 188.3 MB
<none> <none> 837339b91538 13 hours ago 188.3 MB
<none> <none> 53f858aaaf03 13 hours ago 188.1 MB
<none> <none> 511136ea3c5a 19 months ago 0 B
Ok, mais à quoi correspondent toutes ces images intermédiaire ?
Je ne voudrais pas vous embrouiller tout de suite… Elles correspondent en fait aux différentes commandes qui ont été utilisées pour générer l’image finale. Ces commandes peuvent être affichées avec la commande history
.
$ docker history ubuntu:14.04
IMAGE CREATED CREATED BY SIZE
b39b81afc8ca 13 hours ago /bin/sh -c #(nop) CMD [/bin/bash] 0 B
615c102e2290 13 hours ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/ 1.895 kB
837339b91538 13 hours ago /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic 194.5 kB
53f858aaaf03 13 hours ago /bin/sh -c #(nop) ADD file:ca5b63647c6b7a419e 188.1 MB
511136ea3c5a 19 months ago 0 B
Nous y reviendrons quand nous créerons nos propres images, pas de panique 🙂
Et tout ce bazar, il est stocké où ?
$ docker info
docker info
Containers: 0
Images: 5
Storage Driver: aufs
Root Dir: /var/lib/docker/aufs
Dirs: 5
Execution Driver: native-0.2
Kernel Version: 3.16.0-0.bpo.4-amd64
Operating System: Debian GNU/Linux 7 (wheezy)
CPUs: 2
Total Memory: 3.86 GiB
Name: dev
ID: E7WT:PPQD:Z6IE:ZGW2:42EC:T5IE:KXLQ:CIRB:WI54:KJG7:KUYM:SYFB
WARNING: No memory limit support
WARNING: No swap limit support
Voilà toutes les informations relatives notre installation de Docker. Pour répondre à la question, la ligne qui nous intéresse est donc le Root Dir, soit /var/lib/docker/aufs :
$ sudo ls -l /var/lib/docker/aufs/*
/var/lib/docker/aufs/diff:
total 20
drwxr-xr-x 2 root root 4096 Jan 17 11:42 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
drwxr-xr-x 21 root root 4096 Jan 17 11:44 53f858aaaf03033e088d357df23e311d71aa93ac578ef39aa8230580ea95076f
drwxr-xr-x 3 root root 4096 Jan 17 11:44 615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e
drwxr-xr-x 6 root root 4096 Jan 17 11:44 837339b915388417a842c87a681a5448df2509068c8d3efd1638f1fad2eacea2
drwxr-xr-x 2 root root 4096 Jan 17 11:44 b39b81afc8cae27d6fc7ea89584bad5e0ba792127597d02425eaee9f3aaaa462
/var/lib/docker/aufs/layers:
total 16
-rw-r--r-- 1 root root 0 Jan 17 11:42 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
-rw-r--r-- 1 root root 65 Jan 17 11:44 53f858aaaf03033e088d357df23e311d71aa93ac578ef39aa8230580ea95076f
-rw-r--r-- 1 root root 195 Jan 17 11:44 615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e
-rw-r--r-- 1 root root 130 Jan 17 11:44 837339b915388417a842c87a681a5448df2509068c8d3efd1638f1fad2eacea2
-rw-r--r-- 1 root root 260 Jan 17 11:44 b39b81afc8cae27d6fc7ea89584bad5e0ba792127597d02425eaee9f3aaaa462
/var/lib/docker/aufs/mnt:
total 20
drwxr-xr-x 2 root root 4096 Jan 17 11:42 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
drwxr-xr-x 2 root root 4096 Jan 17 11:44 53f858aaaf03033e088d357df23e311d71aa93ac578ef39aa8230580ea95076f
drwxr-xr-x 2 root root 4096 Jan 17 11:44 615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e
drwxr-xr-x 2 root root 4096 Jan 17 11:44 837339b915388417a842c87a681a5448df2509068c8d3efd1638f1fad2eacea2
drwxr-xr-x 2 root root 4096 Jan 17 11:44 b39b81afc8cae27d6fc7ea89584bad5e0ba792127597d02425eaee9f3aaaa462
Ce retour n’est pas sans rappeler le contenu de .git/objects pour les utilisateurs de Git, nous verrons plus tard que ce n’est absolument pas un hasard si nous retrouvons une syntaxe et une philosophie de versionning très proches de Git.
Enfin, pour connaître tous les détails d’une image :
docker inspect ubuntu:14.04
[{
"Architecture": "amd64",
"Author": "",
"Checksum": "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"Comment": "",
"Config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/bash"
],
"CpuShares": 0,
"Cpuset": "",
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"ExposedPorts": null,
"Hostname": "2338b11efbe1",
"Image": "615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e",
"MacAddress": "",
"Memory": 0,
"MemorySwap": 0,
"NetworkDisabled": false,
"OnBuild": [],
"OpenStdin": false,
"PortSpecs": null,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": null,
"WorkingDir": ""
},
"Container": "485a6058231fa39ea20d4c7a526ff5916aee18284fd1fa7d78e1e5bea7ff662b",
"ContainerConfig": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/sh",
"-c",
"#(nop) CMD [/bin/bash]"
],
"CpuShares": 0,
"Cpuset": "",
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"ExposedPorts": null,
"Hostname": "2338b11efbe1",
"Image": "615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e",
"MacAddress": "",
"Memory": 0,
"MemorySwap": 0,
"NetworkDisabled": false,
"OnBuild": [],
"OpenStdin": false,
"PortSpecs": null,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": null,
"WorkingDir": ""
},
"Created": "2015-01-16T21:25:30.510167396Z",
"DockerVersion": "1.4.1",
"Id": "b39b81afc8cae27d6fc7ea89584bad5e0ba792127597d02425eaee9f3aaaa462",
"Os": "linux",
"Parent": "615c102e2290b70d38d89c03a1ad263da8bd8b05fb7fc8479174e5fd2215520e",
"Size": 0,
"VirtualSize": 188305056
}
]
Le mot de la fin
Voilà j’espère que cette première approche vous a plu. Notre environnement de travail est maintenant prêt et nous allons pouvoir commencer à rentrer dans le vif du sujet, à savoir manipuler les conteneurs.
Lancer des conteneurs Docker
Préparation
Voilà donc où nous en étions :
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04 b39b81afc8ca 36 hours ago 188.3 MB
Sauf que finalement nous n’allons pas travailler sur une image d’Ubuntu, mais plutôt de Debian.
Plutôt que de travailler avec une image d’Ubuntu, je vais lui préférer une image de Debian, deux fois moins lourde.
# Je récupère la dernière image de Debian
$ docker pull debian:latest
Manipulation de nos premiers conteneurs
Mais au fait, c’est quoi déjà un conteneur ?
Pas de panique c’est très simple : un conteneur est une instance active d’une image. On lance un conteneur afin d’y exécuter un ou plusieurs processus, et ce avec la commande run
.
$ docker run debian:latest /bin/echo 'Hello World!'
Hello World!
Félicitation, vous avez lancé votre premier conteneur !
Mais que s’est-il passé ?
Nous avons demandé à Docker de lancer un conteneur à partir de l’image debian:latest grâce à la commande run
.
Docker a donc cherché l’image debian:latest sur l’hôte local, ou
l’aurait automatiquement téléchargée si elle n’avait pas été présente.
Une fois le conteneur instancié, Docker a exécuté la commande /bin/echo 'Hello World!'
à l’intérieur de celui-ci et nous a retourné le résultat : Hello World!
Qu’est-il arrivé à notre conteneur ensuite ?
Et bien Docker l’a tout simplement terminé. Retenez bien cela : la durée de vie d’un conteneur dépend directement du temps de vie des processus qui y sont exécutés ! Ici en l’occurrence, notre conteneur a « vécu » le temps que la commande /bin/echo 'Hello World!'
s’exécute.
Essayons autre chose :
$ docker run -t -i debian:latest /bin/bash
root@82a7a75b7d25:/#
Nous avons passé deux options à la commande run
: -t
assigne un terminal à l’intérieur du conteneur, et -i
nous autorise à nous y connecter en récupérant directement l’entrée standard (STDIN). Notre conteneur est donc lancé, et nous nous retrouvons avec un shell à l’intérieur. Essayez diverses commandes, vous verrez que c’est comme si vous étiez connectés à une Debian classique. Une fois vos expériences terminées, utilisez exit
pour fermer le shell. La commande /bin/bash
est terminée, et donc notre conteneur avec.
Des conteneurs qui exécutent des commandes et s’arrêtent une fois la commande terminée ? On m’explique l’intérêt ?
Ok, l’intérêt est limité pour le moment. C’était histoire de lancer un premier conteneur… Essayons de « daemoniser » notre Hello World! :
$ docker run -d debian:latest /bin/sh -c "while true; do echo hello world; sleep 1; done"
a0018e7b5dd3c93b056c4086ca811233881570058a2dd8011360a25e7e84f0b7
Le retour ressemble à tout sauf à « Hello World! ». C’est normal, remarquez que nous avons ajouté l’option -d
, qui indique à Docker d’instancier un conteneur à partir de l’image debian:latest, et de le placer en arrière-plan, de le « daemoniser ». Le code retourné est juste l’identifiant unique de notre conteneur.
Tu veux dire que là j’ai un conteneur de démarré qui fait un
/bin/echo 'Hello World!'
toutes les secondes ?!
Exactement ! D’ailleurs, pour le vérifier, nous pouvons utiliser la commande docker ps
, qui demande à Docker des informations à propos de tous les conteneurs en cours d’exécution.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0018e7b5dd3 debian:latest "/bin/sh -c 'while t 3 minutes ago Up 3 minutes
Quelques remarques à propos du résultat :
- On retrouve bien l’ID de notre conteneur (a0018e7b5dd3)
- Ce conteneur a hérité d’un nom (determined_bell), automatiquement attribué par Docker, ceci afin de pouvoir manipuler notre conteneur plus facilement qu’avec son ID. Il est tout a fait possible de choisir un nom avec l’option
--name
- Où sont passés les conteneurs précédents ? Ceux qui s’étaient arrêtés ?
L’option -a
de la commande ps
nous permet d’afficher tous les conteneurs, y compris ceux arrêtés :
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0018e7b5dd3 debian:latest "/bin/sh -c 'while " 9 minutes ago Up 9 minutes determined_bell
02a9b1d3cc9a debian:latest "/bin/bash" 19 minutes ago Exited (0) 19 minutes ago thirsty_elion
c8367cbba7ff debian:latest "/bin/echo 'Hello W" 32 minutes ago Exited (0) 32 minutes ago
Mais revenons à notre Hello World daemonisé. Le conteneur est bien lancé, mais fait-il ce que nous lui avons demandé ? Pour le vérifier, nous allons utiliser la commande logs
:
$ docker logs determined_bell
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
[...]
Cette commande retourne la sortie standard (STDOUT) d’un conteneur donné.
Quelques commandes pour manipuler nos conteneurs
Je profite d’avoir un conteneur lancé pour vous parler de quelques commandes utiles. Par exemple, il est tout à fait possible de mettre un conteneur en pause !
$ docker pause determined_bell
determined_bell
$ docker unpause determined_bell
determined_bell
Celui-ci se figera tant qu’il n’est pas sorti de pause, via la commande docker unpause
.
Pour arrêter un conteneur, nous avons la commande docker stop
(ou kill
si vous souhaitez passer un code particulier) :
$ docker stop determined_bell
determined_bell
$ docker -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0018e7b5dd3 debian:latest "/bin/sh -c 'while t 26 minutes ago Exited (-1) 47 seconds ago
Le conteneur est bien arrêté.
Il est aussi possible de redémarrer un conteneur arrêté. Prenons par exemple le conteneur où nous avions exécuté un shell interactif (il s’intitulait insane_kowalevski chez moi). Pour le démarrer, il y a la commande start
$ docker start insane_kowalevski
insane_kowalevski
La commande attach
nous permet de nous reconnecter à ce conteneur :
$ docker attach insane_kowalevski
root@82a7a75b7d25:/#
Les commits sous Docker
Je vous propose de voir comment est-ce que nous pourrions « dockeriser » une application PHP classique.
Reprenons où nous en étions restés :
docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0018e7b5dd3 debian:latest "/bin/sh -c 'while t" 3 weeks ago Exited (-1) 3 weeks ago determined_bell
82a7a75b7d25 debian:latest "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago insane_kowalevski
c8367cbba7ff debian:latest "/bin/echo 'Hello Wo" 3 weeks ago Exited (0) 3 weeks ago
Nous nous étions amusés à lancer quelques conteneurs pour voir comment les manipuler. Comme base pour notre application web, qui se compose d’un serveur web, de PHP et d’une base de données, je vous propose de réutiliser le conteneur avec le shell. Et pour plus de commodité, je vais renommer ce conteneur en « sonerezh ».
docker rename insane_kowalevski sonerezh
docker start sonerezh
docker attach sonerezh
root@82a7a75b7d25:/#
Je travaille maintenant dans mon conteneur, les commandes passées à l’hôte seront précédées de
admin@docker
, et celles passées au conteneur parroot@646c9d0d39a0:/#
.
Mettons à jour notre image de Debian.
root@646c9d0d39a0:/# apt-get update && apt-get upgrade
[...]
Vous vous souvenez quand je vous avais dis que lorsque vous instanciiez un conteneur, Docker ajoutais au dessus de celui-ci une couche accessible en lecture et écriture ? Il est possible de voir les différences qui ont été apportées par rapport à votre image d’origine avec la commande :
admin@docker:~$ docker diff sonerezh
C /root
A /root/.bash_history
C /var
C /var/lib
C /var/lib/apt
[...]
Les lignes précédées d’un A représentent les ajouts, d’un C les modification et D les suppressions.
Je vous propose maintenant d’installer Nginx, avec un simple :
root@646c9d0d39a0:/# apt-get install nginx
root@646c9d0d39a0:/# service nginx start
Et c’est tout ! Nginx est démarré sur notre conteneur ! Vous ne me croyez pas ? Plusieurs moyens de le vérifier :
Depuis le conteneur :
root@646c9d0d39a0:/# ls -ld /proc/[0-9]* | grep www-data
dr-xr-xr-x 9 www-data www-data 0 Feb 10 20:51 /proc/598
dr-xr-xr-x 9 www-data www-data 0 Feb 10 20:51 /proc/599
dr-xr-xr-x 9 www-data www-data 0 Feb 10 20:51 /proc/600
dr-xr-xr-x 9 www-data www-data 0 Feb 10 20:51 /proc/601
Depuis l’hôte :
admin@docker:~$ docker top sonerezh
UID PID PPID C STIME TTY TIME CMD
root 6294 6084 0 21:38 pts/1 00:00:00 /bin/bash
root 6933 6294 0 21:50 ? 00:00:00 nginx: master process /usr/sbin/nginx
www-data 6934 6933 0 21:50 ? 00:00:00 nginx: worker process
www-data 6935 6933 0 21:50 ? 00:00:00 nginx: worker process
www-data 6936 6933 0 21:50 ? 00:00:00 nginx: worker process
www-data 6937 6933 0 21:50 ? 00:00:00 nginx: worker process
Et enfin, vous pouvez aussi le vérifier en accédant directement à l’adresse du conteneur :
admin@docker:~$ wget -q -O - http://172.17.0.4
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor="white" text="black">
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>
Pour connaître l’adresse IP d’un conteneur :
root@646c9d0d39a0:/# ip -4 -o a show eth0
14: eth0 inet 172.17.0.4/16 scope global eth0\ valid_lft forever preferred_lft forever
Et côté conteneur, ou :
guillaume@docker:~$ docker inspect --format "{{.NetworkSettings.IPAddress}}" sonerezh
172.17.0.4
depuis l’hôte.
Je vous passe l’installation de PHP et compagnie puisqu’à partir de là tout est comme si vous étiez sur une Debian classique… puisque vous êtes sur une image de Debian !
À ce stade, nous avons donc un conteneur avec Nginx qui fonctionne. Sauf qu’il n’est pas très élégant de proposer un service de cette manière, directement avec l’adresse IP du conteneur, qui a fortiori, est difficilement accessible depuis une machine extérieure. L’idéal serait de pouvoir faire de la redirection de port. Que l’hôte Docker qui reçoit une requête la transmette directement au conteneur adéquat.
Que nous dit la documentation à ce propos ? Argh il aurait fallu utiliser l’option -p
, ou --publish
avec la commande docker run
! D’ailleurs si vous vous souvenez bien, il y a une colonne intitulée « PORTS » en sortie de la commande docker ps
.
Han ça veut dire que je dois relancer un conteneur et me retaper toute la configuration de PHP ?! 0o
Oui… et non ! On peut très bien recommencer, mais on peut aussi transformer note conteneur en image, afin de pouvoir la réutiliser avec la commande run
. Cette action s’appelle un commit, et n’est pas sans rappeler le commit de Git. Et les similitudes ne s’arrêtent pas là, regardez plutôt comment on fait pour créer un commit d’un conteneur :
admin@docker:~$ docker commit sonerezh sonerezh-nginx
ecb48406a489ba67885f9ec3fdd98b606da701791fa2db06646a84b766bda204
Une nouvelle image a été créé à partir des modifications que nous avons apporté à l’image debian:latest.
admin@docker:~$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
sonerezh-nginx latest ecb48406a489 About a minute ago 155.4 MB
debian latest 4d6ce913b130 3 weeks ago 84.98 MB
ubuntu 14.04 b39b81afc8ca 3 weeks ago 188.3 MB
Cette image a été affectée par défaut du tag « latest », que vous pouvez modifier avec la commande tag
.
admin@docker:~$ docker tag sonerezh-nginx:latest sonerezh-nginx:0.9.0-beta
guillaume@docker:~$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
sonerezh-nginx latest ecb48406a489 5 minutes ago 155.4 MB
sonerezh-nginx 0.9.0-beta ecb48406a489 5 minutes ago 155.4 MB
debian latest 4d6ce913b130 3 weeks ago 84.98 MB
ubuntu 14.04 b39b81afc8ca 3 weeks ago 188.3 MB
Oulala ! J’ai deux images Sonerezh de 155.4 MB du coup ! Mon stockage va diminuer très vite à ce rythme là.
Mais non pas de panique. Rappelez-vous ce que je vous avais dit lors de nos premiers pas avec les images : les tags sont seulement un moyen efficace et facile d’identifier une image, or si vous regardez l’ID de l’image sonerezh-nginx, c’est le même sur les deux lignes. On parle donc bien de la même image, qui ne prend qu’une fois 155.4MB.
D’ailleurs, il faut soustraire à ces 155.4 MB les 84.98 MB de l’image debian sur laquelle elle se base. Et oui ! Elles sont cela en commun !
admin@docker:~$ docker history sonerezh-nginx
IMAGE CREATED CREATED BY SIZE
ecb48406a489 9 minutes ago /bin/bash 70.47 MB
4d6ce913b130 3 weeks ago /bin/sh -c #(nop) CMD [/bin/bash] 0 B
d0a18d3b84de 3 weeks ago /bin/sh -c #(nop) ADD file:4a58c36173b61e8a7b 84.98 MB
511136ea3c5a 20 months ago
Vous comprenez mieux pourquoi Docker est vraiment séduisant sur bien des aspects ? 🙂
Revenons maintenant à la redirection de port avant de refermer ce chapitre. Je vous le disais, nous allons utiliser l’option -p
:
admin@docker:~$ docker run -d -p 80 sonerezh-nginx /usr/sbin/nginx -g "daemon off;"
2fb71867a9eabf18731352b20deda2da517c1e283ca7c904088d2ba02f51658e
admin@docker:~$ docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2fb71867a9ea sonerezh-nginx:0.9.0-beta "/usr/sbin/nginx -g 59 seconds ago Up 58 seconds 0.0.0.0:49155->80/tcp cranky_morse
On voit que le port 49155 de l’hôte Docker (ma machine) a été redirigé vers le port 80 du conteneur cranky_morse, qui est une instance de l’image sonerezh-nginx. Vous pouvez essayer d’accéder à votre machine depuis une autre, avec son IP, sur le port adéquat.
Par exemple mon hôte Docker ayant l’IP 192.168.100.51, je tape depuis une autre machine de mon réseau local :
wget -q -O - http://192.168.100.51:49155
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor="white" text="black">
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>
Pas mal non ? 😀
Le Docker Hub
À l’instar d’un GitHub où vous allez pouvoir envoyer et partager vos codes sources, le Docker Hub est un site en ligne qui va vous permettre d’envoyer et partager vos images. Il est au cœur de la communauté Docker puisque c’est ici que vous allez retrouver tous les dépôts et images officiels. Docker interroge d’ailleurs ce site lorsque nous utilisons les commandes docker pull
ou docker search.
En plus d’être joli, le site propose tout un tas d’informations très précises sur les différentes versions d’images qui pourraient vous intéresser, par exemple pour Debian :
Dépôt officiel de Debian, sur le Docker Hub
N’hésitez pas à l’explorer 😉
Comme mes images ne sont pas tout à fait prêtes je ne vais pas les envoyer maintenant sur le Docker Hub, nous aurons tous le temps de voir cela une fois Sonerezh proprement dockerisée !
Conclusion pour aujourd’hui
Nous progressons tranquillement dans la mise en place de Sonerezh sous Docker. Nous avons une image, qui embarque Nginx, PHP et Sonerezh. Que nous manque-t-il ? Une base de données, qui fera l’objet d’un autre conteneur. Ça sera l’occasion de voir comment nous pouvons lier plusieurs conteneurs entre eux.
Ensuite Sonerezh a besoin d’accéder à des fichiers audios. Il faudrait que les conteneurs Sonerezh puissent accéder aux fichiers audios présents sur mon hôte. Ça sera là aussi l’occasion d’un prochain article où nous verrons comment gérer les volumes avec Docker.
Les liaisons sous Docker
Nous avions vu comment instancier une image Debian pour y ajouter notre application web (Nginx, PHP et le code source de l’application) pour enfin comprendre comment sauvegarder les changements apportés à l’image d’origine grâce aux commits.
Pour des raisons de simplicité je m’étais arrêté à la simple installation de Nginx pour vous montrer aussi comment faire dialoguer nos conteneurs avec le reste du monde grâce à la gestion de leurs ports d’écoute, et leur redirection entre conteneurs et l’hôte Docker.
Aujourd’hui nous allons terminer notre image Docker contenant Sonerezh et voir comment la lier à un conteneur qui offrira la base de données MySQL dont Sonerezh a besoin. Mais d’abord, revoyons comment j’ai créé l’image de Sonerezh.
Création de l’image Sonerezh
Cette section permet juste de faire une révision par rapport aux précédents articles, c’est pourquoi je ne commenterai pas ce que je fais. C’est en faisant qu’on apprend, et en rédigeant cet article je me suis rendu compte que le conteneur Nginx / PHP n’était pas le plus simple à implémenter pour faire une démonstration de ce genre. J’ai en effet raisonné au départ de manière à croire qu’un conteneur = une machine virtuelle. Or Docker n’est pas de la virtualisation dans le sens où j’y ai été habitué jusqu’à maintenant. Docker permet simplement de cloisonner des services. Un conteneur = un service.
Partant de ce principe, il m’aurait fallu un conteneur pour Nginx, un pour PHP et un pour MySQL. Ça commence à être un peu tordu pour débuter… On va donc rester sur le bon vieux Apache (qui embarque PHP par défaut) !
$ docker run -t -i debian:jessie /bin/bash
Unable to find image 'debian:jessie' locally
jessie: Pulling from debian
3cb35ae859e7: Downloading [===========================================> ] 44.56 MB/51.36 MB
41b730702607: Download complete
root@1426b082ab71:/ apt update
root@1426b082ab71:/ apt upgrade
root@1426b082ab71:/ apt install -y php5-gd php5-mysql php5 apache2 libav-tools git vim
root@1426b082ab71:/ vim /etc/php5/fpm/conf.d/00-local.ini
[date]
date.timezone = Europe/Paris
root@1426b082ab71:/ mkdir /www && cd /www
root@1426b082ab71:/ git clone --branch master https://github.com/Sonerezh/sonerezh.git
root@1426b082ab71:/ chown -R www-data: sonerezh
root@1426b082ab71:/ chmod -R 775 sonerezh
root@1426b082ab71:/ vim /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
ServerName localhost
DocumentRoot /www/sonerezh
<Directory /www/sonerezh>
Options -Indexes
AllowOverride All
Require all granted
</Directory>
CustomLog /var/log/apache2/sonerezh-access.log "Combined"
ErrorLog /var/log/apache2/sonerezh-error.log
</VirtualHost>
root@1426b082ab71:/ a2enmod php5
root@1426b082ab71:/ a2enmod rewrite
root@1426b082ab71:/ service apache2 restart
root@1426b082ab71:/ exit
J’ai tout simplement déployé Sonerezh dans mon conteneur à base de
Debian ! Premièrement : installation et configuration d’Apache, PHP et
Git (notez que le Virtual Host Apache par défaut a été effacé). Ensuite
je clone le dépôt GitHub de Sonerezh dans /www/sonerezh
.
Maintenant que mon conteneur est prêt, mais à l’arrêt puisque je l’ai quitté, profitons-en pour en faire une image, grâce à un commit :
$ docker commit --author="Admin <admin@serveur-linux>" thirsty_mccarthy sonerezh/sonerezh:v0.9.0-beta
b3282df5ff60517432e44f56ede09f77cdda53d514b5aa0a91398fe0b4b0504d
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
sonerezh/sonerezh v0.9.0-beta 8d7a0b0c64fb About a minute ago 398.7 MB
debian jessie 41b730702607 8 days ago 125.1 MB
Et voilà le travail ! Je peux maintenant transporter et déployer Sonerezh très facilement avec un simple docker run
!
Non, il manque la base de données !
Exact, et c’est là que l’article prend tout son intérêt. Les applications que nous plaçons dans des conteneurs fonctionnent rarement seules, surtout en matière de web où il est très souvent nécessaire d’avoir une base de données à portée de main. Nous l’avons vu avec Docker il est très facile d’instancier des conteneurs et on se retrouver vite avec un bordel sans nom où il devient compliquer de faire communiquer tout ce petit monde.
Heureusement, Docker propose des mécanismes pour lier plusieurs
conteneurs entre eux lorsqu’on les exécute. Comme cela se passe-t-il ?
Tout bêtement avec les fichiers /etc/hosts
des conteneurs,
que Docker va aller modifier en fonction des arguments que nous
passerons lors du lancement d’un conteneur. Exemple.
Lier plusieurs conteneurs entre eux
Récupérer une image MariaDB
Pour le coup je vais faire plus simple que précédemment en récupérant directement l’image de MariaDB sur le dépôt officiel.
$ docker run --name sonerezh-mariadb -e MYSQL_ROOT_PASSWORD=sonerezh -d mariadb:latest
Rappelez-vous l’option -d
a placé le conteneur en arrière plan mais il fonctionne bien :
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae15f2411af2 mariadb:latest "/docker-entrypoint. 52 seconds ago Up 50 seconds 3306/tcp sonerezh-mariadb
On remarque d’ailleurs qu’il écoute sur le port standard de MySQL (3306), on peut essayer de s’y connecter depuis l’hôte :
$ docker inspect --format "{{.NetworkSettings.IPAddress}}" sonerezh-mariadb
172.17.0.2
$ mysql -h172.17.0.2 -uroot -psonerezh
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 4
Server version: 10.0.17-MariaDB-1~wheezy-log mariadb.org binary distribution
Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]>
On pourrait même en profiter pour créer l’utilisateur sonerezh !
CREATE DATABASE sonerezh;
GRANT ALL PRIVILEGES ON sonerezh.* TO 'sonerezh'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
exit;
Lier les conteneurs sonerezh-app et sonerezh-mariadb
La liaison se fait avec l’option --link
(ou -l
) et prend en argument nom_du_conteneur:alias
où là encore l’alias reste à votre appréciation. Nous allons relancer
l’image préalablement créée en lui demandant d’y exécuter Apache
(et donc Sonerezh), sans oublier la liaison avec sonerezh-mariadb
. Oh et n’oublions pas de demander au conteneur Apache d’écouter le port 80 !
$ docker run --link sonerezh-mariadb:mariadb -d -p 80 --name sonerezh-app sonerezh/sonerezh:v0.9.0-beta /usr/sbin/apache2ctl -D FOREGROUND
163ceac2c1573590f1ad5bfabf85d81b92ceb666378c366addb7143ed17fee8a
Je vous laisse relire cette commande deux ou trois fois pour bien comprendre ce qu’il s’y passe.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1eaafc9d9f66 sonerezh/sonerezh:v0.9.0-beta "/usr/sbin/apache2ct 3 seconds ago Up 2 seconds 0.0.0.0:32771->80/tcp sonerezh-app
ae15f2411af2 mariadb:latest "/docker-entrypoint. 35 minutes ago Up 35 minutes 3306/tcp sonerezh-mariadb
Vérification
Quoi de mieux pour vérifier que de tenter d’installer Sonerezh ? D’après le ps
ci-dessus, mon hôte Docker redirige son port 32771 vers le port 80 de mon conteneur sonerezh-app
, celui-là même lié au conteneur sonerezh-mariadb
.
J’accède donc, via mon navigateur, à mon hôte Docker (192.168.100.240:32771
)
Comment mon conteneur sonerezh-app
peut-il contacter le conteneur sonerezh-mariadb
? Grâce à l’alias que j’ai utilisé avec l’option --link
. Pour l’installation j’ai donc comme configuration :
Ce que permet aussi --link
En fait, pour que ces alias fonctionnent, Docker est allé modifier le fichier /etc/hosts
de mes conteneurs, voyez plutôt, ici sur sonerezh-app
:
root@1eaafc9d9f66:/# cat /etc/hosts
172.17.0.3 1eaafc9d9f66
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 mariadb ae15f2411af2 sonerezh-mariadb
De plus, on trouve tout un tas de nouvelles variables d’environnements qui pourraient être très pratiques dans des scripts par exemple :
root@1eaafc9d9f66:/# env
MARIADB_ENV_MARIADB_VERSION=10.0.17+maria-1~wheezy
HOSTNAME=1eaafc9d9f66
MARIADB_PORT=tcp://172.17.0.2:3306
TERM=xterm
MARIADB_PORT_3306_TCP=tcp://172.17.0.2:3306
MARIADB_PORT_3306_TCP_PORT=3306
MARIADB_ENV_MYSQL_ROOT_PASSWORD=sonerezh
MARIADB_PORT_3306_TCP_PROTO=tcp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
SHLVL=1
HOME=/root
MARIADB_NAME=/sonerezh-app/mariadb
MARIADB_ENV_MARIADB_MAJOR=10.0
MARIADB_PORT_3306_TCP_ADDR=172.17.0.2
_=/usr/bin/env
Pas mal non ? 🙂
Nous avons encore bien avancé dans la découverte de Docker et nous pouvons maintenant commencer à créer de belles petites architectures ! Maintenant que Sonerezh est prête sous forme d’image Docker, je vais pouvoir l’exploiter et la distribuer facilement via le Hub Docker ! Nous verrons tout cela un peu plus tard.
Avant cela, il va falloir que Sonerezh accède à ma musique. Or je ne
vais pas copier ma musique dans mon conteneur cela n’aurait aucun sens.
L’idéal serait que mon conteneur sonerezh-app
puisse accéder à un dossier de mon hôte qui contiendrait ma musique.
La gestion des volumes sous Docker
la philosophie de Docker nous dit qu’un conteneur égal une application ou un service. Hors de question alors d’embarquer MariaDB, Apache et Sonerezh dans le même conteneur.
Ces containers sont volatiles et doivent en tout cas être conçus pour l’être. Comment dès lors avoir de la persistance de données ? Comment être sûr que si mon container MariaDB qui contient la base de données de mon application ne va pas faire disparaître les données qu’il contient si par mégarde je l’arrête, ou si j’ai besoin de le supprimer ?
Tout cela, Docker le gère à travers ce qui s’appelle les volumes de données.
Qu’est-ce que les volumes de données Docker ?
C’est tout simplement un dossier un peu spécial qui outrepasse la couche UnionFS de nos containers et permet le partage de données entre un ou plusieurs containers, et la persistance de celles-ci. On trouve plusieurs autres fonctionnalités intéressantes :
- Les volumes sont initialisés à la création du container. Si l’image de base contient des données au point de montage spécifié, celles-ci sont copiés dans le volume lors de son initialisation
- Les volumes de données peuvent être partagés entre plusieurs containers (très pratique pour créer des clusters de container par exemple)
- Les changements apportés aux données du volume sont pris en compte immédiatement
- Les données restent sur votre hôte même si le container vient à disparaître
Ça peut sembler un peu flou au premier abord mais vous allez voir qu’avec un exemple ça va aller beaucoup mieux.
Ajouter un volume de données
La gestion des volumes se fait avec l’option -v
(pour --volume
) des commandes docker run
ou docker create
. Évidemment, plusieurs -v
peuvent être placés les uns après les autres en fonction du nombre de
volumes souhaités. Ajoutons un volume à l’image de MariaDB par exemple :
$ docker run --name mariadb-test --volume /var/lib/mysql -d mariadb
Ici le volume /var/lib/mysql
a été créé dans mon
container. Il est par défaut accessible en lecture / écriture mais il
est possible de restreindre ces accès en utilisant /var/lib/mysql:ro
pour read-only.
Où trouver ce volume sur mon hôte ?
Toujours grâce à la commande inspect
:
$ docker inspect mariadb-test
[...]
"Mounts": [
{
"Name": "0afd0...68d73",
"Source": "/var/lib/docker/volumes/0afd0...68d73/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"RW": true
}
],
[...]
Où « Source » indique le dossier sur l’hôte et « Destination » le point de montage dans le container. « RW » indiquant si le volume est accessible en lecture / écriture.
Mais il est vrai qu’il n’est pas évident de travailler avec des volumes à si bas niveau. Qui voudrait s’embêter à fouiller dans /var/lib/docker
pour trouver le volume qu’il cherche. On peut faire plus simple : créer
un autre conteneur qui va monter ce volume automatiquement. Ce deuxième
conteneur pourrait être un service de sauvegarde de MySQL par exemple,
qui monterait le volume, réaliserait sa sauvegarde et se terminerait.
$ docker run --volumes-from mariadb-test debian:latest monscriptdesauvegarde.sh
Notez que les volumes sont toujours conservés, que les containers soient arrêtés ou non. En revanche le volume (et les données) est supprimé une fois que le dernier container y faisant référence l’est aussi.
Pas mal non ?
Mouais pas mal. Mais dans le cas de Sonerezh, ma musique est dans /home/guillaume/Musique
. Si je démarre un container Sonerezh avec un volume, il faudra que je copie toute ma musique dedans ?!
Non, c’est là que ça devient intéressant. Plutôt que de laisser
Docker choisir l’emplacement du volume sur l’hôte, on peut spécifier un
dossier en particulier. Dans le cas de Sonerezh, on va être capable
d’indiquer à Docker de monter le dossier /home/guillaume/Musique
de notre hôte sur le point de montage /music
de notre container. L’application Sonerezh accédera donc à la musique via /music
.
Et d’ailleurs ça se passe un peu comme les liaisons du chapitre précédent :
$ mkdir /home/guillaume/mariadb-volume
$ docker run --name mariadb-test --volume /home/guillaume/mariadb-volume:/var/lib/mysql -d -e MYSQL_ROOT_PASSWORD=pass mariadb
$ docker inspect mariadb-test
[...]
"Mounts": [
{
"Source": "/home/guillaume/mariadb-volume",
"Destination": "/var/lib/mysql",
"Mode": "",
"RW": true
}
],
[...]
Ici le dossier de ma /home
est monté dans le container à /var/lib/mysql
. D’ailleurs comme c’est ici que MySQL stocke toute ses données par défaut je devrais y trouver du contenu.
$ ls -l /home/guillaume/mariadb-volume
-rw-rw---- 1 999 docker 16K oct. 26 22:39 aria_log.00000001
-rw-rw---- 1 999 docker 52 oct. 26 22:39 aria_log_control
-rw-rw---- 1 999 docker 12M oct. 26 22:39 ibdata1
-rw-rw---- 1 999 docker 48M oct. 26 22:39 ib_logfile0
-rw-rw---- 1 999 docker 48M oct. 26 22:39 ib_logfile1
-rw-rw---- 1 999 docker 0 oct. 26 22:39 multi-master.info
drwx------ 2 999 root 4,0K oct. 26 22:39 mysql
drwx------ 2 999 docker 4,0K oct. 26 22:39 performance_schema
Tadaa !!!
C’est plus clair maintenant ? Je peux faire ce que je veux avec mes
containers mariadb. Les arrêter, les supprimer, les mettre à jour… Tant
que je continuerai de mapper /home/guillaume/mariadb-volume
à /var/lib/mysql
, mes données persisteront dans mes containers.
D’ailleurs à ce propos, si vous regardez la documentation de l’image Docker officielle de Sonerezh, vous voyez qu’il est conseillé de monter le volume /music
, et de le faire correspondre à l’endroit où votre musique est stockée !
$ docker run --name sonerezh-app --link sonerezh-mariadb:mariadb \
--volume /path/to/music:/music \
--detach --publish 8080:80 \
sonerezh/sonerezh:latest
Les containers de volumes de données
Il peut aussi y avoir d’autres cas où nous avons besoin de partager un volume entre un ou plusieurs containers. Il est conseillé dans ce cas d’utiliser un container de volume de données (« data volume container« ).
Ce container de volume de données se créé avec la commande docker create
. Restons avec MariaDB et créons un nouveau container de type volume de données.
$ docker create --volume /var/lib/mysql --name mariadb-data mariadb /bin/true
Bien que ce container n’exécute aucune application (le docker ps
ne renvoie rien), on réutilise quand même l’image de mariadb car
n’oubliez pas que les couches de filesystem sont partagées entre tous
les containers utilisant la même image pour gagner de la place.
On peut maintenant réutiliser l’option --volumes-from
pour monter /var/lib/mysql
dans un autre container :
$ docker run --name mariadb-test --volumes-from mariadb-data -d -e MYSQL_ROOT_PASSWORD=pass mariadb
Et un deuxième :
$ docker run --name mariadb-test2 --volumes-from mariadb-data -d -e MYSQL_ROOT_PASSWORD=pass mariadb
Les containers mariadb-test et mariadb-test2 accèdent tous les deux aux mêmes données puisqu’ils montent le même volume créé dans mariadb-data.
Sauvegarde, restauration et migration de volumes
L’avantage d’utiliser les containers de volume, c’est que nous pouvons facilement sauvegarde, exporter et restaurer nos données !
$ docker run --volumes-from mariadb-data -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /var/lib/mysql
Hein ?! Il se passe quoi là ?!
- On crée un container qui va monter le volume présenté par
mariadb-data
que nous avions créé un peu plus tôt - L’option
-v
nous permet de lier le dossier/backup
du container au dossier courant de notre hôte ($(pwd)
) - Ce container utilise l’image ubuntu
- Et y exécute la commande
tar cf /backup/backup.tar /var/lib/mysql
Comme /backup
= $(pwd), l’archive se retrouve directement sur notre hôte !
$ ls -l *.tar
-rw-r--r-- 1 root root 119644160 nov. 1 12:45 backup.tar
Pour restaurer sur un autre hôte par exemple, il faut faire la procédure inverse :
# 1. On recrée le container de volume
$ docker create --volume /var/lib/mysql --name mariadb-data2 mariadb /bin/true
# 2. On y décompresse l'archive
$ docker run --volumes-from mariadb-data2 -v $(pwd):/backup ubuntu tar xvf /backup/backup.tar
Et le tour est joué !
Où héberger ses applications sous Docker ?
Depuis la progression rapide de Docker, de multiples hébergeurs proposent de stocker vos conteneurs, en voici une liste :
Conclusion
Maintenant que vous savez tout des images, des containers, des liens et des volumes, vous pouvez commencer sérieusement à vous amuser sur Docker.
Merci à Guillaume pour la rédaction !