virli/tutorial/dockerfiles/dockerfile.md

331 lines
11 KiB
Markdown
Raw Normal View History

2015-10-22 03:25:20 +00:00
\newpage
2018-10-18 05:05:36 +00:00
Ma première image ... par `Dockerfile`
2021-09-23 00:55:18 +00:00
--------------------------------------
2015-10-22 03:25:20 +00:00
2017-10-04 23:42:56 +00:00
Pour construire une image, nous ne sommes pas obligés de passer par une série
2018-10-18 05:05:36 +00:00
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
2021-09-23 00:55:18 +00:00
que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```dockerfile
FROM ubuntu:latest
2015-10-22 03:25:20 +00:00
RUN apt-get update
RUN apt-get install -y nano
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
La syntaxe d'un `Dockerfile` est simple : le premier mot de chaque ligne est
2015-10-22 03:25:20 +00:00
l'intitulé d'une instruction (que l'on écrit généralement en majuscule), elle
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.
2015-10-22 03:25:20 +00:00
2018-10-18 05:05:36 +00:00
Pour lancer la construction de la nouvelle image, créons un nouveau dossier ne
2021-09-24 15:12:07 +00:00
contenant que notre fichier `Dockerfile`, plaçons-nous ensuite dedans, puis
2021-09-23 00:55:18 +00:00
lançons la commande `build` :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
docker image build --tag=my_editor .
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2018-10-18 05:05:36 +00:00
Une fois la construction de l'image terminée, nous pouvons la lancer et
2021-09-23 00:55:18 +00:00
constater l'existence de notre éditeur favori :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
docker container run -it my_editor /bin/bash
2020-09-14 13:46:13 +00:00
(in_cntr)# nano
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
### `RUN` dans le `Dockerfile`
2015-10-22 03:25:20 +00:00
2017-10-16 20:59:22 +00:00
Dans un `Dockerfile`, chaque ligne est exécutée indépendamment des autres et
2020-10-28 22:16:34 +00:00
correspondra à une nouvelle couche de notre image. Exactement comme on a
2021-09-24 15:12:07 +00:00
réalisé le script à la fin de la partie précédente.
2016-09-08 01:44:20 +00:00
2021-09-23 00:55:18 +00:00
Cela signifie que l'exemple suivant **ne fonctionne pas** :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```dockerfile
COPY db.sql /db.sql
RUN service mysqld start
RUN mysql -u root -p toor virli < /db.sql
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2018-10-18 05:05:36 +00:00
Cet exemple ne fonctionne pas car le serveur MySQL est bien lancé dans le
2021-09-23 00:55:18 +00:00
premier `RUN`{.dockerfile}, mais il se trouve brutalement arrêté dès lors que
la commande `service` se termine. En fait, à chaque instruction, Docker réalise
2020-09-14 13:46:13 +00:00
automatiquement l'équivalent un `docker run` suivi d'un `commit`. Et vous
pouvez constater par vous-même que, en créant l'image `tinysql` à partir d'un
2021-09-23 00:55:18 +00:00
simple `apt install mysql` :
2018-10-18 05:05:36 +00:00
<div lang="en-US">
```bash
docker container run tinysql service mysqld start
2018-10-18 05:05:36 +00:00
```
</div>
rend la main directement, sans laisser de `mysqld` dans l'arborescence de
2021-09-24 15:12:07 +00:00
processus.\
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```dockerfile
COPY db.sql /db.sql
RUN service mysqld start && mysql -u root -p toor virli < /db.sql
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2021-09-24 15:12:07 +00:00
Après le `RUN`{.dockerfile}, MySQL sera de nouveau tué.\
2018-10-18 05:05:36 +00:00
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
2018-10-18 05:05:36 +00:00
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.
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
### Exposer des ports
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
Construisons maintenant un conteneur avec un service web :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```dockerfile
FROM my_editor
2015-10-22 03:25:20 +00:00
RUN apt-get update
RUN apt-get install -y nginx
2015-10-22 03:25:20 +00:00
EXPOSE 80
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2020-09-14 13:46:13 +00:00
L'instruction `EXPOSE`{.dockerfile} sera traitée plus tard par le client Docker
(équivalent à l'argument `--expose`). Il s'agit d'une métadonnée qui sera
2021-09-24 15:12:07 +00:00
attachée à l'image (et à toutes ses images filles). Elle ne crée d'ailleurs pas
de couche supplémentaire dans notre image.\
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
En précisant tous les ports qu'expose une image dans ses métadonnées, ces
ports seront automatiquement exposés en utilisant l'option `-P` du `run` : cela
2018-10-18 05:05:36 +00:00
assigne une redirection de port aléatoire sur la machine hôte vers votre
2021-09-23 00:55:18 +00:00
conteneur :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2015-10-22 03:25:20 +00:00
```
42sh$ docker image build --tag=my_webserver .
42sh$ docker container run -it -P my_webserver /bin/bash
(cntnr)# service nginx start
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2021-09-24 15:12:07 +00:00
Dans un autre terminal, lançons un `docker container ls`, pour consulter la colonne
*PORTS* afin de connaître le port choisi par Docker pour effectuer la redirection.
2015-10-22 03:25:20 +00:00
Rendez-vous ensuite dans votre navigateur sur <http://localhost:49153/>.
2021-09-23 00:55:18 +00:00
#### À vous de jouer {-}
Utilisez l'instruction `COPY`{.dockerfile} pour afficher votre propre
2018-10-18 05:05:36 +00:00
`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).
2021-09-23 00:55:18 +00:00
### Les caches
2018-10-18 05:05:36 +00:00
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).
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
Pour profiter du cache, on va placer de préférence les étapes les plus
2018-10-18 05:05:36 +00:00
génériques (qui seraient les plus susceptibles d'apparaître dans d'autres
images), en haut du `Dockerfile`.
2015-10-22 03:25:20 +00:00
2018-10-18 05:05:36 +00:00
2021-09-23 00:55:18 +00:00
### Métadonnées pures
2018-10-18 05:05:36 +00:00
L'instruction `LABEL`{.dockerfile} permet d'ajouter une métadonnée à une image,
sous forme de clef/valeur.
2018-10-18 05:05:36 +00:00
Une métadonnée
2021-09-24 15:12:07 +00:00
[courante](https://github.com/nginxinc/docker-nginx/blob/master/stable/debian/Dockerfile#L8)
2021-09-23 00:55:18 +00:00
est d'indiquer le nom du mainteneur de l'image :
2018-10-18 05:05:36 +00:00
<div lang="en-US">
```dockerfile
LABEL maintainer="Pierre-Olivier Mercier <nemunaire@nemunai.re>"
2018-10-18 05:05:36 +00:00
```
</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`{.dockerfile},
pour indiquer que c'est vous qui maintenez cette image, si des utilisateurs ont
2018-10-18 05:05:36 +00:00
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.
2021-09-23 00:55:18 +00:00
### Commande par défaut
2015-10-22 03:25:20 +00:00
Vous pouvez placer dans un `Dockerfile` une instruction `CMD`{.dockerfile} qui
2021-09-23 00:55:18 +00:00
sera exécutée si aucune commande n'est passée lors du `run`, par exemple :
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```dockerfile
CMD nginx -g "daemon off;"
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ docker image build --tag=my_nginx .
42sh$ docker container run -d -P my_nginx
2015-10-22 03:25:20 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2015-10-22 03:25:20 +00:00
2017-10-16 20:59:22 +00:00
L'option `-d` passée au `run` lance le conteneur en tâche de fond. Si vous
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`.
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
### Construire son application au moment de la construction du conteneur ?
2018-10-18 05:05:36 +00:00
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
2021-09-23 00:55:18 +00:00
ses couches des données inutiles à l'exécution : les sources, les produits
2018-10-18 05:05:36 +00:00
intermédiaires de compilation, le compilateur, n'ont rien à faire dans les
couches de notre image.
2021-09-23 00:55:18 +00:00
Le meilleur des deux mondes se trouve dans les *Multi-stage builds* : au sein
2018-10-18 05:05:36 +00:00
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
2021-09-23 00:55:18 +00:00
final :
2018-10-18 05:05:36 +00:00
<div lang="en-US">
```dockerfile
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"]
2018-10-18 05:05:36 +00:00
```
</div>
2021-09-23 00:55:18 +00:00
Dans cet exemple, deux conteneurs distincts sont créés : le premier à partir de
2018-10-18 05:05:36 +00:00
l'image `gcc`, il 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.
2018-10-18 05:05:36 +00:00
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.
2021-09-23 00:55:18 +00:00
#### Étapes nommées\
2018-10-18 05:05:36 +00:00
Nous avons utilisé `--from=0` pour désigner la première image de notre
2021-09-23 00:55:18 +00:00
`Dockerfile`. Lorsque l'on réalise des montages plus complexes, on peut vouloir
2018-10-18 05:05:36 +00:00
donner des noms à chaque image, plutôt que de devoir jongler avec les
2021-09-23 00:55:18 +00:00
numéros. Dans ce cas, on indiquera :
2018-10-18 05:05:36 +00:00
<div lang="en-US">
```dockerfile
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"]
2018-10-18 05:05:36 +00:00
```
</div>
2021-09-23 00:55:18 +00:00
Par défaut la dernière étape du `Dockerfile` est retenue comme étant l'image que
2018-10-18 05:05:36 +00:00
l'on souhaite `tagger`, mais il est possible de préciser quelle image
2021-09-23 00:55:18 +00:00
spécifiquement on souhaite construire avec l'option `--target` :
2018-10-18 05:05:36 +00:00
<div lang="en-US">
```
42sh$ docker build --target builder -t hello-builder .
2018-10-18 05:05:36 +00:00
```
</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.
2021-09-23 00:55:18 +00:00
### D'autres instructions ?
2015-10-22 03:25:20 +00:00
2017-10-16 20:59:22 +00:00
Consultez <https://docs.docker.com/engine/reference/builder/> pour la liste
complète des instructions reconnues.
2015-10-22 03:25:20 +00:00
2021-09-23 00:55:18 +00:00
### Exercice {-}
2018-10-18 05:05:36 +00:00
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.
2015-10-22 03:25:20 +00:00
2018-10-18 05:05:36 +00:00
Pour réaliser ce genre de contribution, on ajoute généralement un `Dockerfile`
à la racine du dépôt.
2017-10-08 22:08:33 +00:00
2021-09-23 00:55:18 +00:00
Vous pouvez cloner le dépôt de sources de `youp0m` à :
<https://git.nemunai.re/nemunaire/youp0m.git>
Pour compiler le projet, vous pouvez utiliser dans votre `Dockerfile`
<div lang="en-US">
```dockerfile
2021-09-23 00:55:18 +00:00
FROM golang:1.16
COPY . /go/src/git.nemunai.re/youp0m
WORKDIR /go/src/git.nemunai.re/youp0m
2020-09-14 13:46:13 +00:00
RUN go build -tags dev -v
```
</div>