10 KiB
\newpage
Mon premier conteneur
Afin de tester la bonne marche de notre installation, lançons notre premier conteneur avec la commande :
Cette commande va automatiquement exécuter une série d'actions pour nous, comme indiqué dans le message affiché en retour :
D'abord, le daemon va rechercher s'il possède localement l'image hello-world. Si ce n'est pas le cas, il va aller récupérer les différentes couches de l'image sur le registre par défaut (celui de Docker). Il assemble ensuite les images en ajoutant une couche en lecture/écriture, propre au conteneur. Enfin, il lance la commande par défaut, telle que définie dans les métadonnées de l'image.
Nous pouvons directement utiliser le client pour rechercher une image sur le
registre, en utilisant la commande search
:
Il est possible de mettre à jour les images locales, ou télécharger les couches
d'images qui nous intéressent, en utilisant la commande pull
:
::::: {.warning}
Les registres publics tels quel le Docker Hub mettent à disposition des images officielles, mais aussi des images créées par la communauté. Chaque utilisateur est libre d'envoyer une image qu'il a lui-même créée : soit car l'éditeur ne proposait pas d'image et que quelqu'un s'est dévoué pour la faire, soit parce qu'il avait des besoins plus spécifiques qui n'étaient pas couverts par l'image originale. Il est important de garder en tête que vous téléchargez des exécutables, et bien qu'ils s'exécutent dans un environnement isolé, ils peuvent contenir du code malveillant.
De la même manière que vous devez être attentif aux binaires que vous exécutez sur votre machine et au contexte de leurs téléchargements, ici assurez-vous d'avoir confiance dans la personne affiliée à l'image.
:::::
Arguments de la ligne de commande
Remarquez comment on interagit avec chaque objet Docker : dans la ligne de
commande, le premier mot clef est le type d'objet (container
, image
,
service
, network
, volume
, ...) ; ensuite, vient l'action que l'on
souhaite faire dans ce cadre.1
Par exemple, pour consulter la liste des images dont nous disposons localement
(soit parce qu'on les a téléchargées, soit parce que nous les avons créées
nous-même), on utilise la commande ls
sous le type d'objets image
:
Image ID, nom, tag
Chaque image est identifiable par son Image ID : il s'agit d'un long identifiant unique. Chaque modification qui est apportée à l'image générera un Image ID différent. Un peu comme un identifiant de commit dans Git.
Pour s'y retrouver, on utilise habituellement les noms des images :
hello-world
est ainsi le nom de l'image
1b26826f602946860c279fce658f31050cff2c596583af237d971f4629b57792
.
Lorsque, comme dans le cas d'Ubuntu, il y a plusieurs versions disponibles,
il est possible de préciser la version au moyen d'un tag. En consultant la
documentation2 qui accompagne chaque conteneur, nous pouvons
constater la présence de plusieurs versions d'Ubuntu : trusty
, xenial
,
focal
ou bionic
.
Par convention, lorsque l'on souhaite désigner un tag en particulier, on utilise la syntaxe suivante :
Par exemple, pour lancer un conteneur Ubuntu Focal, on utilisera :
Chaque nom d'image possède au moins un tag associé par défaut : latest. C'est le tag qui est automatiquement recherché lorsque l'on ne le précise pas en lançant l'image.
Exécuter un programme dans un conteneur
Maintenant que nous avons à notre disposition l'image d'un conteneur Ubuntu, nous allons pouvoir jouer avec !
La commande run
de Docker prend comme derniers arguments le programme à
lancer dans le conteneur ainsi que ses éventuels arguments. Essayons d'afficher
un Hello World :
Dans notre exemple, c'est bien le /bin/echo
présent dans le conteneur qui est
appelé. Ce n'est pas le programme /bin/echo
de la machine hôte qui a été
transféré dans le conteneur.
Pour nous en convaincre, nous pouvons tenter d'exécuter un programme qui n'est
pas présent sur notre machine, mais bien uniquement dans le conteneur. Si vous
n'utilisez pas Alpine Linux, vous pourriez
tenter d'utiliser son gestionnaire de paquet apk
, via :
Images vs. conteneurs
À chaque fois que nous lançons un run
, un nouveau conteneur est créé.
L'image fournie comme argument est utilisée comme un modèle de base pour le
conteneur et est recopiée grâce à un mécanisme de Copy-On-Write: c'est donc
très rapide et ne consomme pas beaucoup d'espace disque.
Étant donné que chaque conteneur est créé à partir d'un modèle, cela signifie que lorsque nous exécutons une commande modifiant les fichiers d'un conteneur, cela ne modifie pas l'image de base. La modification reste contenue dans la couche propre au conteneur dans l'UnionFS.
Dans le schéma ci-après, on considère les images comme étant la partie figée de Docker à partir desquelles on peut créer des conteneurs.
Si l'on souhaite qu'une modification faite dans un conteneur (par exemple l'installation d'un paquet) s'applique à d'autres conteneurs, il va falloir créer une nouvelle image à partir de ce conteneur.
Programme par défaut
Chaque image vient avec un certain nombre de métadonnées, notamment le programme à exécuter par défaut si l'on ne le précise pas dans la ligne de commande.
C'est grâce à cela que vous n'avez pas eu besoin de préciser de programme
lorsque vous avez lancé l'image hello-world
:
Il est commun que le programme le plus attendu/approprié soit lancé par défaut,
il est donc tout naturel que pour l'image hello-world
, ce soit /hello
:
L'image ne contenant que ce programme, vous ne pourrez pas y lancer de shell :
Pour les images alpine
et ubuntu
, le programme par défaut est un shell
(/bin/ash
pour alpine
et /bin/bash
pour ubuntu
), mais il y a une
subtilité : il faut ajouter les options --interactive
et --tty
pour ne pas
que docker
nous rende la main tout de suite :
En fait, comme on a vu que le programme docker
n'est qu'un client du daemon,
c'est toujours le daemon qui exécute directement les commandes et gère les
entrées et sorties standards et d'erreur. Avec l'option --interactive
, on
s'assure que l'entrée standard ne sera pas fermée (close(2)
). Nous demandons
également l'allocation d'un TTY, sans quoi bash
ne se lancera pas en mode
interractif3.
Rassurez-vous, on peut les abréger en -i
et -t
:
Les paramètres
Vous avez remarqué le placement des options --tty
et --interactive
? Avant
le nom de l'image, elles sont utilisées par Docker pour modifier le comportement
du run
. En fait, tout comme git(1)
et ses sous-commandes, chaque niveau de
commande peut prendre des paramètres :
Par exemple :
Ici, l'option -H
sera traitée par le client Docker (pour définir
l'emplacement du point de communication avec le daemon), tandis que les options
-it
seront utilisées lors du traitement du run
. Quant au -c
, il sera
simplement transmis au conteneur comme argument au premier execve(2)
du
conteneur.
Lister les conteneurs
Avant de quitter notre conteneur, regardons, à l'aide d'un autre terminal, l'état de notre conteneur. La commande suivante permet d'obtenir la liste des conteneurs en cours d'exécution :
Sortir d'un conteneur
Pour mettre fin à l'exécution d'un conteneur, il convient de terminer le premier processus lancé dans celui-ci.
Si vous faites face à une invite de commande, le classique exit
ou ^D
mettra fin au shell, qui était le premier processus lancé de notre conteneur,
comme le montre la colonne COMMAND
. Nous retrouvons juste après notre invite
habituelle, dans l'état où nous l'avions laissée avant de lancer notre
conteneur. En effet, le conteneur était alors le processus fils lancé par notre
shell.
-
cela n'a pas toujours été aussi simple, cette syntaxe n'existe que depuis la version 1.13 (janvier 2017). C'est pourquoi, lorsque vous ferez des recherches sur internet, vous trouverez de nombreux articles utilisant l'ancienne syntaxe, sans le type d'objets :
docker images
au lieu dedocker image ls
,docker run
au lieu dedocker container run
, ...L'ancienne syntaxe est dépréciée, mais il reste actuellement possible de l'utiliser. ↩︎
-
Pour voir la documentation des images d'Ubuntu, consultez https://hub.docker.com/_/ubuntu ↩︎
-
Mais il sera possible de l'utiliser sans allouer de TTY, comme dans cet exemple :
``` 42sh$ cat cmd echo foo 42sh$ cat cmd | docker run -i busybox foo ```L'option
-i
reste néanmoins nécessaire pour que l'entrée standard soit transmise au conteneur. ↩︎