Work on tuto 2

This commit is contained in:
nemunaire 2022-09-20 06:02:53 +02:00
commit bc179806db
19 changed files with 545 additions and 145 deletions

View file

@ -8,7 +8,7 @@ que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant
<div lang="en-US">
```dockerfile
FROM ubuntu:latest
FROM ubuntu:jammy
RUN apt-get update
RUN apt-get install -y nano
@ -21,7 +21,16 @@ est suivie de ses arguments.
Dans notre exemple, nous utilisons `FROM`{.dockerfile} qui indique une image de
départ à utiliser ; `RUN`{.dockerfile} est une commande qui sera exécutée dans
le conteneur, dans le but de le construire.
le conteneur intermédiaire, dans le but de construire l'image. De la même
manière que les `docker container run` de la partie précédente.
::::: {.warning}
Vous avez remarqué que la première instruction que l'on utilise est
`FROM`. Chaque image construite par un `Dockerfile` doit dépendre d'une autre
image. Ici nous avons choisi de partir de l'image `ubuntu`.
:::::
Pour lancer la construction de la nouvelle image, créons un nouveau dossier ne
contenant que notre fichier `Dockerfile`, plaçons-nous ensuite dedans, puis
@ -33,6 +42,23 @@ docker image build --tag=my_editor .
```
</div>
On utilise l'option `--tag` pour donner un nom et un tag à l'image qui
résultera de l'exécution de cette construction.
::::: {.warning}
#### Attention de ne pas oublier le point à la fin de la commande ! {-}
Vous n'êtes plus sans savoir que Docker se compose d'un client et d'un
serveur. Et c'est la partie serveur qui va s'occuper de construire l'image.
Le client transmet donc tout le contexte autour du Dockerfile (les fichiers,
dossiers, sons-dossiers) à partir du chemin qu'on lui indique en dernier
argument. Le point représente donc ici simplement le dossier courant. Tous les
fichiers et dossiers présents ici seront transmis au daemon.
:::::
Une fois la construction de l'image terminée, nous pouvons la lancer et
constater l'existence de notre éditeur favori :
@ -85,13 +111,17 @@ RUN service mysqld start && mysql -u root -p toor virli < /db.sql
```
</div>
Après le `RUN`{.dockerfile}, MySQL sera de nouveau tué.\
Après le `RUN`{.dockerfile}, MySQL sera de nouveau tué, mais la seconde
commande aura entre-temps pu ajouter des données.\
En aucun cas, une commande exécutée par un `RUN`{.dockerfile} se retrouvera en
::::: {.warning}
**En aucun cas, une commande exécutée par un `RUN`{.dockerfile} se retrouvera en
cours d'exécution lorsque l'on invoquera un conteneur par `docker container
run`. Seul la commande fournie par l'utilisateur ou la commande par défaut de
l'image sera exécutée au lancement d'un conteneur.
l'image sera exécutée au lancement d'un conteneur.**
:::::
### Exposer des ports
@ -131,6 +161,51 @@ Dans un autre terminal, lançons un `docker container ls`, pour consulter la col
Rendez-vous ensuite dans votre navigateur sur <http://localhost:49153/>.
### Copier des fichiers dans l'image
Une autre action très courante est de vouloir recopier un fichier ou un binaire
dans notre image : un fichier de configuration, un produit de compilation, des
scripts pour contrôler l'exécution, ...
On va utiliser pour cela l'instruction `COPY` :
<div lang="en-US">
```
COPY myconfig.conf /etc/nginx/conf.d/my.conf
```
</div>
Cette instruction permet également de copier l'arborescence d'un dossier :
<div lang="en-US">
```
COPY myconfs/ etc/nginx/conf.d/
COPY mywebsite /usr/share/nginx/html/
```
</div>
::::: {.warning}
Le comportement de la copie de dossier est différente du comportement que l'on
a l'habitude d'avoir avec `cp -r`. Si la source du `COPY` est un dossier, c'est
son contenu qui sera recopié récursivement, habituellement avec `cp` le dossier
recopié puis son contenu.
Pour obtenir le même comportement, il faut bien indiquer une cible
incluant le nom du dossier :
<div lang="en-US">
```
COPY docker-entrypoint.d /docker-entrypoint.d
```
</div>
Le dossier sera créé s'il n'existe pas, et le contenu du dossier source ser
recopié.
:::::
:::::: {.exercice}
#### À vous de jouer {-}
@ -166,11 +241,76 @@ build`.
Les couches du cache peuvent être partagées entre plusieurs conteneurs, c'est
ainsi que vous pouvez partager facilement une plus grosse partie du système de
fichiers (rappelez-vous le principe d'union FS).
fichiers.\
Pour profiter du cache, on va placer de préférence les étapes les plus
génériques (qui seraient les plus susceptibles d'apparaître dans d'autres
images), en haut du `Dockerfile`.
Pour profiter au mieux du cache, on place les instructions qui sont le moins
susceptibles de changer en haut du `Dockerfile`, celles qui changent le plus
régulièrement à la fin. Ainsi, lorsqu'une reconstruction de l'image sera
nécessaire, on gagnera du temps puisque le cache sera utilisé jusqu'à la
première instruction changeante. Un `Dockerfile` bien ordonné peu facilement
faire gagner de nombreuses minutes à ses utilisateurs.
::::: {.question}
#### Quelle place cela prend-t-il sur mon disque ? {-}
Nous pouvons afficher la taille de chaque image via la commande `docker image
ls` :
<div lang="en-US">
```
42sh$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 2d389e545974 6 days ago 142MB
debian stable 9b4953ae981c 7 days ago 124MB
nemunaire/youp0m latest 2c06880e48aa 12 days ago 25MB
```
</div>
Si vous avez beaucoup d'images, cela peut paraître beaucoup, mais rappelez-vous
que les images sont composées de couches qui sont souvent partagées entre
plusieurs conteneurs.
Si on regarde l'espace vraiment utilisé, il est moindre :
<div lang="en-US">
```
42sh$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 3 3 167MB 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B
```
</div>
:::::
Les couches partagées sont un gain non négligeable pour l'espace de stockage !
Par exemple, prenons le `Dockerfile` suivait :
<div lang="en-US">
```Dockerfile
FROM python:3.10
COPY build /usr/lib/python/grapher
EXPOSE 8080
RUN pip install pillow pygal
```
</div>
Il y a de fortes chances pour que vous travailliez sur le code de
l'application, le dossier `build` sera donc très souvent mis à jour, alors que
les dépendances ne bougeront sans doute plus ...
Avec un tel `Dockerfile`, dès que le dossier `build` sera mis à jour les
dépendances seront à nouveau téléchargées, puisque toutes les couches suivant
la première qui change sont invalidées.
Une approche plus optimale serait donc de faire la `COPY` en dernier, car c'est
l'opération qui changera le plus souvent. L'idéal étant que 90 % des
reconstructions ne refassent que la dernière instruction, toutes les autres
devraient être récupérées du cache.
### Métadonnées pures
@ -181,7 +321,7 @@ sous forme de clef/valeur.
Une métadonnée courante[^MAINTAINER] est d'indiquer le nom du
mainteneur de l'image :
[^MAINTAINER]: Voir par exemple : <https://github.com/nginxinc/docker-nginx/blob/master/stable/debian/Dockerfile#L8>
[^MAINTAINER]: Voir par exemple : <https://github.com/nginxinc/docker-nginx/blob/master/stable/debian/Dockerfile#L8>
<div lang="en-US">
```dockerfile
@ -222,6 +362,9 @@ constatez via un `docker container ls` que le conteneur s'arrête directement,
retirez cette option pour voir ce qui ne va pas, ou utilisez la commande
`docker container logs`.
Comme les `LABEL`, ce n'est pas une instruction qui change régulièrement. On la
place plutôt au début du `Dockerfile`.
### Construire son application au moment de la construction du conteneur ?
@ -257,8 +400,8 @@ CMD ["/hello"]
```
</div>
Dans cet exemple, deux conteneurs distincts sont créés : le premier à partir de
l'image `gcc`, il contient tout le nécessaire pour compiler notre
Dans cet exemple, deux images distinctes sont créées : la première à partir de
l'image `gcc`, elle contient tout le nécessaire pour compiler notre
`hello.c`. Mais l'image finale (le dernier `FROM`{.dockerfile} de notre
`Dockerfile`) est l'image vide, dans laquelle nous recopions simplement le
produit de notre compilation.
@ -302,10 +445,23 @@ sélectionnera ainsi avec l'option `--target` l'un ou l'autre en fonction de
l'environnement dans lequel on souhaite se déployer.
### Déclarer des volumes
Tout comme nous pouvons déclarer préalablement dans le `Dockerfile` les ports qui
sont normalement exposés par le conteneur, nous pouvons déclarer les
volumes. L'instruction pour cela est `VOLUME`.
Il convient de l'utiliser pour déclarer les emplacements qui vont par défaut
contenir des données à faire persister. Ce serait le cas de `/var/lib/mysql`
pour les conteneurs MariaDB ou MySQL, `/images/` pour notre image `youp0m` ...
### D'autres instructions ?
Consultez <https://docs.docker.com/engine/reference/builder/> pour la liste
complète des instructions reconnues.
Nous avons fait le tour des principales instructions et de leurs différents
usages *classiques*. Il existe quelques autres instructions que nous n'avons
pas présentées ici, pour aller plus loin, consultez la référence sur :\
<https://docs.docker.com/engine/reference/builder/>
::::: {.exercice}
@ -324,11 +480,16 @@ Pour compiler le projet, vous pouvez utiliser dans votre `Dockerfile`
<div lang="en-US">
```dockerfile
FROM golang:1.16
FROM golang:1.18
COPY . /go/src/git.nemunai.re/youp0m
WORKDIR /go/src/git.nemunai.re/youp0m
RUN go build -tags dev -v
```
</div>
Remarquez la puissance de Docker : vous n'avez sans doute pas de compilateur Go
installé sur votre machine, et pourtant, en quelques minutes et à partir du
seul code source de l'application et d'un `Dockerfile`, vous avez pu compiler sur
votre poste le binaire attendu. WOW, non ?
:::::