web-dev-qa-db-fra.com

Comment monter des volumes d'hôtes dans des conteneurs de menu fixe dans Dockerfile pendant la construction

Question originale: Comment utiliser l'instruction VOLUME dans Dockerfile?

La question que je souhaite résoudre est la suivante: comment monter des volumes d’hôte dans des conteneurs Docker dans Dockerfile lors de la construction, c’est-à-dire avoir la capacité docker run -v /export:/export pendant docker build.

La raison derrière cela, pour moi, est que lorsque je construis des choses dans Docker, je ne veux pas que ces caches (apt-get install) soient verrouillés dans un seul docker, mais que je les partage/les réutilisent. C'est la raison principale pour laquelle je pose cette question.

Dernière mise à jour:

Avant docker v18.09, la réponse correcte devrait être celle qui commence par:

Il existe un moyen de monter un volume lors d'une construction, mais cela n'implique pas Dockerfiles.

Cependant, cette réponse était mal énoncée, organisée et appuyée. Lors de la réinstallation de mon menu fixe, je suis tombé par hasard sur l'article suivant:

dockérisez un service apt-cacher-ng
https://docs.docker.com/engine/examples/apt-cacher-ng/

C'est la solution du docker à cette/ma question, pas directement mais indirectement. C'est la manière orthodoxe que docker nous suggère de faire. Et j'avoue que c'est mieux que celui que j'essayais de demander ici.

Une autre façon de procéder consiste à utiliser la réponse (---) (- === -) , par exemple, dans le Buildkit de la version 18.09.

Choisissez celui qui vous convient le mieux.


Était: Il y avait eu une solution - le rocker, qui n'était pas de Docker, mais maintenant que le rocker est interrompu, je reviens à la réponse "Pas possible" à nouveau.


Ancienne mise à jour: La réponse est donc "Impossible". Je peux l’accepter comme une réponse car je sais que la question a été longuement discutée à l’adresse https://github.com/docker/docker/issues/3156 . Je peux comprendre que la portabilité est une question primordiale pour les développeurs de docker; mais en tant qu'utilisateur de docker, je dois dire que je suis très déçu de cette fonctionnalité manquante. Laissez-moi conclure mon propos par une citation de la discussion susmentionnée: ", j'aimerais utiliser Gentoo comme image de base, mais je ne souhaite absolument pas que> 1 Go de données d'arborescence de Portage soient dans l'une des couches une fois. l’image a été construite. Vous pourriez avoir de jolis conteneurs compacts s’il n’y avait pas d’arbre de portage gigantesque devant apparaître dans l’image lors de l’installation. "Oui, je peux utiliser wget ou curl pour télécharger Tout ce dont j’ai besoin, mais le fait qu’un simple problème de portabilité m’oblige à télécharger> 1 Go d’arbre Portage chaque fois que je construis une image de base Gentoo n’est ni efficace ni convivial. De plus, le dépôt de paquets sera TOUJOURS sous/usr/portage, donc TOUJOURS PORTABLE sous Gentoo. Encore une fois, je respecte la décision, mais permettez-moi d’exprimer ma déception également. Merci.


Question originale en détail:

De

Partager des répertoires via des volumes
http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/

il indique que la fonctionnalité de volumes de données "est disponible depuis la version 1 de l'API Docker Remote". Mon menu fixe est de la version 1.2.0, mais j'ai trouvé que l'exemple donné dans l'article ci-dessus ne fonctionnait pas:

# BUILD-USING:        docker build -t data .
# RUN-USING:          docker run -name DATA data
FROM          busybox
VOLUME        ["/var/volume1", "/var/volume2"]
CMD           ["/usr/bin/true"]

Quelle est la bonne manière dans Dockerfile de monter des volumes montés sur l'hôte dans des conteneurs de menu fixe, via la commande VOLUME?

$ apt-cache policy lxc-docker
lxc-docker:
  Installed: 1.2.0
  Candidate: 1.2.0
  Version table:
 *** 1.2.0 0
        500 https://get.docker.io/ubuntu/ docker/main AMD64 Packages
        100 /var/lib/dpkg/status

$ cat Dockerfile 
FROM          debian:sid

VOLUME        ["/export"]
RUN ls -l /export
CMD ls -l /export

$ docker build -t data .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon 
Step 0 : FROM          debian:sid
 ---> 77e97a48ce6a
Step 1 : VOLUME        ["/export"]
 ---> Using cache
 ---> 59b69b65a074
Step 2 : RUN ls -l /export
 ---> Running in df43c78d74be
total 0
 ---> 9d29a6eb263f
Removing intermediate container df43c78d74be
Step 3 : CMD ls -l /export
 ---> Running in 8e4916d3e390
 ---> d6e7e1c52551
Removing intermediate container 8e4916d3e390
Successfully built d6e7e1c52551

$ docker run data
total 0

$ ls -l /export | wc 
     20     162    1131

$ docker -v
Docker version 1.2.0, build fa7b24f
205
xpt

Tout d'abord, pour répondre "pourquoi VOLUME ne fonctionne-t-il pas?" Lorsque vous définissez un VOLUME dans le fichier Docker, vous ne pouvez définir que la cible, pas la source du volume. Pendant la construction, vous obtiendrez uniquement un volume anonyme. Ce volume anonyme sera monté à chaque commande RUN, pré-rempli avec le contenu de l'image, puis supprimé à la fin de la commande RUN. Seules les modifications apportées au conteneur sont enregistrées, pas les modifications apportées au volume.


Depuis que cette question a été posée, quelques fonctionnalités ont été publiées qui pourraient aider. Il s’agit tout d’abord des versions à plusieurs étages vous permettant de créer une première étape inefficace sur l’espace disque et de copier uniquement la sortie requise dans l’étape finale de livraison. La deuxième fonctionnalité est Buildkit, qui modifie radicalement la manière dont les images sont construites et de nouvelles fonctionnalités sont ajoutées à la construction.

Pour une construction en plusieurs étapes, vous devez avoir plusieurs lignes FROM, chacune commençant la création d'une image distincte. Seule la dernière image est marquée par défaut, mais vous pouvez copier les fichiers des étapes précédentes. L’utilisation standard consiste à disposer d’un environnement de compilation pour créer un artefact d’application binaire ou autre, et d’un environnement d’exécution au deuxième stade, qui copie cet artefact. Tu aurais pu:

FROM debian:sid as builder
COPY export /export
RUN compile command here >/result.bin

FROM debian:sid
COPY --from=builder /result.bin /result.bin
CMD ["/result.bin"]

Cela se traduirait par une construction qui ne contiendrait que le binaire obtenu, et non le répertoire complet/export.


Buildkit vient d'expérimenter en 18.09. C'est une refonte complète du processus de construction, y compris la possibilité de changer l'analyseur frontal. L’un de ces changements d’analyseur a implémenté l’option RUN --mount qui vous permet de monter un répertoire de cache pour vos commandes d’exécution. Par exemple. en voici un qui monte certains des répertoires debian (avec une reconfiguration de l'image debian, cela pourrait accélérer les réinstallations de paquets):

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/var/lib/apt/lists,type=cache \
    --mount=target=/var/cache/apt,type=cache \
    apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      git

Vous pouvez ajuster le répertoire de cache pour le cache d’application que vous avez, par exemple. $ HOME/.m2 pour maven ou /root/.cache pour golang.


TL; DR: La réponse est ici: Avec cette syntaxe RUN --mount, vous pouvez également lier des répertoires de montage en lecture seule à partir du contexte de construction. Le dossier doit exister dans le contexte de construction et il n'est pas mappé vers l'hôte ou le client de génération:

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/export,type=bind,source=export \
    process export directory here...

Notez que, comme le répertoire est monté à partir du contexte, il est également monté en lecture seule et vous ne pouvez pas renvoyer les modifications à l'hôte ou au client. Lorsque vous compilez, vous souhaiterez une installation de 18.09 ou plus récente et activer le kit de construction avec export DOCKER_BUILDKIT=1.

21
BMitch

Il n'est pas possible d'utiliser l'instruction VOLUME pour indiquer à docker quoi de monter. Cela compromettrait sérieusement la portabilité. Cette instruction indique à docker que le contenu de ces répertoires ne figure pas dans les images et que vous pouvez y accéder à partir d'autres conteneurs à l'aide du paramètre de ligne de commande --volumes-from. Vous devez exécuter le conteneur en utilisant -v /path/on/Host:/path/in/container pour accéder aux répertoires à partir de l'hôte.

Le montage de volumes d'hôtes lors de la construction n'est pas possible. Il n'y a pas de build privilégié et monter l'hôte dégraderait aussi sérieusement la portabilité. Vous voudrez peut-être essayer d'utiliser wget ou curl pour télécharger tout ce dont vous avez besoin pour la construction et le mettre en place.

109
Andreas Steffan

MISE À JOUR: Quelqu'un ne prendra tout simplement pas non comme réponse, et j'aime beaucoup cela, en particulier pour cette question particulière.

BONNES NOUVELLES, il y a un moyen maintenant -

La solution est Rocker: https://github.com/grammarly/rocker

John Yani dit , "IMO, il résout tous les points faibles de Dockerfile, le rendant ainsi apte au développement."

Bascule

https://github.com/grammarly/rocker

En introduisant de nouvelles commandes, Rocker vise à résoudre les cas d'utilisation suivants, qui sont douloureux avec Docker:

  1. Montez les volumes réutilisables sur l’étape de la construction. Les outils de gestion des dépendances peuvent donc utiliser le cache entre les générations.
  2. Partagez les clés ssh avec build (pour extraire des pensions privées, etc.) sans les laisser dans l'image résultante.
  3. Construire et exécuter une application dans différentes images, être capable de passer facilement un artefact d'une image à une autre, idéalement, avoir cette logique dans un seul fichier Docker.
  4. Tag/Push images directement à partir de Dockerfiles.
  5. Transmettez les variables de la commande de construction du shell afin qu’elles puissent être substituées à un fichier Docker.

Et plus. Ce sont les problèmes les plus critiques qui bloquaient notre adoption de Docker à Grammarly.

Mise à jour: Rocker a été arrêté, selon le rapport de projet officiel sur Github

Au début de 2018, l'écosystème des conteneurs est beaucoup plus mature qu'il ne l'était il y a trois ans lorsque ce projet a été lancé. Désormais, certaines des caractéristiques essentielles et remarquables de Rocker peuvent être facilement couvertes par la construction de docker ou d'autres outils bien pris en charge, bien que certaines fonctionnalités restent spécifiques à Rocker. Voir https://github.com/grammarly/rocker/issues/199 pour plus de détails.

63
xpt

Il existe un moyen de monter un volume lors d'une construction, mais cela n'implique pas Dockerfiles.

La technique consisterait à créer un conteneur à partir de la base que vous souhaitez utiliser (montage de votre/vos volume (s) dans le conteneur avec l'option -v), exécutez un script Shell pour créer votre image. travaux de construction, puis valider le conteneur en tant qu’image une fois terminé.

Cela laissera non seulement de côté les fichiers superflus que vous ne voulez pas (cela vaut également pour les fichiers sécurisés, comme les fichiers SSH), mais cela crée également une seule image. Cela a des inconvénients: la commande commit ne prend pas en charge toutes les instructions de Dockerfile et ne vous laisse pas reprendre lorsque vous vous êtes arrêté si vous devez modifier votre script de construction.

MISE À JOUR:

Par exemple,

CONTAINER_ID=$(docker run -dit ubuntu:16.04)
docker cp build.sh $CONTAINER_ID:/build.sh
docker exec -t $CONTAINER_ID /bin/sh -c '/bin/sh /build.sh'
docker commit $CONTAINER_ID $REPO:$TAG
docker stop $CONTAINER_ID
14
Keith Mason

Lorsque vous exécutez le conteneur, un répertoire sur votre hôte est créé et monté dans le conteneur. Vous pouvez trouver avec quel répertoire c'est

$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]

Si vous souhaitez monter un répertoire à partir de votre hôte dans votre conteneur, vous devez utiliser le paramètre -v et spécifier le répertoire. Dans votre cas, ce serait:

docker run -v /export:/export data

Donc, vous utiliseriez le dossier hosts dans votre conteneur.

5
Behe

Je pense que vous pouvez faire ce que vous voulez faire en exécutant la construction via une commande docker qui est elle-même exécutée dans un conteneur docker. Voir Docker peut maintenant fonctionner dans Docker | Blog Docker . Une technique comme celle-ci, mais qui a effectivement accédé au menu fixe extérieur à partir d'un conteneur, a été utilisée, par exemple, tout en explorant la procédure à suivre Créer le plus petit conteneur possible de Docker | Blog Xebia .

Un autre article pertinent est Optimisation des images Docker | CenturyLink Labs , ce qui explique que, si vous finissez par télécharger des fichiers au cours d'une construction, vous pouvez éviter de perdre de l'espace dans l'image finale en téléchargeant, en construisant et en supprimant le téléchargement tout en une étape d'exécution.

4
nealmcb

C'est moche, mais j'ai réalisé un semblant de ceci comme ceci:

Dockerfile:

FROM foo
COPY ./m2/ /root/.m2
RUN stuff

imageBuild.sh:

docker build . -t barImage
container="$(docker run -d barImage)"
rm -rf ./m2
docker cp "$container:/root/.m2" ./m2
docker rm -f "$container"

J'ai une version Java qui télécharge l'univers dans /root/.m2, et l'a fait à chaque fois. imageBuild.sh copie le contenu de ce dossier sur l'hôte après la construction, et Dockerfile les recopie dans l'image pour la prochaine construction.

Cela ressemble à la façon dont un volume fonctionnerait (c’est-à-dire qu’il persiste entre les générations).

3

Voici une version simplifiée de l'approche en 2 étapes utilisant build et commit, sans scripts Shell. Ça implique:

  1. Construction partielle de l'image, sans volumes
  2. Exécuter un conteneur avec des volumes, apporter des modifications, puis valider le résultat, en remplaçant le nom de l'image d'origine.

Avec des modifications relativement mineures, l’étape supplémentaire n’ajoute que quelques secondes au temps de génération.

Fondamentalement:

docker build -t image-name . # your normal docker build

# Now run a command in a throwaway container that uses volumes and makes changes:
docker run -v /some:/volume --name temp-container image-name /some/post-configure/command

# Replace the original image with the result:
# (reverting CMD to whatever it was, otherwise it will be set to /some/post-configure/command)   
docker commit --change="CMD bash" temp-container image-name 

# Delete the temporary container:
docker rm temp-container

Dans mon cas d'utilisation, je souhaite pré-générer un fichier maven toolchains.xml, mais mes nombreuses installations JDK se trouvent sur un volume qui n'est pas disponible avant l'exécution. Certaines de mes images ne sont pas compatibles avec tous les JDKS. J'ai donc besoin de tester la compatibilité au moment de la construction et de peupler le fichier toolchains.xml de manière conditionnelle. Notez que je n'ai pas besoin que l'image soit portable, je ne la publie pas sur Docker Hub.

0
Akom