Comprendre l'instruction «VOLUME» dans DockerFile

136

Ci-dessous le contenu de mon "Dockerfile"

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]

Dans ce fichier, j'attends l'instruction "VOLUME. / Usr / src / app" pour monter le contenu du répertoire de travail actuel dans l'hôte à monter sur le dossier / usr / src / app du conteneur.

S'il vous plaît laissez-moi savoir si c'est la bonne façon?

refactor
la source

Réponses:

88

Le tutoriel officiel de docker dit:

Un volume de données est un répertoire spécialement désigné dans un ou plusieurs conteneurs qui contourne le système de fichiers Union. Les volumes de données fournissent plusieurs fonctionnalités utiles pour les données persistantes ou partagées:

  • Les volumes sont initialisés lors de la création d'un conteneur. Si l'image de base du conteneur contient des données au point de montage spécifié,
    ces données existantes sont copiées dans le nouveau volume lors de l'
    initialisation du volume . (Notez que cela ne s'applique pas lors du montage d'un
    répertoire hôte .)
  • Les volumes de données peuvent être partagés et réutilisés entre les conteneurs.

  • Les modifications apportées à un volume de données sont effectuées directement.

  • Les modifications apportées à un volume de données ne seront pas incluses lorsque vous mettez à jour une image.

  • Les volumes de données persistent même si le conteneur lui-même est supprimé.

Dans Dockerfilevous ne pouvez spécifier que la destination d'un volume à l' intérieur d' un conteneur. par exemple /usr/src/app.

Lorsque vous exécutez un conteneur, par exemple docker run --volume=/opt:/usr/src/app my_image, vous pouvez, mais ce n'est pas nécessaire, spécifier son point de montage ( / opt ) sur la machine hôte. Si vous ne spécifiez pas d' --volumeargument, le point de montage sera choisi automatiquement, généralement sous /var/lib/docker/volumes/.

Boukharov Sergey
la source
277

En bref: non, votre VOLUMEinstruction n'est pas correcte.

Les fichiers Dockerfile VOLUMEspécifient un ou plusieurs volumes en fonction des chemins côté conteneur. Mais cela ne permet pas à l'auteur de l'image de spécifier un chemin d'hôte. Du côté de l'hôte, les volumes sont créés avec un nom de type ID très long à l'intérieur de la racine Docker. Sur ma machine, c'est /var/lib/docker/volumes.

Remarque: étant donné que le nom généré automatiquement est extrêmement long et n'a aucun sens du point de vue d'un humain, ces volumes sont souvent appelés «sans nom» ou «anonymes».

Votre exemple qui utilise un "." caractère ne fonctionnera même pas sur ma machine, peu importe si je fais du point le premier ou le deuxième argument. Je reçois ce message d'erreur:

docker: réponse d'erreur du démon: erreur d'exécution oci: container_linux.go: 265: démarrage du processus de conteneur causé "process_linux.go: 368: conteneur init causé \" open / dev / ptmx: aucun fichier ou répertoire de ce type \ "".

Je sais que ce qui a été dit à ce point est sans doute pas très utile pour quelqu'un qui essaie de comprendre VOLUMEet -vet il ne est certainement pas à une solution pour ce que vous essayez d'accomplir. Nous espérons donc que les exemples suivants éclaireront davantage ces questions.

Minitutorial: Spécification des volumes

Compte tenu de ce Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(Pour le résultat de ce minitutoriel, cela ne fait aucune différence si nous spécifions vol1 vol2ou /vol1 /vol2- ne me demandez pas pourquoi)

Construit le:

docker build -t my-openjdk

Courir:

docker run --rm -it my-openjdk

À l'intérieur du conteneur, exécutez lsla ligne de commande et vous remarquerez que deux répertoires existent; /vol1et /vol2.

L'exécution du conteneur crée également deux répertoires, ou "volumes", du côté hôte.

Pendant que le conteneur est en cours d'exécution, exécutez docker volume lssur la machine hôte et vous verrez quelque chose comme ceci (j'ai remplacé la partie centrale du nom par trois points par souci de concision):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

De retour dans le conteneur , exécutez touch /vol1/weird-ass-file(crée un fichier vierge à cet emplacement).

Ce fichier est maintenant disponible sur la machine hôte, dans l'un des volumes sans nom lol. Cela m'a pris deux essais car j'ai d'abord essayé le premier volume répertorié, mais j'ai finalement trouvé mon fichier dans le deuxième volume répertorié, en utilisant cette commande sur la machine hôte:

sudo ls /var/lib/docker/volumes/f670...49f0/_data

De même, vous pouvez essayer de supprimer ce fichier sur l'hôte et il sera également supprimé dans le conteneur.

Remarque: Le _datadossier est également appelé "point de montage".

Quittez le conteneur et répertoriez les volumes sur l'hôte. Ils sont partis. Nous avons utilisé l' --rmindicateur lors de l'exécution du conteneur et cette option efface efficacement non seulement le conteneur à la sortie, mais également les volumes.

Exécutez un nouveau conteneur, mais spécifiez un volume en utilisant -v:

docker run --rm -it -v /vol3 my-openjdk

Cela ajoute un troisième volume et l'ensemble du système finit par avoir trois volumes sans nom. La commande se serait plantée si nous l'avions spécifié seulement -v vol3. L'argument doit être un chemin absolu à l' intérieur du conteneur. Du côté hôte, le nouveau troisième volume est anonyme et réside avec les deux autres volumes dans /var/lib/docker/volumes/.

Il a été indiqué précédemment que le Dockerfilene peut pas être mappé à un chemin d'hôte, ce qui pose un problème pour nous lorsque nous essayons d'apporter des fichiers de l'hôte au conteneur pendant l'exécution. Une -vsyntaxe différente résout ce problème.

Imaginez que j'ai un sous-dossier dans le répertoire de mon projet ./srcque je souhaite synchroniser à l' /srcintérieur du conteneur. Cette commande fait l'affaire:

docker run -it -v $(pwd)/src:/src my-openjdk

Les deux côtés du :personnage attendent un chemin absolu. Le côté gauche étant un chemin absolu sur la machine hôte, le côté droit étant un chemin absolu à l'intérieur du conteneur. pwdest une commande qui "imprime le répertoire courant / de travail". Mettre la commande dans $()prend la commande entre parenthèses, l'exécute dans un sous-shell et renvoie le chemin absolu vers notre répertoire de projet.

En mettant tout cela ensemble, supposons que nous avons ./src/Hello.javadans notre dossier de projet sur la machine hôte avec le contenu suivant:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}

Nous construisons ce Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

Nous exécutons cette commande:

docker run -v $(pwd)/src:/src my-openjdk

Cela imprime "Hello, World!".

La meilleure partie est que nous sommes totalement libres de modifier le fichier .java avec un nouveau message pour une autre sortie lors d'une deuxième exécution - sans avoir à reconstruire l'image =)

Remarques finales

Je suis assez nouveau dans Docker, et le "tutoriel" mentionné ci-dessus reflète les informations que j'ai recueillies à partir d'un hackathon en ligne de commande de 3 jours. J'ai presque honte de ne pas avoir été en mesure de fournir des liens vers une documentation claire de type anglais pour étayer mes déclarations, mais je pense honnêtement que cela est dû à un manque de documentation et non à un effort personnel. Je sais que les exemples fonctionnent comme annoncé en utilisant ma configuration actuelle qui est "Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce".

Le tutoriel ne résout pas le problème "comment spécifier le chemin du conteneur dans le Dockerfile et laisser la commande run spécifier uniquement le chemin de l'hôte". Il y a peut-être un moyen, je ne l'ai tout simplement pas trouvé.

Enfin, j'ai le sentiment instinctif que la spécification VOLUMEdans le Dockerfile n'est pas seulement rare, mais c'est probablement une meilleure pratique à ne jamais utiliser VOLUME. Pour deux raisons. La première raison que nous avons déjà identifiée: nous ne pouvons pas spécifier le chemin de l'hôte - ce qui est une bonne chose car Dockerfiles doit être très indépendant des spécificités d'une machine hôte. Mais la deuxième raison est que les gens peuvent oublier d'utiliser l' --rmoption lors de l'exécution du conteneur. On peut se rappeler de retirer le conteneur mais oublier de retirer le volume. De plus, même avec le meilleur de la mémoire humaine, il peut être difficile de déterminer quels volumes anonymes peuvent être supprimés en toute sécurité.

Martin Andersson
la source
2
Quand devons-nous utiliser des volumes sans nom / anonymes?
Searene
10
@Martin merci beaucoup. Votre hackathon et le tutoriel qui en résulte ici sont très appréciés.
Beezer
6
"Je n'ai pas été en mesure de fournir des liens vers une documentation claire de type anglais ... Je pense honnêtement que cela est dû à un manque de documentation". Je peux confirmer. C'est la documentation la plus complète et la plus à jour que j'ai trouvée et que je recherche depuis des heures.
user697576
4
docker volume prunepeut être utilisé pour nettoyer les volumes restants qui ne sont pas attachés aux conteneurs en cours d'exécution. Pour ne pas dire qu'il sera facile de distinguer ceux potentiellement importants par id seul ...
Jeremy
4
"Pour le résultat de ce minitutoriel, cela ne fait aucune différence si nous spécifions vol1 vol2 ou / vol1 / vol2 - ne me demandez pas pourquoi". @MartinAndersson c'est parce que le répertoire de travail actuel est /, donc vol1est relatif à /, qui se résout en /vol1. Si vous utilisez WORKDIRpour spécifier un répertoire de travail autre que /, vol1et /vol1ne pointerait plus vers le même répertoire.
sebastian
41

La spécification d'une VOLUMEligne dans un Dockerfile configure un peu de métadonnées sur votre image, mais la manière dont ces métadonnées sont utilisées est importante.

Tout d'abord, qu'ont fait ces deux lignes:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

La WORKDIRligne crée le répertoire s'il n'existe pas, et met à jour certaines métadonnées d'image pour spécifier tous les chemins relatifs, ainsi que le répertoire actuel pour les commandes comme RUNsera à cet emplacement. La VOLUMEligne y spécifie deux volumes , l'un est le chemin relatif .et l'autre est /usr/src/app, les deux sont simplement le même répertoire. Le plus souvent, la VOLUMEligne ne contient qu'un seul répertoire, mais elle peut en contenir plusieurs comme vous l'avez fait, ou il peut s'agir d'un tableau au format json.

Vous ne pouvez pas spécifier une source de volume dans le Dockerfile : une source courante de confusion lors de la spécification de volumes dans un Dockerfile essaie de faire correspondre la syntaxe d'exécution d'une source et d'une destination au moment de la création de l'image, cela ne fonctionnera pas . Le Dockerfile ne peut spécifier que la destination du volume. Ce serait un exploit de sécurité trivial si quelqu'un pouvait définir la source d'un volume car il pouvait mettre à jour une image commune sur le hub docker pour monter le répertoire racine dans le conteneur, puis lancer un processus d'arrière-plan à l'intérieur du conteneur dans le cadre d'un point d'entrée qui ajoute des connexions à / etc / passwd, configure systemd pour lancer un mineur Bitcoin au prochain redémarrage, ou recherche dans le système de fichiers des cartes de crédit, des SSN et des clés privées à envoyer vers un site distant.

Que fait la ligne VOLUME? Comme mentionné, il définit certaines métadonnées d'image pour dire qu'un répertoire à l'intérieur de l'image est un volume. Comment ces métadonnées sont-elles utilisées? Chaque fois que vous créez un conteneur à partir de cette image, docker forcera ce répertoire à être un volume. Si vous ne fournissez pas de volume dans votre commande d'exécution ou dans votre fichier de composition, la seule option pour le docker est de créer un volume anonyme. Il s'agit d'un volume nommé local avec un long identifiant unique pour le nom et aucune autre indication de la raison pour laquelle il a été créé ou des données qu'il contient (les volumes anonymes sont des données perdues). Si vous remplacez le volume, en pointant vers un volume nommé ou hôte, vos données y iront à la place.

VOLUME casse les choses: vous ne pouvez pas désactiver un volume une fois défini dans un Dockerfile. Et plus important encore, la RUNcommande dans docker est implémentée avec des conteneurs temporaires. Ces conteneurs temporaires recevront un volume anonyme temporaire. Ce volume anonyme sera initialisé avec le contenu de votre image. Toutes les écritures à l'intérieur du conteneur à partir de votre RUNcommande seront effectuées sur ce volume. Une fois la RUNcommande terminée, les modifications apportées à l'image sont enregistrées et les modifications apportées au volume anonyme sont supprimées. Pour cette raison, je déconseille fortement de définir unVOLUME à l'intérieur du Dockerfile. Il en résulte un comportement inattendu pour les utilisateurs en aval de votre image qui souhaitent étendre l'image avec les données initiales dans l'emplacement du volume.

Comment devez-vous spécifier un volume? Pour spécifier où vous souhaitez inclure les volumes avec votre image, fournissez undocker-compose.yml . Les utilisateurs peuvent modifier cela pour ajuster l'emplacement du volume à leur environnement local, et il capture d'autres paramètres d'exécution tels que les ports de publication et la mise en réseau.

Quelqu'un devrait documenter cela! Ils ont. Docker inclut des avertissements sur l'utilisation de VOLUME dans sa documentation sur le Dockerfile ainsi que des conseils pour spécifier la source au moment de l'exécution:

  • Modification du volume à partir du Dockerfile: si des étapes de construction modifient les données du volume après sa déclaration, ces modifications seront supprimées.

...

  • Le répertoire hôte est déclaré lors de l'exécution du conteneur: Le répertoire hôte (le point de montage) est, de par sa nature, dépendant de l'hôte. Cela permet de préserver la portabilité des images, car un répertoire hôte donné ne peut pas être garanti comme étant disponible sur tous les hôtes. Pour cette raison, vous ne pouvez pas monter un répertoire hôte à partir du Dockerfile. L' VOLUME instruction ne prend pas en charge la spécification d'un host-dirparamètre. Vous devez spécifier le point de montage lorsque vous créez ou exécutez le conteneur.
BMitch
la source
36

La VOLUMEcommande dans a Dockerfileest tout à fait légitime, totalement conventionnelle, tout à fait correcte à utiliser et elle n'est de toute façon pas obsolète. Juste besoin de le comprendre.

Nous l'utilisons pour pointer vers tous les répertoires dans lesquels l'application du conteneur écrira beaucoup. Nous n'utilisons pas VOLUMEsimplement parce que nous voulons partager entre l'hôte et le conteneur comme un fichier de configuration.

La commande a simplement besoin d'un paramètre; un chemin d'accès à un dossier, relatif à WORKDIRsi défini, à partir du conteneur. Ensuite, docker créera un volume dans son graphique (/ var / lib / docker) et le montera dans le dossier du conteneur. Maintenant, le conteneur aura un endroit où écrire avec des performances élevées. Sans la VOLUMEcommande, la vitesse d'écriture dans le dossier spécifié sera très lente car le conteneur utilise désormais sa copy on writestratégie dans le conteneur lui-même. La copy on writestratégie est l'une des principales raisons pour lesquelles les volumes existent.

Si vous montez sur le dossier spécifié par la VOLUMEcommande, la commande n'est jamais exécutée car elle VOLUMEn'est exécutée que lorsque le conteneur démarre, un peu commeENV .

Fondamentalement, avec la VOLUMEcommande, vous obtenez des performances sans monter aucun volume en externe. Les données seront également enregistrées dans les exécutions de conteneurs sans aucun montage externe. Ensuite, lorsque vous êtes prêt, montez simplement quelque chose dessus.

Quelques bons exemples d'utilisation:
- journaux
- dossiers temporaires

Quelques mauvais cas d'utilisation:
- fichiers statiques
- configs
- code

monsieur havre
la source
2
En ce qui concerne les bons et les mauvais exemples d'utilisation, la page «Meilleures pratiques dockerfile» de Docker indique: «Vous êtes fortement encouragé à utiliser VOLUME pour toute partie modifiable et / ou réparable par l'utilisateur de votre image.». Je pense que les configs sont là-dedans.
OmerSch
2
Il est normal d'être explicite sur les VOLUMErépertoires des configurations. Cependant, une fois que vous avez réellement monté une configuration, vous devrez monter sur ce répertoire et doncVOLUME commande ne s'exécutera pas. Par conséquent, il est inutile d'utiliser la VOLUMEcommande sur un répertoire spécifié pour une configuration. L'initialisation d'un graphe de volume avec un seul fichier statique en lecture seule est également une sérieuse surcharge. Donc je maintiens ce que j'ai dit, pas besoin de VOLUMEcommande sur les configs.
mr Haven le
Les volumes peuvent apporter des caractéristiques de performance différentes en raison des détails de mise en œuvre. Les fichiers de données de base de données correspondent à ce cas d'utilisation, mais quel serait l'intérêt de stocker des données parallèlement au stockage (éphémère) du conteneur? Ie attribuer l'existence de volumes à la performance est incorrect.
André Werlang le
33

Pour mieux comprendre les volumeinstructions de dockerfile, apprenons l'utilisation typique du volume dans l'implémentation officielle du fichier docker mysql.

VOLUME /var/lib/mysql

Référence: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

C'est /var/lib/mysqll'emplacement par défaut de MySQL qui stocke les fichiers de données.

Lorsque vous exécutez le conteneur de test à des fins de test uniquement, vous ne pouvez pas spécifier son point de montage, par exemple

docker run mysql:8

alors l'instance de conteneur mysql utilisera le chemin de montage par défaut qui est spécifié par l' volumeinstruction dans dockerfile. les volumes sont créés avec un nom de type ID très long à l'intérieur de la racine Docker, cela s'appelle volume "sans nom" ou "anonyme". Dans le dossier du système hôte sous-jacent / var / lib / docker / volumes.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Ceci est très pratique à des fins de test rapide sans avoir besoin de spécifier le point de montage, mais peut toujours obtenir les meilleures performances en utilisant Volume pour le stockage de données, pas la couche conteneur.

Pour une utilisation formelle, vous devrez spécifier le chemin de montage en utilisant le volume nommé ou le montage de liaison, par exemple

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

La commande monte le répertoire / my / own / datadir à partir du système hôte sous-jacent en tant que / var / lib / mysql à l'intérieur du conteneur. Le répertoire de données / my / own / datadir ne sera pas automatiquement supprimé, même le conteneur est supprimé.

Utilisation de l'image officielle mysql (veuillez consulter la section «Où stocker les données»):

Référence: https://hub.docker.com/_/mysql/

Li-Tian
la source
2
J'aime beaucoup votre explication.
LukaszTaraszka
Mais docker enregistre quand même les modifications. Vous pouvez également définir le chemin de montage, l' -vutiliser sans régler le volume dans le Dockerfile
Alex78191
1

Je ne considère pas l'utilisation de VOLUME comme bonne en tout cas, sauf si vous créez une image pour vous-même et que personne d'autre ne l'utilisera.

J'ai été affecté négativement en raison du VOLUME exposé dans les images de base que j'ai étendues et je n'ai pris connaissance du problème qu'après que l'image était déjà en cours d'exécution, comme wordpress qui déclare le /var/www/htmldossier comme un VOLUME , ce qui signifiait que tous les fichiers ajoutés ou modifiés pendant l'étape de construction n'est pas prise en compte et les changements en direct persistent, même si vous ne le savez pas. Il existe une solution de contournement moche pour définir un répertoire Web à un autre endroit, mais c'est juste une mauvaise solution à une solution plus simple: supprimez simplement la directive VOLUME.

Vous pouvez facilement atteindre l'intention du volume en utilisant l' -voption, cela non seulement indique clairement quels seront les volumes du conteneur (sans avoir à jeter un coup d'œil au Dockerfile et aux Dockerfiles parent), mais cela donne également au consommateur la possibilité de utiliser le volume ou non.

Il est fondamentalement mauvais d'utiliser VOLUMES pour les raisons suivantes, comme le dit cette réponse :

Cependant, l'instruction VOLUME a un coût.

  • Les utilisateurs peuvent ne pas être conscients des volumes sans nom en cours de création et continuer à occuper de l'espace de stockage sur leur hôte Docker après la suppression des conteneurs.
  • Il n'existe aucun moyen de supprimer un volume déclaré dans un Dockerfile. Les images en aval ne peuvent pas ajouter de données aux chemins où existent des volumes.

Ce dernier problème entraîne des problèmes comme ceux-ci.

Avoir la possibilité d' annuler la déclaration d' un volume aiderait, mais seulement si vous connaissez les volumes définis dans le fichier docker qui a généré l'image (et les fichiers docker parent!). De plus, un VOLUME pourrait être ajouté dans les versions plus récentes d'un Dockerfile et casser des choses de manière inattendue pour les consommateurs de l'image.

Une autre bonne explication (à propos de l'image oracle ayant VOLUME , qui a été supprimée ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Plus de cas dans lesquels VOLUME a cassé des choses pour les gens:

Une pull request pour ajouter des options pour réinitialiser les propriétés de l'image parente (y compris VOLUME), a été fermée et est en cours de discussion ici (et vous pouvez voir plusieurs cas de personnes affectées négativement en raison des volumes définis dans les fichiers dockerfiles), qui a un commentaire avec un bon explication contre VOLUME:

L'utilisation de VOLUME dans le Dockerfile est sans valeur. Si un utilisateur a besoin de persistance, il veillera à fournir un mappage de volume lors de l'exécution du conteneur spécifié. Il était très difficile de savoir que mon problème de ne pas pouvoir définir la propriété d'un répertoire (/ var / lib / influxdb) était dû à la déclaration VOLUME dans le Dockerfile d'InfluxDB. Sans l'option de type UNVOLUME, ou sans m'en débarrasser complètement, je ne peux rien changer au dossier spécifié. Ceci est loin d'être idéal, en particulier lorsque vous êtes conscient de la sécurité et que vous souhaitez spécifier un certain UID, l'image doit être exécutée, afin d'éviter qu'un utilisateur aléatoire, avec plus d'autorisations que nécessaire, exécute un logiciel sur votre hôte.

Je considère également EXPOSE comme mauvais, mais il a moins d'effets secondaires. La seule bonne chose que je puisse voir à propos de VOLUME et EXPOSE concerne la documentation, et je les considérerais comme bonnes si elles ne servaient que pour cela (sans aucun effet secondaire).

TL; DR

Je considère que la meilleure utilisation de VOLUME est d'être obsolète.

Lucas Basquerotto
la source