Dockerfile part done
This commit is contained in:
parent
02db9cc19c
commit
55817a2073
12 changed files with 381 additions and 279 deletions
|
|
@ -1,13 +1,11 @@
|
|||
\newpage
|
||||
|
||||
`Dockerfile`
|
||||
============
|
||||
|
||||
## Ma première image ... par `Dockerfile`
|
||||
Ma première image ... par `Dockerfile`
|
||||
======================================
|
||||
|
||||
Pour construire une image, nous ne sommes pas obligés de passer par une série
|
||||
de commits. Docker dispose d'un mécanisme permettant d'automatiser la
|
||||
construction de nouvelles images. Vous pouvez arriver au même résultat que ce
|
||||
de *commits*. Docker dispose d'un mécanisme permettant d'automatiser la
|
||||
construction de nouvelles images. Nous pouvons arriver au même résultat que ce
|
||||
que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -19,7 +17,7 @@ que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant
|
|||
```
|
||||
</div>
|
||||
|
||||
La syntaxe d'un `Dockerfile` est simple, le premier mot de chaque ligne est
|
||||
La syntaxe d'un `Dockerfile` est simple : le premier mot de chaque ligne est
|
||||
l'intitulé d'une instruction (que l'on écrit généralement en majuscule), elle
|
||||
est suivie de ses arguments.
|
||||
|
||||
|
|
@ -27,9 +25,9 @@ Dans notre exemple, nous utilisons `FROM` qui indique une image de départ à
|
|||
utiliser ; `RUN` est une commande qui sera exécutée dans le conteneur, dans le
|
||||
but de le construire.
|
||||
|
||||
Pour lancer la construction de la nouvelle image, créer un nouveau dossier ne
|
||||
contenant que votre fichier `Dockerfile`, placez-vous dedans, puis utilisez la
|
||||
commande `build` :
|
||||
Pour lancer la construction de la nouvelle image, créons un nouveau dossier ne
|
||||
contenant que votre fichier `Dockerfile`, plaçons-nous ensuite dedans, puis
|
||||
lançons la commande `build` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
|
|
@ -37,7 +35,7 @@ commande `build` :
|
|||
```
|
||||
</div>
|
||||
|
||||
Une fois la construction de l'image terminée, vous pouvez la lancer et
|
||||
Une fois la construction de l'image terminée, nous pouvons la lancer et
|
||||
constater l'existence de notre éditeur favori :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -62,9 +60,21 @@ Cela signifie que l'exemple suivant **ne fonctionne pas** :
|
|||
```
|
||||
</div>
|
||||
|
||||
Cet exemple ne fonctionne pas car le serveur MySQL qui est lancé dans le
|
||||
premier `RUN`, n'est plus lancé au moment du deuxième `RUN`. En effet, chaque
|
||||
commande du `Dockerfile` a pour but de modifier le système de fichiers.
|
||||
Cet exemple ne fonctionne pas car le serveur MySQL est bien lancé dans le
|
||||
premier `RUN`, mais il se trouve brûtalement arrêté dès lors que la commande
|
||||
`service` se termine. En fait, à chaque instruction, Docker réalise
|
||||
automatiquement un `run` suivi d'un `commit`. Et vous pouvez constater par
|
||||
vous-même que, en créant l'image `tinysql` à partir d'un simple `apt install
|
||||
mysql` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
docker container run tinysql service mysqld start
|
||||
```
|
||||
</div>
|
||||
|
||||
rend la main directement, sans laisser de `mysqld` dans l'arborescence de
|
||||
processus.
|
||||
|
||||
Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
|
||||
|
||||
|
|
@ -75,13 +85,17 @@ Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
|
|||
```
|
||||
</div>
|
||||
|
||||
Après le `RUN`, MySQL sera de nouveau arrêté, si on veut l'utiliser dans le
|
||||
conteneur, il ne faudra pas oublier de lancer le processus.
|
||||
Après le `RUN`, MySQL sera de nouveau tué.
|
||||
|
||||
En aucun cas, une commande exécutée par un `RUN` 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.
|
||||
|
||||
|
||||
## Exposer des ports
|
||||
|
||||
Construisons maintenant un conteneur avec un serveur web :
|
||||
Construisons maintenant un conteneur avec un service web :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
|
|
@ -95,30 +109,90 @@ Construisons maintenant un conteneur avec un serveur web :
|
|||
</div>
|
||||
|
||||
L'instruction `EXPOSE` sera traitée plus tard par le client Docker (équivalent
|
||||
à l'argument `--expose`). Il s'agit de préciser les ports sur lesquels votre
|
||||
image écoute.
|
||||
à l'argument `--expose`). Il s'agit d'une métadonnée qui sera attachée à
|
||||
l'image (et à toutes ses images filles).
|
||||
|
||||
En utilisant l'option `-P` du `run`, vous allez pouvoir assigner une
|
||||
redirection de port aléatoire sur la machine hôte vers votre conteneur :
|
||||
En précisant tous les ports qu'exposent une image dans ses métadonnées, ces
|
||||
ports seront automatiquement exposés en utilisant l'option `-P` du `run` : cela
|
||||
assigne une redirection de port aléatoire sur la machine hôte vers votre
|
||||
conteneur :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
docker image build --tag=my_webserver .
|
||||
docker container run -it -P my_webserver /bin/bash
|
||||
service nginx start
|
||||
42sh$ docker image build --tag=my_webserver .
|
||||
42sh$ docker container run -it -P my_webserver /bin/bash
|
||||
(cntnr)# service nginx start
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans un autre terminal, lancer un `docker ps` et consulter la colonne *PORTS*
|
||||
pour connaître le port choisi par Docker pour effectuer la redirection.
|
||||
Dans un autre terminal, lancer un `docker container ls` et consulter la colonne
|
||||
*PORTS* pour connaître le port choisi par Docker pour effectuer la redirection.
|
||||
|
||||
Rendez-vous ensuite dans votre navigateur sur <http://localhost:49153/>.
|
||||
|
||||
*À vous de jouer :* utilisez l'instruction `COPY` pour afficher votre propre
|
||||
`index.html` remplaçant celui installé de base par nginx.
|
||||
`index.html` remplaçant celui installé de base par `nginx`. Si vous manquez
|
||||
d'inspiration, utilisez [cette page de compte à
|
||||
rebours](https://virli.nemunai.re/countdown.html).
|
||||
|
||||
|
||||
## Lancement de commande automatique
|
||||
## Les caches
|
||||
|
||||
Nous avons vu que chaque instruction de notre `Dockerfile` est exécutée dans un
|
||||
conteneur, qui génère une image intermédiaire. Cette image intermédiaire sert
|
||||
ensuite d'image de base pour le conteneur qui sera lancé avec l'instruction
|
||||
suivante.
|
||||
|
||||
Lorsqu'on lance la reconstruction du même `Dockerfile`, les images
|
||||
intermédiaires sont réutilisées, comme un cache d'instructions. Cela permet de
|
||||
gagner du temps sur les étapes qui n'ont pas changées. Ainsi, lorsque vous
|
||||
modifiez une instruction dans votre `Dockerfile`, les instructions précédentes
|
||||
ne sont pas réexécutées mais sont ressorties du cache.
|
||||
|
||||
Le cache se base principalement sur le contenu de chaque instruction du
|
||||
`Dockerfile` (pour les `COPY` et `ADD`, il va aussi regarder la date de
|
||||
dernière modification de fichier à copier ou à ajouter). Donc tant qu'une
|
||||
instruction n'est pas modifiée dans le `Dockerfile`, le cache sera utilisé.
|
||||
|
||||
Il est possible de ne pas utiliser le cache et de relancer toutes les étapes du
|
||||
`Dockerfile` en ajoutant l'option `--no-cache` au moment du `docker image
|
||||
build`.
|
||||
|
||||
Les couches du cache peuvent être partagées entre plusieurs conteneur, c'est
|
||||
ainsi que vous pouvez partager facilement une plus grosse partie du système de
|
||||
fichiers (rappelez-vous le principe d'union FS).
|
||||
|
||||
Pour profiter du cache, on va placer de préférences les étapes les plus
|
||||
génériques (qui seraient les plus susceptibles d'apparaître dans d'autres
|
||||
images), en haut du `Dockerfile`.
|
||||
|
||||
|
||||
## Métadonnées pures
|
||||
|
||||
L'instruction LABEL permet d'ajouter une métadonnée à une image, sous forme de
|
||||
clef/valeur.
|
||||
|
||||
Une métadonnée
|
||||
[courante](https://github.com/nginxinc/docker-nginx/blob/master/mainline/stretch/Dockerfile#L3)
|
||||
est d'indiquer le nom du mainteneur de l'image :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
LABEL maintainer="Pierre-Olivier Mercier <nemunaire@nemunai.re>"
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans notre `Dockerfile`, indiquez juste après l'image de base, vos noms,
|
||||
prénoms et mails de contact avec l'instruction `LABEL maintainer`, pour
|
||||
indiquer que c'est vous qui maintenez cette image, si des utilisateurs ont
|
||||
besoin de vous avertir pour le mettre à jour ou s'ils rencontrent des
|
||||
difficultés par exemple.
|
||||
|
||||
On le place dès le début, car comme c'est une information qui n'est pas amener
|
||||
à changer, elle sera toujours retrouvée en cache.
|
||||
|
||||
|
||||
## Commande par défaut
|
||||
|
||||
Vous pouvez placer dans un `Dockerfile` une instruction `CMD` qui sera exécutée
|
||||
si aucune commande n'est passée lors du `run`, par exemple :
|
||||
|
|
@ -131,8 +205,8 @@ si aucune commande n'est passée lors du `run`, par exemple :
|
|||
|
||||
<div lang="en-US">
|
||||
```
|
||||
docker image build --tag=my_nginx .
|
||||
docker container run -d -P my_nginx
|
||||
42sh$ docker image build --tag=my_nginx .
|
||||
42sh$ docker container run -d -P my_nginx
|
||||
```
|
||||
</div>
|
||||
|
||||
|
|
@ -142,16 +216,104 @@ retirez cette option pour voir ce qui ne va pas, ou utilisez la commande
|
|||
`docker container logs`.
|
||||
|
||||
|
||||
## Construire son application au moment de la construction du conteneur ?
|
||||
|
||||
Comment faire lorsque l'on a besoin de compiler une application avant de
|
||||
l'intégrer dans le conteneur ?
|
||||
|
||||
On peut vouloir lancer la compilation sur notre machine, mais cela ne sera pas
|
||||
très reproductible et cela aura nécessité d'installer le compilateur et les
|
||||
outils liés au langage que l'on souhaite compiler. Peut-être que plusieurs
|
||||
versions de ces outils existent, laquelle choisir ? ... Ok c'est trop
|
||||
compliqué.
|
||||
|
||||
D'un autre côté, si l'on fait cela dans un conteneur, celui-ci contiendra dans
|
||||
ses couches des données inutiles à l'exécution : les sources, les produits
|
||||
intermédiaires de compilation, le compilateur, n'ont rien à faire dans les
|
||||
couches de notre image.
|
||||
|
||||
Le meilleur des deux mondes se trouve dans les *Multi-stage builds* : au sein
|
||||
du même `Dockerfile`, on va réaliser les opérations de préparation dans un ou
|
||||
plusieurs conteneurs, avant d'agréger le contenu compilé au sein du conteneur
|
||||
final :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
FROM gcc:4.9
|
||||
COPY . /usr/src/myapp
|
||||
WORKDIR /usr/src/myapp
|
||||
RUN gcc -static -static-libgcc -o hello hello.c
|
||||
|
||||
FROM scratch
|
||||
COPY --from=0 /usr/src/myapp/hello /hello
|
||||
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
|
||||
`hello.c`. Mais l'image finale (le dernier `FROM` de notre `Dockerfile`) est
|
||||
l'image vide, dans laquelle nous recopions simplement le produit de notre
|
||||
compilation.
|
||||
|
||||
L'image ainsi générée est minime, car elle ne contient rien d'autre que le
|
||||
strict nécessaire pour s'exécuter.
|
||||
|
||||
### Étapes nommées
|
||||
|
||||
Nous avons utilisé `--from=0` pour désigner la première image de notre
|
||||
`Dockerfile`. Lorsque l'on réalise des montages plus complexe, on peut vouloir
|
||||
donner des noms à chaque image, plutôt que de devoir jongler avec les
|
||||
numéros. Dans ce cas, on indiquera :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
FROM gcc:4.9 as builder
|
||||
COPY . /usr/src/myapp
|
||||
WORKDIR /usr/src/myapp
|
||||
RUN gcc -static -static-libgcc -o hello hello.c
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /usr/src/myapp/hello /hello
|
||||
CMD ["/hello"]
|
||||
```
|
||||
</div>
|
||||
|
||||
Par défaut la dernière étape du `Dockerfile` est retenu comme étant l'image que
|
||||
l'on souhaite `tagger`, mais il est possible de préciser quelle image
|
||||
spécifiquement on souhaite construire avec l'option `--target` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker build --target builder -t hello-builder .
|
||||
```
|
||||
</div>
|
||||
|
||||
Cela peut être particulièrement utile si l'on dispose d'une image de debug,
|
||||
incluant tous les symboles, et une image de production, plus propre. On
|
||||
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'autres instructions ?
|
||||
|
||||
Consultez <https://docs.docker.com/engine/reference/builder/> pour la liste
|
||||
complète des instructions reconnues.
|
||||
|
||||
|
||||
## Rendu
|
||||
## Exercice
|
||||
|
||||
Rendez le fichier `Dockerfile` et son contexte (`index.html`, fichiers de conf
|
||||
éventuels, ...) que vous avez utilisé pour réaliser votre image `my_webserver`.
|
||||
Pour mettre en application tout ce que nous venons de voir, réalisons le
|
||||
`Dockerfile` du service web [`youp0m`](https://you.p0m.fr/) que nous avons
|
||||
utilisé la semaine dernière.
|
||||
|
||||
Une attention particulière sera apportée au respect des différentes bonnes
|
||||
pratiques vues en cours pour l'écriture du `Dockerfile`.
|
||||
Pour réaliser ce genre de contribution, on ajoute généralement un `Dockerfile`
|
||||
à la racine du dépôt.
|
||||
|
||||
Vous pouvez cloner le dépôts de sources de `youp0m` à :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
https://git.nemunai.re/youp0m.git
|
||||
```
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue