J'ai lu que Docker fonctionne avec des calques, donc lors de la création d'un container
avec un Dockerfile
, vous commencez avec l'image de base, puis les commandes suivantes exécutent ajouter un calque au conteneur, donc si vous enregistrez le état de ce nouveau conteneur, vous avez une nouvelle image. Il y a deux ou trois choses que je me demande à ce sujet.
Si je pars d'une image Ubuntu
, qui est assez grande et encombrante car c'est un système d'exploitation complet, je lui ajoute quelques outils et l'enregistre en tant que nouvelle image que je télécharge sur le hub. Si quelqu'un télécharge mon image et qu'il a déjà une image Ubuntu enregistrée dans son images folder
, cela signifie-t-il qu'ils peuvent ignorer le téléchargement Ubuntu
car ils ont déjà l'image? Si tel est le cas, comment cela fonctionne-t-il lorsque je modifie des parties de l'image d'origine, Docker utilise-t-il ses données mises en cache pour appliquer ces modifications de manière sélective au Ubuntu image
après l'avoir chargé?
2.) Comment mettre à jour une image que j'ai créée en modifiant le Dockerfile? J'ai installé un simple projet Django avec ce Dockerfile
:
FROM python:3.5
ENV PYTHONBUFFERED 1
ENV APPLICATION_ROOT /app
ENV APP_ENVIRONMENT L
RUN mkdir -p $APPLICATION_ROOT
WORKDIR $APPLICATION_ROOT
ADD requirements.txt $APPLICATION_ROOT
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ADD . $APPLICATION_ROOT
et utilisé cela pour créer l'image au début. Donc, chaque fois que je crée une boîte, elle charge tous ces environment variables
, si je reconstruis complètement la boîte, elle réinstalle les packages et tous les extras. Je dois ajouter une nouvelle variable d'environnement, donc je l'ai ajoutée au bas de Dockerfile
, avec une variable de test:
ENV COMPOSE_CONVERT_WINDOWS_PATHS 1
ENV TEST_ENV_VAR TEST
Lorsque je supprime le conteneur et l'image et que je crée un nouveau conteneur, tout semble aller en conséquence, il me dit qu'il crée le nouveau Step 4: ENV
COMPOSE_CONVERT_WINDOWS_PATHS 1
---> Running in 75551ea311b2
---> b25b60e29f18
Removing intermediate container 75551ea311b2
Donc, c'est comme si quelque chose se perd dans certaines de ces transitions de conteneurs intermédiaires. Est-ce ainsi que fonctionne le système de mise en cache, chaque nouvelle couche est un intermediate container
? Donc, avec cela à l'esprit, comment ajouter une nouvelle couche, devez-vous toujours ajouter les nouvelles données au bas du Dockerfile? Ou serait-il préférable de laisser le Dockerfile seul une fois l'image construite, et de simplement modifier le container
et de construire une nouvelle image?
EDIT Je viens d'essayer d'installer une image, un paquet appelé bwawrik/bioinformatics
, qui est un conteneur basé sur CentOS sur lequel une large gamme d'outils est installée.
Il a gelé à mi-chemin, alors je l'ai quitté, puis je l'ai exécuté à nouveau pour voir si tout était installé:
$ docker pull bwawrik/bioinformatics
Using default tag: latest
latest: Pulling from bwawrik/bioinformatics
a3ed95caeb02: Already exists
a3ed95caeb02: Already exists
7e78dbe53fdd: Already exists
ebcc98113eaa: Already exists
598d3c8fd678: Already exists
12520d1e1960: Already exists
9b4912d2bc7b: Already exists
c64f941884ae: Already exists
24371a4298bf: Already exists
993de48846f3: Already exists
2231b3c00b9e: Already exists
2d67c793630d: Already exists
d43673e70e8e: Already exists
fe4f50dda611: Already exists
33300f752b24: Already exists
b4eec31201d8: Already exists
f34092f697e8: Already exists
e49521d8fb4f: Already exists
8349c93680fe: Already exists
929d44a7a5a1: Already exists
09a30957f0fb: Already exists
4611e742e0b5: Already exists
25aacf0148db: Already exists
74da82504b6c: Already exists
3e0aac083b86: Already exists
f52c7e0ac000: Already exists
35eee92aaf2f: Already exists
5f6d8eb70885: Already exists
536920bfe266: Already exists
98638e678c51: Already exists
9123956b991d: Already exists
1c4c8a29cd65: Already exists
1804bf352a97: Already exists
aa6fe9359956: Already exists
e7e38d1250a9: Already exists
05e935c831dc: Already exists
b7dfc22c26f3: Already exists
1514d4797ffd: Already exists
Digest: sha256:0391808e21b7b5cc0eb44fc2dad0d7f5415115bdaafb4534c0b6a12efd47a88b
Status: Image is up to date for bwawrik/bioinformatics:latest
Il a donc définitivement installé le package en plusieurs parties, pas toutes en une seule fois. Ces pièces sont-elles, des images différentes?
Tout d'abord, permettez-moi de clarifier une terminologie.
image: Un objet statique, immuable. C'est ce que vous créez lorsque vous exécutez docker build
en utilisant un Dockerfile
. Une image n'est pas une chose qui tourne.
Les images sont composées de couches. une image peut avoir un seul calque ou plusieurs calques.
container: Une chose en marche. Il utilise une image comme modèle de départ.
Ceci est similaire à un programme binaire et à un processus. Vous disposez d'un programme binaire sur le disque (tel que /bin/sh
), et lorsque vous l'exécutez, c'est un processus sur votre système. Ceci est similaire à la relation entre les images et les conteneurs.
Vous pouvez créer votre propre image à partir d'une image de base (telle que ubuntu
dans votre exemple). Certaines commandes de votre Dockerfile
créeront un nouveau calque dans l'image ultime. Certains d'entre eux sont RUN
, COPY
et ADD
.
Le tout premier calque n'a pas de calque parent. Mais chaque autre couche aura une couche parent. De cette façon, ils se lient les uns aux autres, s'empilant comme des crêpes.
Chaque couche a un ID unique (les longs hachages hexadécimaux que vous avez déjà vus). Ils peuvent également avoir des noms conviviaux, appelés balises (par exemple ubuntu:16.04
).
Techniquement, chaque calque est aussi une image. Si vous créez une nouvelle image et qu'elle a 5 couches, vous pouvez utiliser cette image et elle contiendra les 5 couches. Si vous exécutez un conteneur en utilisant le troisième calque de la pile comme ID d'image, vous pouvez le faire également - mais il ne contiendrait que 3 calques. Celui que vous spécifiez et les deux qui sont ses ancêtres.
Mais par convention, le terme "image" désigne généralement la couche à laquelle est associée une étiquette. Lorsque vous exécutez docker images
, il vous montrera toutes les images de niveau supérieur et masquera les calques en dessous (mais vous pouvez toutes les afficher avec -a
).
Quand docker build
s'exécute, il fait tout son travail à l'intérieur des conteneurs (naturellement!) Donc, s'il rencontre une étape RUN
, il créera un conteneur à partir de la couche supérieure actuelle, y exécutera les commandes spécifiées, puis enregistrez le résultat en tant que nouveau calque. Ensuite, il créera un conteneur à partir de cette nouvelle couche, exécutera la prochaine chose ... etc.
Les conteneurs intermédiaires sont uniquement utilisés pour le processus de génération et sont supprimés après la génération.
Vous avez demandé si quelqu'un qui télécharge votre image basée sur ubuntu
ne fait qu'un téléchargement partiel, s'il avait déjà l'image ubuntu
localement.
Oui! C'est exactement ça.
Chaque couche utilise la couche en dessous comme base. Le nouveau calque est essentiellement un diff entre ce calque et un nouvel état. Ce n'est pas un diff de la même manière qu'un git commit pourrait fonctionner. Cela fonctionne au niveau du fichier, pas au niveau de la ligne.
Disons que vous avez commencé à partir de ubuntu
et que vous avez exécuté ce Dockerfile.
FROM: ubuntu:16.04
RUN groupadd dan && useradd -g dan dan
Il en résulterait une image à deux couches. La première couche serait l'image ubuntu
. La seconde n'aurait probablement qu'une poignée de changements.
/etc/passwd
avec l'utilisateur "dan"/etc/group
avec le groupe "dan"/home/dan
/home/dan/.bashrc
Et c'est tout. Si vous démarrez un conteneur à partir de cette image, ces quelques fichiers se trouveraient dans la couche supérieure et tout le reste proviendrait du système de fichiers dans l'image ubuntu
.
Un autre point. Lorsque vous exécutez un conteneur, vous pouvez écrire des fichiers dans le système de fichiers. Mais si vous arrêtez le conteneur et exécutez un autre conteneur à partir de la même image, tout est réinitialisé. Alors, où sont écrits les fichiers?
Les images sont immuables, donc une fois qu'elles existent, elles ne peuvent pas être modifiées. Vous pouvez créer une nouvelle version, mais c'est une nouvelle image. Il aurait un ID différent et ne serait pas la même image.
Un conteneur possède une couche de lecture-écriture de niveau supérieur qui est placée au-dessus des couches d'image. Toutes les écritures se produisent dans cette couche. Cela fonctionne comme les autres couches. Si vous devez modifier un fichier (ou en ajouter un ou en supprimer un), cela se fait dans la couche supérieure et n'affecte pas les couches inférieures. Si le fichier existe déjà, il est copié dans la couche lecture-écriture, puis modifié. C'est ce que l'on appelle la copie sur écriture (CoW).
Devez-vous ajouter de nouvelles choses au bas de Dockerfile? Non, vous pouvez ajouter n'importe quoi n'importe où (ou changer quoi que ce soit).
Cependant, la façon dont vous faites les choses affecte votre temps de génération en raison du fonctionnement de la mise en cache de la génération.
Docker essaiera de mettre en cache les résultats lors des builds. S'il constate en lisant via Dockerfile que le FROM
est le même, le premier RUN
est le même, le second RUN
est le même ... il le supposera a déjà effectué ces étapes et utilisera les résultats mis en cache. S'il rencontre quelque chose de différent de la dernière version, il invalidera le cache. Tout à partir de ce moment sera relancé.
Certaines choses invalideront toujours le cache. Par exemple, si vous utilisez ADD
ou COPY
, ceux-ci invalident toujours le cache. En effet, Docker ne fait que suivre les commandes de génération. Il n'essaie pas de comprendre "est-ce que cette version du fichier que je copie est la même que la dernière fois?"
C'est donc une pratique courante de commencer par FROM
, puis de mettre des choses très statiques comme les commandes RUN
qui installent des packages avec par exemple apt-get
, etc. Ces choses ne changent généralement pas beaucoup après l'écriture initiale de votre Dockerfile. Plus loin dans le fichier est un endroit plus pratique pour mettre des choses qui changent plus souvent.
Il est difficile de donner de bons conseils concis à ce sujet, car cela dépend vraiment du projet en question. Mais il est utile d'apprendre comment fonctionne la mise en cache de la génération et d'essayer d'en tirer parti.