458 lines
15 KiB
Markdown
458 lines
15 KiB
Markdown
Mon premier conteneur
|
||
---------------------
|
||
|
||
Afin de tester la bonne marche de notre installation, lançons notre premier
|
||
conteneur avec la commande :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run hello-world
|
||
```
|
||
</div>
|
||
|
||
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 commencer par demander au registre les différents
|
||
composants de l'image. On aperçoit la récupération de l'image dans les
|
||
premières lignes :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh$ docker container run hello-world
|
||
Unable to find image 'hello-world:latest' locally
|
||
latest: Pulling from library/hello-world
|
||
2db29710123e: Already exists
|
||
Digest: sha256:7d246653d0511db2a6b2e0436cfd0e52ac8c066000264b3ce63331ac66dca625
|
||
Status: Downloaded newer image for hello-world:latest
|
||
```
|
||
</div>
|
||
|
||
Le daemon assemble ensuite l'image et met en place les différents éléments
|
||
d'isolation pour préparer l'exécution de notre conteneur.
|
||
|
||
Enfin, il lance la commande qui sera le premier processus du conteneur. Cette
|
||
commande fait parti des métadonnées de l'image. Le processus ainsi lancé est un
|
||
peu particulier : il obtient les mêmes caractéristiques que le PID 1 de notre
|
||
système ('init').
|
||
|
||
|
||
### Recherche d'images
|
||
|
||
Docker est donc allé récupérer une image existante. Par défaut, il va
|
||
rechercher sur le Docker Hub, un registre d'images géré par les équipes de
|
||
Docker.
|
||
|
||
Nous pouvons explorer les images existantes en utilisant la commande suivante :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker search mariadb
|
||
```
|
||
</div>
|
||
|
||
Cette commande atteint rapidement ses limites en raison de la difficulté à
|
||
afficher beaucoup de contenu pour faire son choix. Aussi, il est souvent plus
|
||
pratique d'aller explorer les registres en passant directement par leur interface
|
||
web.
|
||
|
||
Vous trouverez forcément votre bonheur parmi les images proposées par les deux
|
||
principaux registres :
|
||
|
||
- <https://hub.docker.com/>
|
||
- <https://quay.io/>
|
||
|
||
::::: {.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.**
|
||
|
||
:::::
|
||
|
||
|
||
### Exécuter des images d'autres registres
|
||
|
||
Il existe une multitude de registres d'images. Le Docker Hub et Quay.io sont
|
||
sans aucun doute les registres publics les plus utilisés. Mais il y en a
|
||
d'autres : GitHub, Gitlab et Gitea proposent un service de registre afin de
|
||
centraliser la récupération du code et des produits de compilation (comme les
|
||
binaires ou les images de conteneur).
|
||
|
||
On a dit que Docker utilisait le Docker Hub comme registre par défaut. Cela
|
||
signifie que sans cette facilité, nous aurions dû écrire :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run docker.io/library/hello-world
|
||
```
|
||
</div>
|
||
|
||
Vous pouvez l'essayer, c'est une commande valide. En fait, pour utiliser une
|
||
image d'un registre, on va simplement utiliser le nom de domaine du registre en
|
||
question.
|
||
|
||
Il y a encore bien d'autres registres existant. En fait, les spécifications
|
||
sont ouvertes, chacun peut implémenter un registre dans ses logiciels, ou même
|
||
déployer un conteneur de registre (tels que `registry` ou `harbor`).
|
||
|
||
|
||
### Registres et images privés
|
||
|
||
Il y a certaines images que l'on désire garder privées : il peut s'agir d'un
|
||
travail que l'on ne souhaite pas partager ou, dans le cas d'une entreprise,
|
||
celle-ci peut préférer que son code source ou ses binaires ne soient pas
|
||
distribués.
|
||
|
||
Certains registres intègrent des fonctionnalités d'authentification, afin de ne
|
||
permettre la découverte et l'accès qu'aux seuls utilisateurs autorisés.
|
||
|
||
Nous n'en aurons pas besoin durant notre présente découverte, mais notons que l'on
|
||
utiliserait la commande suivante :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker login registry.container.io
|
||
```
|
||
</div>
|
||
|
||
|
||
Il convient de remplacer le dernier paramètre par le nom de domaine du registre
|
||
(ou rien si l'on veut s'authentifier sur le registre par défaut).
|
||
|
||
On ne s'authentifie pas pour une image en particulier, mais pour un domaine.
|
||
|
||
::::: {.warning}
|
||
|
||
Les informations d'authentification, une fois la commande validée, sont
|
||
stockées dans un fichier sur le disque de la machine.
|
||
|
||
:::::
|
||
|
||
::::: {.question}
|
||
|
||
Notez que le Docker Hub a mis en place des limites sur le nombre de requêtes qui
|
||
lui sont faites. Avoir un compte sur le Docker Hub vous permet d'avoir des
|
||
limites plus élevées.
|
||
|
||
:::::
|
||
|
||
|
||
### Images vs. conteneurs
|
||
|
||
Exécutons sans plus attendre une nouvelle image, cette fois-ci issue d'un
|
||
registre tiers :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container un registry.nemunai.ie/hello-world
|
||
```
|
||
</div>
|
||
|
||
De la même manière que pour le `hello-world` du Docker Hub, il est d'abord
|
||
nécessaire de récupérer l'image. Elle est ensuite exécutée. Vous devriez donc
|
||
obtenir un résultat en deux parties, similaire à la sortie suivante :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
Unable to find image 'registry.nemunai.re/hello-world:latest' locally
|
||
latest: Pulling from library/hello-world
|
||
2db29710123e: Already exists
|
||
Digest: sha256:7d246653d0511db2a6b2e0436cfd0e52ac8c066000264b3ce63331ac66dca625
|
||
Status: Downloaded newer image for registry.nemunai.re/hello-world:latest
|
||
|
||
Hello World!
|
||
============
|
||
|
||
This is a container running the image hello-world.
|
||
My name is: 32df94d25823
|
||
|
||
Feel free to run this image again to see if the name changes or not,
|
||
denoting a new container or not.
|
||
```
|
||
</div>
|
||
|
||
Le conteneur affiche son nom, celui-ci sera nécessairement différent pour vous
|
||
et il sera différent pour chaque nouveau conteneur exécuté.
|
||
|
||
Chaque conteneur `hello-world` est créé à partir de la même image, mais on
|
||
arrive toujours dans un nouvel environnement. Docker assigne un nom d'hôte
|
||
aléatoire à chaque nouveau conteneur créé. L'idée est de visualiser ici que
|
||
chaque exécution donne naissance à un nouveau conteneur.
|
||
|
||
|
||
### 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.[^oldcall]
|
||
|
||
[^oldcall]: 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 de
|
||
`docker image ls`, `docker run` au lieu de `docker container run`, ...
|
||
|
||
L'ancienne syntaxe est dépréciée, mais il reste actuellement possible de
|
||
l'utiliser.
|
||
|
||
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` :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker image ls
|
||
```
|
||
</div>
|
||
|
||
Nous utiliserons plus tard les autres objets dont Docker dispose.
|
||
|
||
|
||
### *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
|
||
documentation[^hubDocUbuntu] qui accompagne chaque conteneur, nous pouvons
|
||
constater la présence de plusieurs versions d'Ubuntu : `trusty`, `xenial`,
|
||
`focal` ou `bionic`.
|
||
|
||
[^hubDocUbuntu]: Pour voir la documentation des images d'Ubuntu, consultez
|
||
<https://hub.docker.com/_/ubuntu>
|
||
|
||
Par convention, lorsque l'on souhaite désigner un tag en particulier,
|
||
on utilise la syntaxe suivante :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
ubuntu:focal
|
||
```
|
||
</div>
|
||
|
||
Par exemple, pour lancer un conteneur Ubuntu Focal, on utilisera :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
docker container run ubuntu:focal
|
||
```
|
||
</div>
|
||
|
||
|
||
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 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run ubuntu /bin/echo "Hello World"
|
||
```
|
||
</div>
|
||
|
||
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](https://www.alpinelinux.org) comme distribution
|
||
hôte, vous pourriez tenter d'utiliser son gestionnaire de paquet `apk`, via :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run alpine /sbin/apk stats
|
||
```
|
||
</div>
|
||
|
||
Vous n'avez a priori pas le binaire `/sbin/apk` sur votre système. C'est bien
|
||
celui du conteneur qui a été exécuté.
|
||
|
||
|
||
### 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` :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run hello-world
|
||
```
|
||
</div>
|
||
|
||
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` :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker container run hello-world /hello
|
||
```
|
||
</div>
|
||
|
||
L'image ne contenant que ce programme, vous ne pourrez pas y lancer de shell :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh$ docker container run hello-world /bin/sh
|
||
docker: Error response from daemon: OCI runtime create failed:
|
||
container_linux.go:349: starting container process caused
|
||
"exec: \"/bin/sh\": stat /bin/sh: no such file or directory"
|
||
```
|
||
</div>
|
||
|
||
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 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ docker container run alpine
|
||
42sh$ echo $?
|
||
0
|
||
```
|
||
</div>
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ docker container run --interactive --tty alpine
|
||
/ # _
|
||
```
|
||
</div>
|
||
|
||
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
|
||
interractif[^bashnointer].
|
||
|
||
[^bashnointer]: Mais il sera possible de l'utiliser sans allouer de TTY, comme
|
||
dans cet exemple :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh$ cat cmd
|
||
echo foo
|
||
42sh$ cat cmd | docker run -i busybox
|
||
foo
|
||
```
|
||
</div>
|
||
|
||
L'option `-i` reste néanmoins nécessaire pour que l'entrée standard soit
|
||
transmise au conteneur.
|
||
|
||
Rassurez-vous, on peut les abréger en `-i` et `-t` :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ docker container run -it alpine
|
||
/ # _
|
||
```
|
||
</div>
|
||
|
||
|
||
### 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 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker DOCKER_PARAMS container run RUN_OPTS image IMAGE_CMD IMAGE_ARGS …
|
||
```
|
||
</div>
|
||
|
||
Par exemple :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
docker -H unix:///run/docker.sock container run -it alpine /bin/ash -c \
|
||
"echo foo"
|
||
```
|
||
</div>
|
||
|
||
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 :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh$ docker container ls
|
||
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
|
||
4c39fc049cd1 ubuntu "/bin/bash" 6 minutes ago Up 5 minutes bold_gates
|
||
```
|
||
</div>
|
||
|
||
::::: {.question}
|
||
|
||
#### Avez-vous remarqué que le `CONTAINER ID` correspond au nom d'hôte du conteneur ? {-}
|
||
|
||
À la création du conteneur, Docker génère un identifiant unique qu'il attribue
|
||
au conteneur. Au passage, les premiers chiffres sont utilisés pour définir le
|
||
nom d'hôte du conteneur.
|
||
|
||
:::::
|
||
|
||
Chaque ligne représente un conteneur en cours d'exécution. Pour toutes les
|
||
commandes qui permettent d'interagir avec un conteneur, il est possible
|
||
d'utiliser indifféremment l'identifiant du conteneur (long ou court) ou bien le
|
||
nom qu'il lui a été attribué (soit automatiquement par Docker, dans ce cas le
|
||
nom ressemblera à un adjectif suivi du nom d'une personnalité de l'informatique
|
||
connue, soit par vous, via l'option `--name` du `run`).
|
||
|
||
### Sortir d'un conteneur
|
||
|
||
Pour mettre fin à l'exécution d'un conteneur, il convient de terminer le
|
||
premier processus lancé dans celui-ci (le processus au PID 1).
|
||
|
||
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.
|