virli/tutorial/docker-basis/first.md

15 KiB
Raw Blame History

Mon premier conteneur

Afin de tester la bonne marche de notre installation, lançons notre premier conteneur avec la commande:

```bash docker container run hello-world ```

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:

``` 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 ```

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 :

```bash docker search mariadb ```

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 :

::::: {.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 :

```bash docker container run docker.io/library/hello-world ```

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 :

```bash docker login registry.container.io ```

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 :

```bash docker container un registry.nemunai.ie/hello-world ```

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 :

```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

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 documentation1 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:

``` ubuntu:focal ```

Par exemple, pour lancer un conteneur Ubuntu Focal, on utilisera:

``` docker container run ubuntu:focal ```

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:

```bash docker container run ubuntu /bin/echo "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 comme distribution hôte, vous pourriez tenter d'utiliser son gestionnaire de paquet apk, via:

```bash docker container run alpine /sbin/apk stats ```

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:

```bash docker container run 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:

```bash docker container run hello-world /hello ```

L'image ne contenant que ce programme, vous ne pourrez pas y lancer de shell:

``` 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" ```

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:

```bash 42sh$ docker container run alpine 42sh$ echo $? 0 ```
```bash 42sh$ docker container run --interactive --tty alpine / # _ ```

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 interractif2.

Rassurez-vous, on peut les abréger en -i et -t:

```bash 42sh$ docker container run -it alpine / # _ ```

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:

```bash docker DOCKER_PARAMS container run RUN_OPTS image IMAGE_CMD IMAGE_ARGS … ```

Par exemple:

```bash docker -H unix:///run/docker.sock container run -it alpine /bin/ash -c \ "echo foo" ```

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:

``` 42sh$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 4c39fc049cd1 ubuntu "/bin/bash" 6 minutes ago Up 5 minutes bold_gates ```

::::: {.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.


  1. Pour voir la documentation des images d'Ubuntu, consultez https://hub.docker.com/_/ubuntu ↩︎

  2. 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. ↩︎