diff --git a/subject/docker-updater/Makefile b/subject/docker-updater/Makefile new file mode 100644 index 0000000..5e858c8 --- /dev/null +++ b/subject/docker-updater/Makefile @@ -0,0 +1,12 @@ +include ../../tutorial/pandoc-opts.mk + +SOURCES = subject.md docker-api.md ex-api-updater-intro.md ex-api-updater.md rendu.md end.md + + +all: subject.pdf + +subject.pdf: ${SOURCES} + pandoc ${PANDOCOPTS} -o $@ $+ + +clean:: + rm subject.pdf diff --git a/subject/docker-updater/docker-api.md b/subject/docker-updater/docker-api.md new file mode 100644 index 0000000..e69de29 diff --git a/subject/docker-updater/end.md b/subject/docker-updater/end.md new file mode 100644 index 0000000..9dc41f2 --- /dev/null +++ b/subject/docker-updater/end.md @@ -0,0 +1 @@ +Bon courage ! diff --git a/subject/docker-updater/ex-api-updater-intro.md b/subject/docker-updater/ex-api-updater-intro.md new file mode 100644 index 0000000..38f6d5c --- /dev/null +++ b/subject/docker-updater/ex-api-updater-intro.md @@ -0,0 +1,2 @@ +Mini-projet +=========== diff --git a/subject/docker-updater/ex-api-updater.md b/subject/docker-updater/ex-api-updater.md new file mode 100644 index 0000000..7dae630 --- /dev/null +++ b/subject/docker-updater/ex-api-updater.md @@ -0,0 +1,131 @@ +::::: {.exercice} + +Écrivez un programme tirant parti de l'API du Docker Engine (le *daemon* +Docker) qui pour un conteneur donné : + +1. détecte si le conteneur exécute une image disposant d'une mise à jour ; +1. cherche à récupérer la dernière image disponible ; +1. mette à jour le contneur ; +1. dans un conteneur, automatiquement pour toutes les images. + +## Étape 1 : Lister les conteneurs + +Lancé sans argument, votre programme doit retourner la liste des conteneurs +actifs. Un nom par ligne. + +### Exemple d'exécution {-} + +
+```bash +42sh$ docker run -d nginx +42sh$ docker run -d mysql +42sh$ docker run -d redis +42sh$ ctr-updater +Choose a container to analyze: +dazzling_driscoll +amazing_proskuriakova +optimistic_meninsky +``` +
+ + +## Étape 1 bis : Prêt pour la prod + +Écrivez un `Dockerfile` pour conteneuriser ce programme : gérer tant la +construction (s'il y a des étapes de construction) que l'exécution. En +utilisant les bonnes pratiques vues en cours. + +### Exemple d'exécution {-} + +
+```bash +42sh$ docker run -d --name myp0m nemunaire/youp0m +42sh$ docker build -t login/ctr-updater . +42sh$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock login/ctr-updater +Choose a container to analyze: +myp0m + +# Après l'étape 2 : +42sh$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock login/ctr-updater myp0m +42sh$ echo $? +0 # Le conteneur est à jour +``` +
+ +Notez que l'on n'emploie pas `ctr-updater` pour appeler le binaire dans +l'image. Le premier argument passé au conteneur est donc le nom du conteneur à +vérifier. + + +## Étape 2 : Détecter si le conteneur exécute la dernière image disponible + +Lancé avec un argument, votre programme doit, à partir des informations +accessibles localement (conteneurs et images), retourner le code de 0 ou 1, +suivant si (0) aucune mise à jour de l'image n'est disponible, respectivement +(1) une mise à jour est disponible. + +### Exemple d'exécution {-} + +
+```bash +42sh$ docker build -t my_webserver . +42sh$ docker run -d --name mws my_webserver +42sh$ ctr-updater mws +42sh$ echo $? +0 # Le conteneur est à jour + +42sh$ echo "nouvelle page" > index.html +42sh$ docker build -t my_webserver . +42sh$ ctr-updater mws +42sh$ echo $? +1 # Une mise à jour est dispo. +``` +
+ +N'hésitez pas à utiliser la sortie d'erreur et la sortie standard pour afficher +des informations pour vous. Celles-ci ne seront pas vérifiées. + + +## Étape 3 : Chercher une mise à jour l'image + +En ajoutant l'option `--pull`, votre programme va lancer un pull de l'image +avant de faire la vérification. + +
+```bash +42sh$ docker run -d --name myp0m nemunaire/youp0m +42sh$ ctr-updater myp0m +42sh$ echo $? +0 # Le conteneur est à jour +42sh$ ctr-updater --pull myp0m +42sh$ echo $? +1 # Une mise à jour est dispo. +42sh$ ctr-updater myp0m +42sh$ echo $? +1 # La nouvelle image a été pull précédemment + # et est disponible dans le cache local +``` +
+ +L'image `youp0m` est mise à jour régulièrement. Il y a de forte chance pour +qu'elle ne soit plus à jour si celle dont vous disposez date de plus d'une +semaine. Si vous possédez déjà la dernière version de vos conteneurs, +recherchez une image sur le Docker Hub régulièrement mise à jour pour faire vos +tests. + +Attention une fois l'image *pull* par `ctr-updater`, un appel à nouveau à +`ctr-updater` sans `--pull` retourne la mise à jour, car le `pull` précédent +aura téléchargé localement l'image. + + +## Étape 4 : Mettre à jour le conteneur + +En ajoutant l'option `--autoupdate`, si une mise à jour de l'image utilisée par +le conteneur est disponible (après un éventuel `pull` si l'option est passée), +le conteneur est arrêté, supprimé, puis relancé avec les mêmes options, mais en +utilisant la nouvelle image. + +Il est attendu dans ce cas de toujours retourner le statut 0 si la mise à jour +se passe bien. + +::::: diff --git a/subject/docker-updater/rendu.md b/subject/docker-updater/rendu.md new file mode 120000 index 0000000..76874b5 --- /dev/null +++ b/subject/docker-updater/rendu.md @@ -0,0 +1 @@ +../../tutorial/2/rendu.md \ No newline at end of file diff --git a/subject/docker-updater/subject.md b/subject/docker-updater/subject.md new file mode 100644 index 0000000..97575d7 --- /dev/null +++ b/subject/docker-updater/subject.md @@ -0,0 +1,10 @@ +--- +title: Virtualisation légère -- Mini-projet n^o^ 2 +author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} +institute: EPITA +date: EPITA -- Promo 2023 +abstract: | + Le rendu de ce mini-projet individuel est attendu par un tag signé + sur un dépôt privé de la forge de l'école au plus tard le **mardi + 18 octobre 2022 à 23 h 42**. +... diff --git a/tutorial/2/Makefile b/tutorial/2/Makefile index 408778e..1868738 100644 --- a/tutorial/2/Makefile +++ b/tutorial/2/Makefile @@ -1,8 +1,12 @@ include ../pandoc-opts.mk SOURCES = tutorial.md \ + ../docker-advanced/security.md \ ../dockerfiles/what.md ../dockerfiles/interactive.md ../dockerfiles/dockerfile.md ../dockerfiles/goodpractices.md ../dockerfiles/others.md ../dockerfiles/entrypoint.md \ - ../docker-internals/vulnerability-scan.md ../docker-internals/clair-tiny.md ../docker-internals/vulnerability-scan-ex.md \ + ../docker-internals/vulnerability-scan.md ../docker-internals/clair-tiny.md ../docker-internals/vulnerability-scan-ex.md + +SOURCES_PROJECT = tutorial.md \ + ../docker-advanced/what.md ../docker-advanced/setup.md ../docker-advanced/manual.md ../new-page.md ../docker-advanced/compose.md \ rendu.md SOURCES_CLAIR = tutorial-clair.md \ diff --git a/tutorial/2/rendu.md b/tutorial/2/rendu.md index b51b1a8..36a26d2 100644 --- a/tutorial/2/rendu.md +++ b/tutorial/2/rendu.md @@ -3,61 +3,36 @@ Rendu ===== -Est attendu d'ici le TP suivant : +Est attendu d'ici le cours suivant : -- le rendu des exercice de ce TP ; -- vos réponses à [l'évaluation du cours](https://virli.nemunai.re/quiz/12). - -Pour les SRS (et en bonus pour les GISTRE), [un -projet](https://virli.nemunai.re/project-1.pdf) est à rendre pour le 13 -octobre. Consultez les modalités de rendu sur le sujet directement. +- votre `Dockerfile` et son *entrypoint* pour dépôt `youp0m` ; +- ce mini-projet ; +- vos réponses à [l'évaluation du cours](https://virli.nemunai.re/quiz/34). -Modalités de rendu ------------------- - -En tant que personnes sensibilisées à la sécurité des échanges électroniques, -vous devrez m'envoyer vos rendus signés avec votre clef PGP. - -Un service automatique s'occupe de réceptionner vos rendus, de faire des -vérifications élémentaires et de vous envoyer un accusé de réception (ou de -rejet). - -Ce service écoute sur l'adresse , c'est donc à cette adresse -et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu -envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne -sera pas pris en compte. - -Afin d'orienter correctement votre rendu, ajoutez une balise `[TP2]` au sujet -de votre courriel. N'hésitez pas à indiquer dans le corps du message votre -ressenti et vos difficultés ou bien alors écrivez votre meilleure histoire -drôle si vous n'avez rien à dire. - - -Tarball +Arborescence attendue ------- -Tous les fichiers identifiés comme étant à rendre pour ce TP sont à -placer dans une tarball (pas d'archive ZIP, RAR, ...). +Tous les fichiers identifiés comme étant à rendre sont à placer dans un dépôt +Git privé, que vous partagerez avec [votre +professeur](https://gitlab.cri.epita.fr/nemunaire/). -Voici une arborescence type (vous pourriez avoir des fichiers -supplémentaires) : +Voici une arborescence type (vous pourriez avoir des fichiers supplémentaires) :
``` -login_x-TP2/ -login_x-TP2/my_webservice/ # 1.2.2 + 1.3 -login_x-TP2/my_webservice/Dockerfile -login_x-TP2/my_webservice/index.html -login_x-TP2/youp0m/ # 1.2.7 + 1.3 -login_x-TP2/youp0m/Dockerfile -login_x-TP2/youp0m/entrypoint.sh # 2.2.2 -login_x-TP2/youp0m/.dockerignore -login_x-TP2/youp0m/... -login_x-TP2/mysql:latest.html # rapport d'analyse PAclair/Trivy/... -login_x-TP2/....html +./ +./youp0m/ # 1.2.7 + 1.3 +./youp0m/Dockerfile +./youp0m/entrypoint.sh # 2.2.2 +./youp0m/.dockerignore +./youp0m/... +./ctr-updater/ +./ctr-updater/Dockerfile +./ctr-updater/... ```
-Inutile d'inclure le contenu du dépôt `youp0m` dans votre tarball. Placez-y -uniquement les fichiers que vous avez ajouté. +Votre rendu sera pris en compte en faisant un [tag **signé par votre clef +PGP**](https://lessons.nemunai.re/keys). Consultez les détails du rendu (nom du +tag, ...) sur la page dédiée au projet sur la plateforme de rendu. diff --git a/tutorial/2/tutorial-clair.md b/tutorial/2/tutorial-clair.md index 919496a..16c7553 100644 --- a/tutorial/2/tutorial-clair.md +++ b/tutorial/2/tutorial-clair.md @@ -3,7 +3,7 @@ title: Virtualisation légère -- TP n^o^ 2 subtitle: Construire des images Docker et leur sécurité -- Annexe author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} institute: EPITA -date: Jeudi 23 septembre 2021 +date: Mercredi 21 septembre 2022 abstract: | Clair est un outil très intéressant à déployer dans un contexte d'analyse continue, au sein d'une plateforme de déploiement continue par exemple. Vous diff --git a/tutorial/2/tutorial.md b/tutorial/2/tutorial.md index 8ac7b33..df833f6 100644 --- a/tutorial/2/tutorial.md +++ b/tutorial/2/tutorial.md @@ -3,23 +3,9 @@ title: Virtualisation légère -- TP n^o^ 2 subtitle: Construire des images Docker et leur sécurité author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} institute: EPITA -date: Jeudi 23 septembre 2021 +date: Mercredi 21 septembre 2022 abstract: | Durant ce deuxième TP, nous allons voir comment créer nos propres images, et comment s'assurer qu'elles n'ont pas de vulnérabilités connues ! - - \vspace{1em} - - Tous les éléments de ce TP (exercices et projet) sont à rendre à - au plus tard le **mercredi 29 septembre 2021 à 23 h - 42**. Consultez la dernière section de chaque partie pour plus d'informations - sur les éléments à rendre. Et n'oubliez pas de répondre aux [questions de - cours](https://virli.nemunai.re/quiz/12). - - En tant que personnes sensibilisées à la sécurité des échanges électroniques, - vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à - [me](https://keys.openpgp.org/search?q=nemunaire%40nemunai.re) faire signer - votre clef et n'hésitez pas à [faire signer la - vôtre](https://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/). ... diff --git a/tutorial/docker-internals/vulnerability-scan.md b/tutorial/docker-internals/vulnerability-scan.md index a76ad10..425ae4d 100644 --- a/tutorial/docker-internals/vulnerability-scan.md +++ b/tutorial/docker-internals/vulnerability-scan.md @@ -20,8 +20,8 @@ Il est donc primordial de ne pas laisser ses conteneurs à l'abandon une fois leur image créée et envoyée en production. Nos conteneurs doivent être regénérés sitôt que leur image de base est mise à jour (une mise à jour d'une image telle que Debian, Ubuntu ou Redhat n'apparaît que pour cela) ou bien -lorsqu'un des programmes ou l'une des bibliothèques que l'on a installées -ensuite est mise à jour. +lorsqu'un des programmes ou l'une des bibliothèques que l'on a installées est +mise à jour. Convaincu ? Cela sonne encore comme des bonnes pratiques difficiles à mettre en œuvre, pouvant mettre en péril tout un système d'information. Pour s'en @@ -38,13 +38,13 @@ propager à cause de l'usage d'une image parente pas à jour. [^dockerhubvulnerability]: Une mesure efficace consiste à reconstruire régulièrement (et surtout -automatiquement) les images que l'on publie sur un registre public, sans -oublier de mettre à jour l'image de base. +automatiquement) les images que l'on publie sur un registre, sans oublier de +mettre à jour l'image de base. -D'ailleurs, avez-vous vérifié qu'une mise à jour de l'image `nemunaire/youp0m` -n'était pas disponible depuis que vous avez commencé à l'utiliser ? Docker ne -vérifie jamais si une mise à jour des images que vous avez précédemment -téléchargées. Pensez donc régulièrement à appeler : +D'ailleurs, avez-vous vérifié qu'une mise à jour de l'image +`registry.nemunai.re/youp0m` n'était pas disponible depuis que vous avez +commencé à l'utiliser ? Docker ne vérifie jamais si une mise à jour des images +que vous avez précédemment téléchargées. Pensez donc régulièrement à appeler :
``` @@ -67,7 +67,7 @@ intermédiaire. ::::: {.warning} -Par cette méthode, vous êtes limité à 10 scans par mois. +Par cette méthode, vous êtes limité à 10 scans par mois avec un compte gratuit. ::::: @@ -102,17 +102,17 @@ commencer notre analyse :
``` -42sh$ docker scan nemunaire/fic-admin +42sh$ docker scan nemunaire/youp0m -Testing nemunaire/fic-admin... +Testing nemunaire/youp0m... Package manager: apk -Project name: docker-image|nemunaire/fic-admin -Docker image: nemunaire/fic-admin +Project name: docker-image|nemunaire/youp0m +Docker image: nemunaire/youp0m Platform: linux/amd64 -Base image: alpine:3.14.2 +Base image: alpine:3.16.2 -✓ Tested 16 dependencies for known vulnerabilities, no vulnerable paths found. +✓ Tested 15 dependencies for known vulnerabilities, no vulnerable paths found. According to our scan, you are currently using the most secure version of the selected base image @@ -142,9 +142,9 @@ Package manager: deb Project name: docker-image|mysql Docker image: mysql Platform: linux/amd64 -Base image: mysql:8.0.26 +Base image: mysql:8.0.30 -Tested 135 dependencies for known vulnerabilities, found 79 vulnerabilities +Tested 119 dependencies for known vulnerabilities, found 24 vulnerabilities According to our scan, you are currently using the most secure version of the selected base image @@ -152,8 +152,8 @@ the selected base image
Ce dernier exemple est sans appel : `mysql` est une image officielle, et sa -dernière version à l'écriture de ses lignes contient pas moins de 79 -vulnérabilités dont 11 *high*. +dernière version à l'écriture de ses lignes contient pas moins de 24 +vulnérabilités dont 9 *high* (pourtant corrigées dans des versions suivantes). ## Trivy @@ -176,16 +176,16 @@ Tentons à nouveau d'analyser l'image `mysql` :
``` -42sh$ docker run --rm aquasec/trivy mysql -2021-09-22T10:27:46.509Z INFO Need to update DB -2021-09-22T10:27:46.509Z INFO Downloading DB... 100.00% 14.41 MiB p/s 2s -2021-09-22T10:27:56.556Z INFO Detected OS: debian -2021-09-22T10:27:56.556Z INFO Detecting Debian vulnerabilities... -2021-09-22T10:27:56.579Z INFO Number of language-specific files: 0 +42sh$ docker run --rm aquasec/trivy image mysql +INFO Need to update DB +INFO Downloading DB... 100.00% 14.41 MiB p/s 2s +INFO Detected OS: oracle +INFO Detecting Oracle vulnerabilities... +INFO Number of language-specific files: 0 -mysql (debian 10.10) -==================== -Total: 158 (UNKNOWN: 5, LOW: 19, MEDIUM: 64, HIGH: 61, CRITICAL: 9) +mysql (oracle 8.6) +================== +Total: 5 (UNKNOWN: 0, LOW: 0, MEDIUM: 4, HIGH: 1, CRITICAL: 0) ```
@@ -193,24 +193,24 @@ Les résultats sont un peu différents qu'avec `docker scan`, mais on constate que l'image `mysql` contient vraiment de nombreuses vulnérabilités. Même si elles ne sont heureusement pas forcément exploitables directement. -Voyons maintenant s'il y a des différentes avec l'image `nemunaire/fic-admin` : +Voyons maintenant s'il y a des différentes avec l'image `nemunaire/youp0m` :
``` -42sh$ docker run --rm aquasec/trivy nemunaire/fic-admin -2021-09-22T10:29:48.091Z INFO Need to update DB -2021-09-22T10:29:48.091Z INFO Downloading DB... 100.00% 15.98 MiB p/s 1s -2021-09-22T10:29:51.902Z INFO Detected OS: alpine -2021-09-22T10:29:51.902Z INFO Detecting Alpine vulnerabilities... -2021-09-22T10:29:51.903Z INFO Number of language-specific files: 1 -2021-09-22T10:29:51.903Z INFO Detecting gobinary vulnerabilities... +42sh$ docker run --rm aquasec/trivy image nemunaire/youp0m +INFO Need to update DB +INFO Downloading DB... 100.00% 15.98 MiB p/s 1s +INFO Detected OS: alpine +INFO Detecting Alpine vulnerabilities... +INFO Number of language-specific files: 1 +INFO Detecting gobinary vulnerabilities... -nemunaire/fic-admin (alpine 3.14.2) +nemunaire/youp0m (alpine 3.16.2) =================================== Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) -srv/admin (gobinary) +srv/youp0m (gobinary) ==================== Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) ``` @@ -218,7 +218,7 @@ Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) Nous pouvons remarque que Trivy, en plus de faire l'analyse statique des vulnérabilités de l'image, a aussi fait une analyse des dépendances du binaire -`/srv/admin`. +`/srv/youp0m`. Trivy est en effet capable de rechercher des vulnérabilités par rapport aux dépendances connues de certains langages : Python, PHP, Node.js, .NET, Java, diff --git a/tutorial/dockerfiles/dockerfile.md b/tutorial/dockerfiles/dockerfile.md index 75230ac..1c03c42 100644 --- a/tutorial/dockerfiles/dockerfile.md +++ b/tutorial/dockerfiles/dockerfile.md @@ -8,7 +8,7 @@ que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant
```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 . ```
+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 ```
-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 . + +### 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` : + +
+``` +COPY myconfig.conf /etc/nginx/conf.d/my.conf +``` +
+ +Cette instruction permet également de copier l'arborescence d'un dossier : + +
+``` +COPY myconfs/ etc/nginx/conf.d/ +COPY mywebsite /usr/share/nginx/html/ +``` +
+ +::::: {.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 : + +
+``` +COPY docker-entrypoint.d /docker-entrypoint.d +``` +
+ +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` : + +
+``` +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 +``` +
+ +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 : + +
+``` +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 +``` +
+ +::::: + +Les couches partagées sont un gain non négligeable pour l'espace de stockage ! + +Par exemple, prenons le `Dockerfile` suivait : + +
+```Dockerfile +FROM python:3.10 +COPY build /usr/lib/python/grapher +EXPOSE 8080 +RUN pip install pillow pygal +``` +
+ +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 : +[^MAINTAINER]: Voir par exemple :
```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"] ```
-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 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 :\ + ::::: {.exercice} @@ -324,11 +480,16 @@ Pour compiler le projet, vous pouvez utiliser dans votre `Dockerfile`
```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 ```
+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 ? + ::::: diff --git a/tutorial/dockerfiles/entrypoint.md b/tutorial/dockerfiles/entrypoint.md index f75ec13..3e104cf 100644 --- a/tutorial/dockerfiles/entrypoint.md +++ b/tutorial/dockerfiles/entrypoint.md @@ -70,7 +70,7 @@ possibilité de le surcharger au moyen d'un argument : ```
-::::: {.exerice} +::::: {.exercice} ## Personnalisation basique @@ -187,3 +187,23 @@ You are not allowed to perform this request. ::::: + + +## Étendre un `ENTRYPOINT` existant + +Vous venez de réaliser un script d'*entrypoint* pour votre conteneur. Il ajoute +assurément de nombreuses fonctionnalités indispensables. Mais que se passe-t-il +si quelqu'un souhaite étendre votre image, ou simplement pour ajouter une +fonctionnalité ? + +La plupart des images officielles[^cf-nginx] prêtes à l'emploi disposent d'un +dossier `/docker-entrypoint.d`, à la racine de l'image ; et d'un script +d'*entrypoint* qui va se charger d'appeler chacun des scripts du dossier avant +de lancer la commande par défaut. + +[^cf-nginx]: Consultez le dépôt de l'image `nginx` par exemple. Il possède 3 + scripts pour 3 fonctionnalités différentes. + +Chaque fonctionnalité distincte de l'*entrypoint* est placée dans un script +séparé, et quelqu'un qui souhaite ajouter son propre script peut le faire +facilement, soit au moyen d'un volume, soit en étendant l'image. diff --git a/tutorial/dockerfiles/first.md b/tutorial/dockerfiles/first.md index 9a2b0c1..f57c153 100644 --- a/tutorial/dockerfiles/first.md +++ b/tutorial/dockerfiles/first.md @@ -13,7 +13,7 @@ faire fonctionner notre service de monitoring. -## `RUN` ou script ? +## `RUN` ou script ? ### InfluxDB @@ -23,7 +23,7 @@ n'est pas disponible dans les dépôts[^debrepos]. La télécharger le paquet mis à disposition puis à l'installer via `dpkg -i`. [^debrepos]: Le projet met à disposition des dépôts, si vous préférez cette - méthode, consultez la + méthode, consultez-la [documentation d'installation](https://docs.influxdata.com/influxdb/v1.6/introduction/installation/#ubuntu-debian). Deux solutions s'offrent à nous : @@ -33,7 +33,7 @@ Deux solutions s'offrent à nous : préalable de `wget`/`curl`). La copie étant définitive (supprimer le fichier ne le supprimera pas des -couches où il a pu exister), on préférera la seconde méthode, malgré que `wget` +couches où il a pu exister), on préférera la seconde méthode, bien que `wget` restera en déchet. La première méthode aura plus sa place dans un dépôt de projet où les binaires auront été préalablement compilés, il ne restera plus qu'à les copier dans le conteneur au bon emplacement. @@ -48,8 +48,8 @@ d'InfluxDB, qui va l'installer et supprimer le fichier. Il est possible que vous ayez à écraser le fichier de configuration via un `COPY` (ou de manière plus maligne en utilisant `--volume` au moment du `docker -run`, cela ne fonctionne pas qu'avec les dossiers !). Ou peut-être ferez-vous -un `ENTRYPOINT` ? +run`, cela ne fonctionne pas qu'avec les dossiers !). Ou peut-être ferez-vous +un `ENTRYPOINT` ? ### `telegraf` diff --git a/tutorial/dockerfiles/goodpractices.md b/tutorial/dockerfiles/goodpractices.md index d127a9b..3efd6ff 100644 --- a/tutorial/dockerfiles/goodpractices.md +++ b/tutorial/dockerfiles/goodpractices.md @@ -1,5 +1,3 @@ -\newpage - Les bonnes pratiques -------------------- @@ -247,3 +245,28 @@ processus par conteneur. Il est préférable de répartir chaque application dan un conteneur distinct qui n'effectue que le travail pour lequel il est chargé. Les options de liaison entre conteneur sont à votre disposition pour vous aider à cette tâche. + + +## De l'intérêt de faire des images minimales + +À l'inverse de langages comme Javascript, Python, Java et bien +d'autres, le langage Go compile, comme le C, vers du code directement +exécutable par le processeur. Tandis que les langages interprétés ont +besoin de leur interpréteur et de leur environnement d'exécution, les +langages compilés n'ont pas besoin d'être distribués avec leur +compilateur. + +Prenons le temps de regarder les tailles des images : + +
+``` +42sh$ docker image ls -f reference=golang -f reference=youp0m +REPOSITORY TAG IMAGE ID CREATED SIZE +golang 1-alpine 155ead2e66ca 3 months ago 328MB +nemunaire/youp0m latest 2c06880e48aa 2 days ago 25MB +``` +
+ +L'image contenant le compilateur Go est bien plus lourde que l'image +minimale que l'on a construite avec le binaire compilé. C'est autant +d'espace et de performances gagnées. diff --git a/tutorial/dockerfiles/image-inheritance.png b/tutorial/dockerfiles/image-inheritance.png new file mode 100644 index 0000000..c63d8a5 Binary files /dev/null and b/tutorial/dockerfiles/image-inheritance.png differ diff --git a/tutorial/dockerfiles/interactive.md b/tutorial/dockerfiles/interactive.md index d226e0c..5f99af0 100644 --- a/tutorial/dockerfiles/interactive.md +++ b/tutorial/dockerfiles/interactive.md @@ -59,18 +59,86 @@ avec la commande `commit` :
```bash -docker container commit CONTAINER my_nano +42sh$ docker ps +CONTAINER ID IMAGE COMMAND STATUS NAMES +91d17871d730 ubuntu "bash" Exited (0) musing_tu + +docker container commit 91d17871d730 my_nano ```
-en remplaçant `CONTAINER` par le nom ou l'identifiant du container qui +en remplaçant `91d17871d730` par le nom ou l'identifiant du container qui doit servir de modèle. `my_nano` est le nom que vous voudrez utiliser à la place d'`ubuntu`. +L'action de *commit*, malgré le fait qu'elle crée une nouvelle image est très +rapide : il se trouve que seules les différences avec l'image parente sont +packagées. Les images sont en fait composées de couches : empilant les +différences depuis le système de base ! -Cette action va figer la couche la plus haute de systèmes de fichiers, qui -était jusqu'alors en lecture-écriture pour le conteneur ; afin d'en faire la -dernière couche de notre nouvelle image. + +### À propos des couches + +Revenons quelque-peu en arrière : lorsque nous avons fait notre premier `docker +run hello-world`, rappelez-vous, Docker a téléchargé l'image en nous affichant +la progression, juste avant de lancer le conteneur. + +Analysons ensemble ces quelques lignes pour mieux comprendre de quoi les images +se composent. Nous allons pour cela utiliser la commande `pull` pour récupérer +une nouvelle image : + +
+```bash +42sh$ docker image pull python:3 +3: Pulling from library/python +23858da423a6: Pull complete +326f452ade5c: Pull complete +a42821cd14fb: Pull complete +8471b75885ef: Pull complete +8ffa7aaef404: Pull complete +15132af73342: Pull complete +aaf3b07565c2: Pull complete +736f7bc16867: Pull complete +94da21e53a5b: Pull complete +Digest: sha256:e9c35537103a2801a30b15a77d4a56b35532c964489b125ec1ff24f3d5b53409 +Status: Downloaded newer image for python:3 +docker.io/library/python:3 +``` +
+ +On remarque que plusieurs téléchargement ont lieu, chacun associé à un +identifiant particulier. Une image est généralement découpée en plusieurs +éléments. On parle en fait de *couches* puisqu'on les empile, dans un ordre +précis. + +Les couches sont une astuce formidable pour optimiser tant le téléchargement, +l'espace de stockage nécessaire au cache d'images, que la création des +conteneurs. De nombreux conteneurs vont utiliser les mêmes images de base : +`debian`, `ubuntu`, `alpine`, ... il serait futile de systématiquement +récupérer et stocker autant de systèmes de fichiers de base que d'images. Avec +les couches, si deux images partagent la même version du système de fichiers de +base, il ne sera téléchargé qu'une seule fois. On pourait le schématiser ainsi : + +![L'héritage des principales images officielles](image-inheritance.png) + +Dans les faits, cela va même encore plus loin car Docker crée de nombreuses +couches intermédiaires, chacune peut être l'occasion d'une bifurcation.\ + +Chaque couche est en fait un différentiel des dossiers et fichiers qui sont +ajoutés, modifiés ou supprimés par rapport à la couche précédente. + +::::: {.question} + +#### Comment supprimer les couches d'images que je n'utilise plus ? {-} + +Docker gère lui-même les couches, vous n'avez pas à vous en préoccuper. Si une +image est mise à jour ou supprimée, toutes les couches rendues inutiles seront +automatiquement supprimées. + +::::: + +Revenons au *commit* que nous avons fait précédemment. Nous avons ajouté `nano` +par-dessus une image `ubuntu`. Naturellement, voici ce qu'il s'est passé : ![`docker commit`](commit.png) @@ -82,8 +150,13 @@ docker container run -it my_nano /bin/bash ``` -Vous constatez cette fois que vous pouvez lancer `nano`, alors que vous ne -pouvez toujours pas le faire dans un conteneur issu d'une image `ubuntu` ! +Vous constatez cette fois que vous pouvez lancer `nano` ! + +Nous avons donc créé une couche, elle contient juste le différentiel des +fichiers ajoutés, à savoir le binaire `nano` et sa configuration par défaut +(mais aussi le cache du gestionnaire de paquets `apt`). + +Voyons maintenant comment automatiser cela. ### Scripté ? diff --git a/tutorial/dockerfiles/others.md b/tutorial/dockerfiles/others.md index 682eed9..680e5f2 100644 --- a/tutorial/dockerfiles/others.md +++ b/tutorial/dockerfiles/others.md @@ -3,7 +3,7 @@ D'autres méthodes pour créer des images Les images utilisées par Docker pour lancer les conteneurs répondent avant tout aux spécifications OCI. Le format étant standard, il est normal que d'autres -outils puissent utiliser mais aussi créer des images. Nous allons voir dans +outils puissent utiliser, mais aussi créer des images. Nous allons voir dans cette partie l'avenir des `Dockerfile` ou simplement d'autres outils plus spécifiques. @@ -30,7 +30,7 @@ vous pouvez l'installer comme ceci :
``` -V="v0.6.3" +V="v0.9.1" mkdir -p ~/.docker/cli-plugins curl -L -s -S -o ~/.docker/cli-plugins/docker-buildx \ https://github.com/docker/buildx/releases/download/$V/buildx-$V.linux-amd64 @@ -63,12 +63,12 @@ Actions* :\ Parfois on peut se sentir un peu frustré par la syntaxe des `Dockerfile` ou par son manque d'évolutivité. Avec BuildKit, il est possible de préciser un parseur à utiliser pour l'évaluation de la syntaxe du `Dockerfile`. Les parseurs -(*frontend* dans la documentation en anglais) sont des images Docker, on -indique leur nom dans un commentaire au tout début du fichier : +(*frontend* dans la documentation anglaise) sont des images Docker : on indique +leur nom dans un commentaire au tout début du fichier :
```dockerfile -# syntax=docker/dockerfile:1.2 +# syntax=docker/dockerfile:1.4 FROM ubuntu RUN apt-get update && apt-get install gimp ``` @@ -105,11 +105,10 @@ notamment : `Dockerfile`, et autres scripts de CI et de tests. -#### `docker/dockerfile:1.3` +#### `docker/dockerfile:1.4` La version habituelle de la syntaxe des `Dockerfile` est la version 1.1. En -utilisant BuildKit, nous pouvons dès à présent passer à la version 1.2 (stable) -ou 1.3 (expérimentale). +utilisant BuildKit, nous pouvons dès à présent passer à la version 1.4. Les ajouts par rapport à la syntaxe usuelle sont répertoriés sur cette page :\ . @@ -128,7 +127,9 @@ Il est aussi possible de se passer complètement de Docker. La plupart des outils qui sont capables de générer des images de machines virtuelles, sont aussi capables de générer des images Docker. Citons notamment : -- [Hashicorp Packer](https://www.packer.io/docs/builders/docker), -- [Nix et Guix](https://nix.dev/tutorials/building-and-running-docker-images), -- [Kubler](https://github.com/edannenberg/kubler), +- Buildah : (utilisé par `podman`), +- Buildpacks : , +- Hashicorp Packer : , +- Nix et Guix : , +- Kubler : , - et bien d'autres.