Comment monter des volumes hôtes dans des conteneurs Docker dans Dockerfile pendant la génération

236

Question d'origine: comment utiliser l'instruction VOLUME dans Dockerfile?

La vraie question que je veux résoudre est - comment monter des volumes hôtes dans des conteneurs Docker dans Dockerfile pendant la construction, c'est-à-dire avoir la docker run -v /export:/exportcapacité pendant docker build.

La raison derrière cela, pour moi, est lors de la construction de choses dans Docker, je ne veux pas que ces apt-get installcaches ( ) soient verrouillés dans un seul docker, mais pour les partager / réutiliser. C'est la principale raison pour laquelle je pose cette question.

Dernière mise à jour:

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

Il existe un moyen de monter un volume pendant une génération, mais cela n'implique pas Dockerfiles.

Cependant, c'était une réponse mal formulée, organisée et appuyée. Lorsque j'ai réinstallé mon docker contient, je suis tombé sur l'article suivant:

Docker 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 propose de faire. Et j'avoue que c'est mieux que celui que j'essayais de demander ici.

Une autre façon est la nouvelle réponse acceptée , par exemple, le Buildkit dans la version 18.09.

Choisissez celui qui vous convient.


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


Ancienne mise à jour: la réponse est donc "impossible". Je peux l'accepter comme réponse car je sais que le problème a été longuement discuté sur https://github.com/docker/docker/issues/3156 . Je peux comprendre que la portabilité est un problème primordial pour le développeur docker; mais en tant qu'utilisateur docker, je dois dire que je suis très déçu de cette fonctionnalité manquante. Permettez-moi de terminer mon argumentation par une citation de la discussion susmentionnée: " Je voudrais utiliser Gentoo comme image de base, mais je ne veux certainement pas que> 1 Go de données d'arborescence Portage soient dans l'une des couches une fois l'image créée. Vous pourrait avoir de jolis conteneurs compacts sans le gigantesque arbre de portage 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'une simple considération de portabilité m'oblige maintenant à télécharger> 1 Go d'arborescence Portage chaque fois que je crée 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 dans l'intervalle. 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 docker 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 façon dans Dockerfile de monter des volumes montés sur l'hôte dans des conteneurs Docker, 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
xpt
la source
Demande de fonctionnalité apparemment plus actuelle (pas que je m'attende à ce qu'elle soit implémentée, mais juste au cas où): docker / docker # 14080
Jesse Glick
en effet, il y a une discussion approfondie sur le fait qu'il ne devrait pas être autorisé de lier un répertoire hôte et un répertoire conteneur pendant la construction, c'est-à-dire quelque chose comme VOLUME ~/host_dir ~/container_dir. La discussion est assez approfondie, s'il y a un court moyen de résumer quelle est la raison?
Charlie Parker

Réponses:

34

Tout d'abord, pour répondre "pourquoi ne VOLUMEfonctionne pas ?" Lorsque vous définissez un VOLUMEdans le Dockerfile, vous ne pouvez définir que la cible, pas la source du volume. Pendant la construction, vous n'en obtiendrez qu'un volume anonyme. Ce volume anonyme sera monté à chaque RUNcommande, prérempli avec le contenu de l'image, puis supprimé à la fin de la RUNcommande. Seules les modifications apportées au conteneur sont enregistrées, pas les modifications du volume.


Depuis que cette question a été posée, quelques fonctionnalités ont été publiées qui pourraient aider. Le premier est les constructions à plusieurs étapes vous permettant de créer une première étape d'espace disque inefficace et de copier uniquement la sortie nécessaire dans la dernière étape que vous expédiez. Et la deuxième fonctionnalité est Buildkit qui change radicalement la façon dont les images sont construites et de nouvelles capacités sont ajoutées à la construction.

Pour une construction en plusieurs étapes, vous auriez plusieurs FROMlignes, chacune commençant la création d'une image distincte. Seule la dernière image est balisée par défaut, mais vous pouvez copier des fichiers des étapes précédentes. L'utilisation standard est d'avoir un environnement de compilation pour construire un artefact binaire ou autre application, et un environnement d'exécution comme deuxième étape qui copie sur 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 entraînerait une génération qui ne contient que le binaire résultant, et non le répertoire complet / export.


Buildkit sort de l'expérimental le 18.09. C'est une refonte complète du processus de construction, y compris la possibilité de changer l'analyseur frontal. L'une de ces modifications de l'analyseur a mis en œuvre l' RUN --mountoption qui vous permet de monter un répertoire de cache pour vos commandes d'exécution. Par exemple, voici celui qui monte certains des répertoires debian (avec une reconfiguration de l'image debian, cela pourrait accélérer les réinstallations des 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 devez ajuster le répertoire de cache pour le cache d'application dont vous disposez, par exemple $ HOME / .m2 pour maven, ou /root/.cache pour golang.


TL; DR: La réponse est ici: Avec cette RUN --mountsyntaxe, vous pouvez également lier des répertoires en lecture seule à partir du contexte de construction. Le dossier doit exister dans le contexte de génération 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 le répertoire étant 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 construisez, vous souhaiterez une installation 18.09 ou plus récente et activez buildkit avec export DOCKER_BUILDKIT=1.

Si vous obtenez une erreur indiquant que l'indicateur de montage n'est pas pris en charge, cela indique que vous n'avez pas activé buildkit avec la variable ci-dessus, ou que vous n'avez pas activé la syntaxe expérimentale avec la ligne de syntaxe en haut du Dockerfile avant toute autre ligne, y compris les commentaires. Notez que la variable pour basculer buildkit ne fonctionnera que si votre installation de docker a un support de buildkit intégré, qui nécessite la version 18.09 ou plus récente de Docker, à la fois sur le client et le serveur.

BMitch
la source
2
Malheureusement, sur Windows Buildkit n'est pas encore pris en charge dans la version 18.09
Wesley
1
On dirait que "armhf" ne prend pas en charge "mount" non plus.
Mike
2
Je reçois "Réponse d'erreur du démon: Dockerfile parse error line xx: Unknown flag: mount" sur OSX
ChristoKiwi
1
La prise en charge de docker-compose n'est pas encore là, mais vous n'avez pas besoin de composer pour créer des images. Problème à suivre: github.com/moby/buildkit/issues/685
BMitch
2
Documentation à ce sujet: github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/…
Drew LeSueur
116

Il n'est pas possible d'utiliser l' VOLUMEinstruction pour indiquer à docker quoi monter. Cela romprait sérieusement la portabilité. Cette instruction indique à docker que le contenu de ces répertoires ne va pas dans les images et est accessible à partir d'autres conteneurs à l'aide du --volumes-fromparamètre de ligne de commande. Vous devez exécuter le conteneur en utilisant -v /path/on/host:/path/in/containerpour accéder aux répertoires de l'hôte.

Il n'est pas possible de monter des volumes d'hôtes pendant la génération. Il n'y a pas de construction privilégiée et le montage de l'hôte dégraderait également 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.

Andreas Steffan
la source
2
Merci. Question révisée. La vraie question que je veux résoudre est - comment monter des volumes hôtes dans des conteneurs Docker dans Dockerfile pendant la construction. THX.
2014
2
Pas possible. Voir la réponse révisée.
Andreas Steffan
3
Je peux apprécier les effets secondaires «potentiels» de la portabilité, mais il existe également un cas d'utilisation valable pour avoir cette option. Dans mon cas, j'aimerais pouvoir dire aux utilisateurs de "Déplacer vers le répertoire et d'exécuter la commande 'docker run'" en ayant $ (PWD) monté dans un répertoire de conteneur. $ (PWD) garantit le maintien de la portabilité. Bien que cela puisse être un cas d'angle, cela m'aiderait énormément là où je distribue des environnements d'exécution pour les scripts fournis par l'utilisateur.
ntwrkguru
64

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

BONNES NOUVELLES, il y a un moyen maintenant -

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

John Yani a déclaré : "IMO, il résout tous les points faibles de Dockerfile, ce qui le rend 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 pénibles avec Docker ordinaire:

  1. Montez des volumes réutilisables à l'étape de la génération, afin que les outils de gestion des dépendances puissent utiliser le cache entre les générations.
  2. Partagez les clés ssh avec build (pour extraire les dépôts privés, etc.), sans les laisser dans l'image résultante.
  3. Créez et exécutez une application dans différentes images, soyez capable de passer facilement un artefact d'une image à une autre, idéalement avoir cette logique dans un seul Dockerfile.
  4. Tag / Push images directement à partir de Dockerfiles.
  5. Transmettez les variables de la commande shell build afin qu'elles puissent être remplacées par un Dockerfile.

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

Mise à jour: Rocker a été interrompu, selon le référentiel officiel du projet sur Github

Au début de 2018, l'écosystème des conteneurs est beaucoup plus mature qu'il y a trois ans lorsque ce projet a été lancé. Maintenant, certaines des fonctionnalités essentielles et exceptionnelles 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 uniques à rocker. Voir https://github.com/grammarly/rocker/issues/199 pour plus de détails.

xpt
la source
J'essaie d'utiliser Rocker pour résoudre le problème numéro 1, mais la commande de montage ne fonctionnera pas et l'image créée ne contient pas le dossier hôte. Ma commande de montage Dockerfile ressemble à ceci - MOUNT ~/code/docker-app-dev/new-editor/:/src/et ma commande de construction Rocker est la suivante - rocker build -f Dockerfile .. Qu'est-ce que je fais mal?
Yaron Idan
Essayez peut-être d'utiliser un vrai chemin d'hôte? ~est un métacaractère du shell Bourne.
Jesse Glick
Rocker buildne permet pas les docker runoptions de ligne de commande, donc ne permet pas actuellement des choses comme --privileged.
Monty Wild
Salut @xpt, pouvons-nous obtenir une autre mise à jour puisque le rocker est maintenant arrêté
Shardj
Maintenant que le rocker est interrompu, je reviens à la réponse "Pas possible". Voir OP et la réponse sélectionnée.
xpt
14

Il existe un moyen de monter un volume pendant une génération, mais cela n'implique pas Dockerfiles.

La technique consiste à créer un conteneur à partir de la base que vous souhaitez utiliser (monter votre ou vos volumes dans le conteneur avec l' -voption), exécuter un script shell pour effectuer votre travail de création d'image, puis valider le conteneur en tant qu'image lorsque vous avez terminé .

Non seulement cela laissera de côté les fichiers excédentaires dont vous ne voulez pas (c'est également bon pour les fichiers sécurisés, comme les fichiers SSH), mais cela crée également une seule image. Il a des inconvénients: la commande commit ne prend pas en charge toutes les instructions Dockerfile, et elle ne vous permet pas de reprendre lorsque vous vous êtes arrêté si vous devez modifier votre script de construction.

METTRE À 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
Keith Mason
la source
6
+1 Pourriez-vous s'il vous plaît élaborer un peu plus sur les instructions du 2ème paragraphe. Par exemple, si la base est debian:wheezyet le script shell build.sh, quelles instructions spécifiques utiliserait-on?
Drux
6

Lorsque vous exécutez le conteneur, un répertoire sur votre hôte est créé et monté dans le conteneur. Vous pouvez savoir de quel répertoire il s'agit

$ 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 à l'intérieur de votre conteneur, vous devez utiliser le -vparamètre et spécifier le répertoire. Dans votre cas, ce serait:

docker run -v /export:/export data

Vous utiliserez donc le dossier hosts à l'intérieur de votre conteneur.

Behe
la source
1
Merci. Question révisée. La vraie question que je veux résoudre est - comment monter des volumes hôtes dans des conteneurs Docker dans Dockerfile pendant la construction. THX.
2014
Veuillez ne pas réviser vos questions d' une manière aussi drastique . Cela rend ma question invalide même si elle était parfaitement valide avant vos modifications. Pensez plutôt à poser une nouvelle question.
Behe
11
Question d'origine : comment utiliser l'instruction VOLUME dans Dockerfile? Il est encore au tout début de la question, même aujourd'hui. Votre réponse était pour le temps d'exécution , et ma question a toujours été sur le temps de construction , à quoi sert Dockerfile.
xpt
4

Je pense que vous pouvez faire ce que vous voulez en exécutant la construction via une commande docker qui elle-même est exécutée dans un conteneur docker. Voir Docker peut désormais s'exécuter dans Docker | Blog Docker . Une technique comme celle-ci, mais qui accédait en fait au docker externe à partir d'un conteneur, a été utilisée, par exemple, pour explorer comment créer le plus petit conteneur Docker possible | Blog Xebia .

Un autre article pertinent est Optimisation des images Docker | CenturyLink Labs , qui explique que si vous finissez par télécharger des trucs pendant une construction, vous pouvez éviter de perdre de l'espace dans l'image finale en téléchargeant, construisant et supprimant le téléchargement en une seule étape RUN.

nealmcb
la source
3

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 je l'ai fait à chaque fois . imageBuild.shcopie le contenu de ce dossier sur l'hôte après la génération et les Dockerfilecopie dans l'image pour la génération suivante.

C'est quelque chose comme le fonctionnement d'un volume (c'est-à-dire qu'il persiste entre les builds).

MatrixManAtYrService
la source
Il s'agit d'une solution viable pour l'intégration continue basée sur Docker aka CI. Configurez les bibliothèques et les compilateurs et exécutez make via les commandes Dockerfile, lancez l'image trivialement juste pour créer un conteneur, et enfin copiez l'artefact souhaité comme un .deb. Semble fonctionner, merci d'avoir posté ceci.
chrisinmtown
Cette solution vous laisse une image avec TOUS les fichiers dans ./m2/ - celle dont vous avez besoin et celle dont vous n'avez pas besoin - et cela peut conduire à des images de production ÉNORMES, ce qui n'est pas souhaité! Avec le montage dans le répertoire des dépendances externes, seuls les fichiers nécessaires seraient copiés dans l'image.
Marko Krajnc
Si vous avez l'intention de publier l'image, il est probablement préférable d'attendre et de laisser maven télécharger à nouveau ses propres dépendances à chaque fois. Ce hack n'a de sens que si vous mettez en scène une image à tester - une image avec laquelle les utilisateurs finaux n'entreront jamais en contact.
MatrixManAtYrService
1

Voici une version simplifiée de l'approche en 2 étapes utilisant la génération et la validation, sans scripts shell. Ça implique:

  1. Construire l'image partiellement, 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 construction.

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, je dois donc tester la compatibilité au moment de la construction et remplir conditionnellement toolchains.xml. Notez que je n'ai pas besoin que l'image soit portable, je ne la publie pas sur Docker Hub.

Akom
la source
1

Comme beaucoup l'ont déjà répondu, le montage de volumes d'hôtes pendant la génération n'est pas possible. Je voudrais juste ajouter un docker-composemoyen, je pense que ce sera bien d'avoir, principalement pour le développement / l'utilisation des tests

Dockerfile

FROM node:10
WORKDIR /app
COPY . .
RUN npm ci
CMD sleep 999999999

docker-compose.yml

version: '3'
services:
  test-service:
    image: test/image
    build:
      context: .
      dockerfile: Dockerfile
    container_name: test
    volumes:
      - ./export:/app/export
      - ./build:/app/build

Et exécutez votre conteneur par docker-compose up -d --build

Yegor Zaremba
la source