virli/tutorial/docker-basis/first.md

458 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.