web-dev-qa-db-fra.com

RUN multiple vs RUN simple chaîne dans Dockerfile, quoi de mieux?

Dockerfile.1 exécute plusieurs RUN:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 les rejoint:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

Chaque RUN crée un calque. J'ai donc toujours supposé que moins de calques était préférable, donc Dockerfile.2 était préférable.

Ceci est évidemment vrai lorsqu'un RUN supprime quelque chose ajouté par un précédent RUN (c'est-à-dire yum install nano && yum clean all), mais dans les cas où chaque RUN ajoute quelque chose, il y a quelques points besoin de considérer:

  1. Les calques sont supposés simplement ajouter un diff au-dessus du précédent, donc si le dernier calque ne supprime pas quelque chose ajouté dans un précédent, il ne devrait pas y avoir d’avantage à économiser l’espace disque entre les deux méthodes ...

  2. Les couches sont tirées en parallèle à partir de Docker Hub. Ainsi, Dockerfile.1, bien que probablement légèrement plus grand, serait théoriquement téléchargé plus rapidement.

  3. Si vous ajoutez une quatrième phrase (c'est-à-dire echo This is the D > d) et vous reconstruisez localement, Dockerfile.1 se construit plus rapidement grâce au cache, mais Dockerfile.2 devra exécuter à nouveau les 4 commandes.

Donc, la question: Quel est le meilleur moyen de créer un fichier Dockerfile?

88
Yajo

Lorsque cela est possible, je fusionne toujours les commandes qui créent des fichiers avec des commandes qui suppriment ces mêmes fichiers sur une seule ligne RUN. En effet, chaque ligne RUN ajoute un calque à l’image. La sortie représente littéralement les modifications du système de fichiers que vous pouvez afficher avec docker diff sur le conteneur temporaire créé. Si vous supprimez un fichier créé dans une autre couche, le système de fichiers de l'union enregistrera la modification du système de fichiers dans une nouvelle couche. Le fichier existe toujours dans la couche précédente et est expédié via le réseau et stocké sur le disque. Donc, si vous téléchargez le code source, extrayez-le, compilez-le en binaire, puis supprimez les fichiers source et tgz à la fin, vous voulez vraiment que tout cela soit fait dans un seul calque pour réduire la taille de l'image.

Ensuite, j'ai personnellement divisé les couches en fonction de leur potentiel de réutilisation dans d'autres images et de l'utilisation attendue de la mise en cache. Si j'ai 4 images, toutes avec la même image de base (par exemple, Debian), je peux extraire une collection d'utilitaires communs à la plupart de ces images dans la première commande d'exécution afin que les autres images bénéficient de la mise en cache.

L'ordre dans le fichier Docker est important pour la réutilisation du cache d'images. J'examine tous les composants qui ne seront mis à jour que très rarement, peut-être uniquement lorsque l'image de base sera mise à jour et sera placée dans le fichier Dockerfile. Vers la fin du fichier Dockerfile, j'inclue toutes les commandes qui s'exécutent rapidement et peuvent changer fréquemment, par exemple. ajouter un utilisateur avec un UID spécifique à l'hôte ou créer des dossiers et modifier les autorisations. Si le conteneur comprend du code interprété (par exemple, JavaScript) en cours de développement, il est ajouté le plus tard possible, de sorte qu'une reconstruction n'exécute que cette modification.

Dans chacun de ces groupes de modifications, je consolide du mieux que je peux pour minimiser les couches. Donc, s'il y a 4 dossiers de code source différents, ceux-ci sont placés dans un seul dossier afin qu'il puisse être ajouté avec une seule commande. Toutes les installations installées à partir de quelque chose comme apt-get sont fusionnées en une seule exécution lorsque cela est possible, afin de minimiser la charge de traitement du gestionnaire de paquets (mise à jour et nettoyage).


Mise à jour pour les versions à plusieurs étages:

Je m'inquiète beaucoup moins de la réduction de la taille de l'image au cours des étapes non finales d'une construction en plusieurs étapes. Lorsque ces étapes ne sont pas étiquetées et expédiées à d'autres nœuds, vous pouvez maximiser les chances de réutilisation du cache en scindant chaque commande en une ligne RUN distincte.

Cependant, ce n'est pas une solution parfaite pour réduire les couches car toutes les étapes que vous copiez sont des fichiers, et non le reste des métadonnées de l'image, telles que les paramètres de variable d'environnement, le point d'entrée et la commande. Et lorsque vous installez des packages dans une distribution Linux, les bibliothèques et autres dépendances peuvent être dispersées dans le système de fichiers, ce qui rend difficile la copie de toutes les dépendances.

Pour cette raison, j'utilise des versions à plusieurs étapes en remplacement de la création de fichiers binaires sur un serveur CI/CD, de sorte que mon serveur CI/CD ne doit disposer que de l'outillage nécessaire à l'exécution de docker build, et non d'un jdk. nodejs, go et tout autre outil de compilation installé.

65
BMitch

Réponses officielles répertoriées dans leurs meilleures pratiques (les images officielles DOIVENT y adhérer)

Minimiser le nombre de couche

Vous devez trouver un équilibre entre la lisibilité (et donc la maintenabilité à long terme) du fichier Docker et la réduction du nombre de couches qu’il utilise. Soyez stratégique et prudent quant au nombre de couches que vous utilisez.

Depuis le menu 1.10, les instructions COPY, ADD et RUN ajoutent un nouveau calque à votre image. Soyez prudent lorsque vous utilisez ces déclarations. Essayez de combiner des commandes en une seule instruction RUN. Séparez-le uniquement si cela est nécessaire pour la lisibilité.

Plus d'infos: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize- le-nombre-de-joueurs

Mise à jour: Multi-étapes dans le menu fixe> 17.05

Avec les versions à plusieurs étapes, vous pouvez utiliser plusieurs instructions FROM dans votre fichier Docker. Chaque instruction FROM est une étape et peut avoir sa propre image de base. Lors de la dernière étape, vous utilisez une image de base minimale telle qu'Alpine, copiez les artefacts de construction des étapes précédentes et installez les conditions requises pour l'exécution. Le résultat final de cette étape est votre image. Vous vous inquiétez donc des couches décrites précédemment.

Comme d'habitude, docker a great docs sur des builds à plusieurs étages. Voici un extrait rapide:

Avec les versions en plusieurs étapes, vous utilisez plusieurs instructions FROM dans votre fichier Docker. Chaque instruction FROM peut utiliser une base différente et chacune d'entre elles commence une nouvelle étape de la construction. Vous pouvez copier de manière sélective des artefacts d’un stade à un autre en laissant derrière vous tout ce que vous ne voulez pas dans l’image finale.

Vous trouverez un excellent article de blog à ce sujet ici: https://blog.alexellis.io/mutli-stage-docker-builds/

Pour répondre à vos points:

  1. Oui, les calques sont un peu comme des diffs. Je ne pense pas qu'il y ait des couches ajoutées s'il n'y a absolument aucun changement. Le problème est qu’une fois que vous installez/téléchargez quelque chose dans la couche 2, vous ne pouvez plus le supprimer dans la couche 3. Donc, une fois que quelque chose est écrit dans un calque, la taille de l'image ne peut plus être diminuée en l'enlevant.

  2. Bien que les couches puissent être tirées en parallèle, ce qui rend potentiellement plus rapide, chaque couche augmente sans aucun doute la taille de l'image, même si elles suppriment des fichiers.

  3. Oui, la mise en cache est utile si vous mettez à jour votre fichier docker. Mais cela fonctionne dans un sens. Si vous avez 10 couches et que vous modifiez la couche 6, vous devrez tout reconstruire à partir de la couche 6 à 10. Il n’est donc pas très fréquent que cela accélère le processus de création, mais il est garanti que la taille de votre image augmentera inutilement.


Merci à @ Mohan de m'avoir rappelé de mettre à jour cette réponse.

22
Menzo Wijmenga

Il semble que les réponses ci-dessus sont obsolètes. La note de la documentation:

Avant Docker 17.05, et plus encore avant Docker 1.10, il était important de minimiser le nombre de calques dans votre image. Les améliorations suivantes ont atténué ce besoin:

[...]

Docker 17.05 et versions ultérieures prennent en charge les constructions à plusieurs étapes, qui vous permettent de copier uniquement les artefacts dont vous avez besoin dans l'image finale. Cela vous permet d'inclure des outils et des informations de débogage dans vos étapes de construction intermédiaires sans augmenter la taille de l'image finale.

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-le-number-of-layers

et

Notez que cet exemple comprime aussi artificiellement deux commandes RUN à l'aide de l'opérateur Bash &&, afin d'éviter de créer un calque supplémentaire dans l'image. Ceci est sujet aux échecs et difficile à maintenir.

https://docs.docker.com/engine/userguide/eng-image/multistage- construction/

Les meilleures pratiques semblent avoir évolué pour utiliser des versions à plusieurs étages et garder le Dockerfile lisible.

13
Mohan

Cela dépend de ce que vous incluez dans vos calques d'image.

Le point clé est de partager autant de couches que possible:

Mauvais exemple:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Bon exemple:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Une autre suggestion est que la suppression n’est pas aussi utile que si elle se produit sur le même calque que l’action d’ajout/installation.

2
xdays