Une image Docker est en fait une liste liée de couches de système de fichiers. Chaque instruction dans un Dockerfile crée une couche de système de fichiers qui décrit les différences dans le système de fichiers avant et après l'exécution de l'instruction correspondante. La docker inspect
sous-commande peut être utilisée sur une image Docker pour révéler sa nature d'être une liste liée de couches de système de fichiers.
Le nombre de couches utilisées dans une image est important
- lorsque vous poussez ou tirez des images, car cela affecte le nombre de téléchargements simultanés ou de téléchargements en cours.
- lors du démarrage d'un conteneur, car les couches sont combinées ensemble pour produire le système de fichiers utilisé dans le conteneur; plus il y a de couches, plus les performances sont mauvaises, mais les différents backends du système de fichiers en sont affectés différemment.
Cela a plusieurs conséquences sur la façon dont les images doivent être construites. Le premier et le plus important conseil que je puisse donner est:
Conseil # 1 Assurez-vous que les étapes de génération où votre code source est impliqué arrivent aussi tard que possible dans le Dockerfile et ne sont pas liées aux commandes précédentes à l'aide de a &&
ou de a ;
.
La raison en est que toutes les étapes précédentes seront mises en cache et que les couches correspondantes n'auront pas besoin d'être téléchargées encore et encore. Cela signifie des versions plus rapides et des versions plus rapides, ce qui est probablement ce que vous voulez. Chose intéressante, il est étonnamment difficile de faire un usage optimal du cache docker.
Mon deuxième conseil est moins important mais je le trouve très utile du point de vue de la maintenance:
Conseil n ° 2 N'écrivez pas de commandes complexes dans le Dockerfile mais utilisez plutôt des scripts qui doivent être copiés et exécutés.
Un Dockerfile suivant ce conseil ressemblerait à
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh
etc. L'avis de lier plusieurs commandes avec &&
n'a qu'une portée limitée. Il est beaucoup plus facile d'écrire avec des scripts, où vous pouvez utiliser des fonctions, etc. pour éviter la redondance ou à des fins de documentation.
Les personnes intéressées par les pré-processeurs et désireuses d'éviter les petits frais généraux causés par les COPY
étapes et génèrent en fait à la volée un Dockerfile où le
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
les séquences sont remplacées par
RUN base64 --decode … | sh -x
où …
est la version encodée en base64 de apt_setup.sh
.
Mon troisième conseil s'adresse aux personnes qui souhaitent limiter la taille et le nombre de couches au prix possible de constructions plus longues.
Conseil # 3 Utilisez le with
-idiom pour éviter les fichiers présents dans les couches intermédiaires mais pas dans le système de fichiers résultant.
Un fichier ajouté par une instruction docker et supprimé par une instruction ultérieure n'est pas présent dans le système de fichiers résultant mais il est mentionné deux fois dans les couches docker constituant l'image docker en construction. Une fois, avec le nom et le contenu complet dans la couche résultant de l'instruction qui l'ajoute, et une fois comme avis de suppression dans la couche résultant de la suppression de l'instruction.
Par exemple, supposons que nous ayons temporairement besoin d'un compilateur C et d'une image et considérons le
# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc
(Un exemple plus réaliste consisterait à construire des logiciels avec le compilateur au lieu de simplement affirmer sa présence avec l' --version
indicateur.)
L'extrait Dockerfile crée trois couches, la première contient la suite complète de gcc de sorte que même si elle n'est pas présente dans le système de fichiers final, les données correspondantes font toujours partie de l'image de la même manière et doivent être téléchargées, téléchargées et décompressées chaque fois que le l'image finale est.
Le with
-idiom est une forme courante dans la programmation fonctionnelle pour isoler la propriété des ressources et la libération des ressources de la logique l'utilisant. Il est facile de transposer cet idiome en script shell, et nous pouvons reformuler les commandes précédentes comme le script suivant, à utiliser avec COPY & RUN
comme dans le conseil # 2.
# with_c_compiler SIMPLE-COMMAND
# Execute SIMPLE-COMMAND in a sub-shell with gcc being available.
with_c_compiler()
(
set -e
apt-get install -y gcc
"$@"
trap 'apt-get --purge autoremove -y gcc' EXIT
)
with_c_compiler\
gcc --version
Les commandes complexes peuvent être transformées en fonction afin de pouvoir être introduites dans le with_c_compiler
. Il est également possible de chaîner les appels de plusieurs with_whatever
fonctions, mais ce n'est peut-être pas très souhaitable. (En utilisant des fonctionnalités plus ésotériques du shell, il est certainement possible de faire with_c_compiler
accepter des commandes complexes, mais il est préférable à tous égards d'envelopper ces commandes complexes dans des fonctions.)
Si nous voulons ignorer le conseil n ° 2, l'extrait de Dockerfile résultant serait
RUN apt-get install -y gcc\
&& gcc --version\
&& apt-get --purge autoremove -y gcc
ce qui n'est pas si facile à lire et à entretenir à cause de l'obscurcissement. Voyez comment la variante shell-script met l'accent sur la partie importante gcc --version
tandis que la &&
variante enchaînée enterre cette partie au milieu du bruit.