web-dev-qa-db-fra.com

Comprendre l’instruction "VOLUME" dans DockerFile

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?

78
refactor

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 offrent plusieurs fonctionnalités utiles pour les données persistantes ou partagées:

  • Les volumes sont initialisés lorsqu'un conteneur est créé. Si l’image de base du conteneur contient des données au point de montage spécifié,
    les données existantes sont copiées dans le nouveau volume une fois le volume écoulé
    initialisation. (Notez que cela ne s'applique pas lors du montage d'un hôte
    annuaire.)
  • 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 lors de la mise à jour d'une image.

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

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

Lorsque vous exécutez votre conteneur, par exemple docker run --volume=/opt:/usr/src/app my_image vous peut mais n'avez pas à spécifier son point de montage (/opt ) sur la machine hôte. Si vous ne spécifiez pas l'argument --volume, le point de montage sera choisi automatiquement.

47
Bukharov Sergey

En bref: Non, votre instruction VOLUME n'est pas correcte.

VOLUME de Dockerfile spécifie un ou plusieurs volumes en fonction des chemins d'accès 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 très long de type ID à l'intérieur de la racine de Docker. Sur ma machine, il s'agit de /var/lib/docker/volumes.

Remarque: le nom généré automatiquement étant extrêmement long et dépourvu de sens du point de vue humain, ces volumes sont souvent qualifiés de "non nommés" ou d '"anonymes".

Votre exemple qui utilise un '.' Le personnage 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: oci erreur d'exécution: container_linux.go: 265: le démarrage du processus de conteneur a provoqué "process_linux.go: 368: l'initialisation du conteneur a provoqué \" ouvrir/dev/ptmx: aucun fichier ou répertoire\"".

Je sais que ce qui a été dit jusqu’à présent n’est probablement pas très utile pour quelqu'un qui essaie de comprendre VOLUME et -v et cela ne fournit certainement pas de solution à ce que vous essayez d'accomplir. Nous espérons donc que les exemples suivants jetteront un peu plus de lumière sur ces questions.

Minitutorial: Spécification de volumes

Étant donné ce Dockerfile:

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

(Pour l'issue de cette minitutoriale, cela ne change rien si nous spécifions vol1 vol2 ou /vol1 /vol2 - ne me demandez pas pourquoi.)

Construit le:

docker build -t my-openjdk

Courir:

docker run --rm -it my-openjdk

Dans le conteneur, exécutez ls dans la ligne de commande et vous remarquerez qu'il existe deux répertoires; /vol1 et /vol2.

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

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

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 vide à cet emplacement).

Ce fichier est maintenant disponible sur la machine hôte, dans l'un des volumes non nommés lol. Il m'a fallu 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é, à l'aide de 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é du conteneur.

Remarque: le dossier _data 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'indicateur --rm lors de l'exécution du conteneur et cette option supprime efficacement non seulement le conteneur à la sortie, mais également les volumes.

Exécutez un nouveau conteneur, mais spécifiez un volume à l'aide de -v:

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

Ceci ajoute un troisième volume et le système entier finit par avoir trois volumes non nommés. La commande se serait bloquée si nous n'avions spécifié que -v vol3. L'argument doit être un absol chemin à l'intérieur le conteneur. Du côté de l'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 la Dockerfile ne peut pas mapper sur un chemin d’hôte, ce qui pose un problème pour nous lorsque nous essayons d’importer des fichiers de l’hôte au conteneur pendant l’exécution. Une syntaxe différente de -v résout ce problème.

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

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

Les deux côtés du caractère : 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. pwd est 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 résumé, supposons que nous ayons ./src/Hello.Java dans 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 lançons cette commande:

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

Ceci affiche "Bonjour le monde!".

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 seconde exécution - sans avoir à reconstruire l'image =)

Remarques finales

Je suis un nouveau venu chez Docker, et le "tutoriel" susmentionné reflète les informations que j'ai recueillies lors d'un hackathon de 3 jours en ligne de commande. J'ai presque honte de ne pas avoir été en mesure de fournir des liens vers des documents en anglais qui corroborent mes déclarations, mais honnêtement, je pense que cela est dû à un manque de documentation et non à des efforts personnels. 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 didacticiel ne résout pas le problème "Comment spécifier le chemin du conteneur dans le fichier Dockerfile et laisser la commande d'exécution 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 que spécifier VOLUME dans le fichier Dockerfile n’est pas seulement inhabituel, mais c’est probablement une pratique recommandée de ne jamais utiliser VOLUME. Pour deux raisons La première raison que nous avons déjà identifiée est que nous ne pouvons pas spécifier le chemin de l'hôte - ce qui est une bonne chose, car Dockerfiles doit être très agnostique pour les spécificités d'une machine hôte. Mais la deuxième raison est que les gens peuvent oublier d’utiliser l’option --rm lors de l’exécution du conteneur. On pourrait penser à enlever le conteneur mais oublier de retirer le volume. De plus, même avec la meilleure mémoire humaine, il peut être fastidieux de déterminer lequel de tous les volumes anonymes peut être supprimé en toute sécurité.

190
Martin Andersson

La spécification d'une ligne VOLUME dans un fichier Docker configure un peu de métadonnées sur votre image, mais l'utilisation de ces métadonnées est importante.

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

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

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

Vous ne pouvez pas spécifier de source de volume dans le fichier Dockerfile : une source courante de confusion lors de la spécification de volumes dans un fichier Dockerfile tente de respecter la syntaxe d'exécution d'une source et destination au moment de la création de l’image, cela ne fonctionnera pas . Le fichier Docker 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 puisqu'il pouvait mettre à jour une image commune sur le docker hub pour monter le répertoire racine dans le conteneur, puis lancer un processus en arrière-plan à l'intérieur du conteneur dans le cadre d'un point d'entrée qui ajoute les connexions à/etc/passwd, configure systemd pour qu'il lance un mineur Bitcoin lors du prochain redémarrage, ou recherche dans le système de fichiers les cartes de crédit, les SSN et les clés privées à envoyer à un site distant.

À quoi sert la ligne VOLUME? Comme indiqué, des métadonnées d'image sont définies pour indiquer 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 ni dans le fichier de composition, la seule option pour le menu fixe est de créer un volume anonyme. Il s'agit d'un volume nommé local avec un identifiant unique long pour le nom et aucune autre indication de la raison de sa création ou des données qu'il contient (les volumes anonymes sont ceux où les données vont être perdues). Si vous substituez le volume à un volume nommé ou à un hôte, vos données y iront à la place.

VOLUME casse des choses: Vous ne pouvez pas désactiver un volume une fois défini dans un fichier Docker. Et plus important encore, la commande RUN de docker est implémentée avec des conteneurs temporaires. Ces conteneurs temporaires obtiendront un volume temporaire anonyme. Ce volume anonyme sera initialisé avec le contenu de votre image. Toute écriture dans le conteneur à partir de votre commande RUN sera effectuée sur ce volume. Lorsque la commande RUN est terminée, les modifications apportées à l'image sont enregistrées et les modifications apportées au volume anonyme sont ignorées. Pour cette raison, il est fortement déconseillé de définir un paramètre VOLUME à l'intérieur du fichier Docker. Cela entraîne 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 spécifier un volume? Pour spécifier l'emplacement où vous souhaitez inclure des volumes avec votre image, fournissez un docker-compose.yml. Les utilisateurs peuvent modifier cela pour ajuster l'emplacement du volume à leur environnement local. 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 ceci! Docker inclut des avertissements sur l'utilisation de VOLUME dans leur documentation sur le fichier Dockerfile , avec des conseils pour spécifier la source au moment de l'exécution:

  • Modification du volume depuis le fichier Dockerfile: Si des étapes de construction modifient les données du volume après leur déclaration, ces modifications sont ignorées.

...

  • Le répertoire de l'hôte est déclaré lors de l'exécution du conteneur: Le répertoire de l'hôte (le point de montage) est, par nature, dépendant de l'hôte. Il s’agit de préserver la portabilité des images, puisqu'un répertoire d’hôte donné ne peut pas être garanti sur tous les hôtes. Pour cette raison, vous ne pouvez pas monter un répertoire hôte à partir du fichier Docker. L'instruction VOLUME ne prend pas en charge la spécification d'un paramètre Host-dir. Vous devez spécifier le point de montage lorsque vous créez ou exécutez le conteneur.
15
BMitch

La commande VOLUME dans un Dockerfile est tout à fait légitime, totalement conventionnelle, tout à fait utilisable et n'est en aucun cas déconseillée. Juste besoin de le comprendre.

Nous l'utilisons pour pointer vers les répertoires sur lesquels l'application du conteneur écrira beaucoup. Nous n'utilisons pas VOLUME simplement parce que nous souhaitons partager un hôte et un conteneur comme un fichier de configuration.

La commande a simplement besoin d'un paramètre. un chemin d'accès à un dossier, relatif à WORKDIR s'il est 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 une haute performance. Sans la commande VOLUME, la vitesse d'écriture dans le dossier spécifié sera très lente car le conteneur utilise désormais la stratégie copy on write dans le conteneur lui-même. La stratégie copy on write est l'une des principales raisons pour lesquelles les volumes existent.

Si vous montez sur le dossier spécifié par la commande VOLUME, celle-ci n'est jamais exécutée car VOLUME n'est exécuté qu'au démarrage du conteneur, un peu comme ENV.

En gros, avec la commande VOLUME, vous obtenez des performances sans monter de volume en externe. Les données seront également sauvegardées dans les conteneurs sans aucun montage externe. Puis, quand 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

14
mr haven

Pour mieux comprendre l'instruction volume dans dockerfile, apprenons comment utiliser le volume typique dans l'implémentation du fichier docker officiel mysql.

VOLUME /var/lib/mysql

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

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

Lorsque vous exécutez un conteneur de test uniquement à des fins de test, 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 spécifié par l'instruction volume dans dockerfile. les volumes sont créés avec un nom très long ressemblant à un ID à l'intérieur de la racine de Docker, cela s'appelle un volume "non nommé" 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 pour des tests rapides sans qu'il soit nécessaire de spécifier le point de montage, mais permet néanmoins d'obtenir de meilleures performances en utilisant Volume pour le magasin de données, et non la couche conteneur.

Pour une utilisation formelle, vous devrez spécifier le chemin de montage en remplaçant le point de montage pour utiliser le volume nommé, par exemple.

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

La commande monte le répertoire/my/own/datadir du système hôte sous-jacent en tant que/var/lib/mysql dans le 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: Référence: https://hub.docker.com/_/mysql/

4
Li-Tian