Comprensione dell'istruzione "VOLUME" in DockerFile


136

Di seguito è riportato il contenuto del mio "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" ]

In questo file mi aspetto che le istruzioni "VOLUME. / Usr / src / app" per montare i contenuti della directory di lavoro presente nell'host siano montate sulla cartella / usr / src / app del contenitore.

Per favore fatemi sapere se questo è il modo corretto?

Risposte:


88

Il tutorial ufficiale sulla docker dice:

Un volume di dati è una directory appositamente designata all'interno di uno o più contenitori che aggira il file system Union. I volumi di dati offrono diverse utili funzioni per dati persistenti o condivisi:

  • I volumi vengono inizializzati quando viene creato un contenitore. Se l'immagine di base del contenitore contiene dati nel punto di montaggio specificato,
    i dati esistenti vengono copiati nel nuovo volume dopo l'
    inizializzazione del volume . (Notare che ciò non si applica quando si monta una
    directory host .)
  • I volumi di dati possono essere condivisi e riutilizzati tra i contenitori.

  • Le modifiche a un volume di dati vengono apportate direttamente.

  • Le modifiche a un volume di dati non verranno incluse quando si aggiorna un'immagine.

  • I volumi di dati persistono anche se il contenitore stesso viene eliminato.

In Dockerfileè possibile specificare solo la destinazione di un volume all'interno di un contenitore. es /usr/src/app.

Quando si esegue un contenitore, ad esempio docker run --volume=/opt:/usr/src/app my_image, è possibile ma non è necessario specificare il punto di montaggio ( / opt ) sul computer host. Se non si specifica l' --volumeargomento, il punto di montaggio verrà scelto automaticamente, in genere sotto /var/lib/docker/volumes/.


277

In breve: No, le tue VOLUMEistruzioni non sono corrette.

I file Docker VOLUMEspecificano uno o più volumi dati percorsi lato contenitore. Ma non consente all'autore dell'immagine di specificare un percorso host. Sul lato host, i volumi vengono creati con un nome simile a ID molto lungo all'interno della radice Docker. Sulla mia macchina questo è /var/lib/docker/volumes.

Nota: poiché il nome generato automaticamente è estremamente lungo e non ha senso dal punto di vista di un essere umano, questi volumi sono spesso definiti "senza nome" o "anonimi".

Il tuo esempio che utilizza un '.' il personaggio non funzionerà nemmeno sulla mia macchina, non importa se faccio del punto il primo o il secondo argomento. Ricevo questo messaggio di errore:

finestra mobile: Risposta di errore dal demone: errore di runtime oci: container_linux.go: 265: l'avvio del processo del contenitore ha causato "process_linux.go: 368: init contenitore ha causato \" open / dev / ptmx: nessun file o directory \ "".

So che ciò che è stato detto fino a questo punto probabilmente non è molto prezioso per qualcuno che cerca di capire VOLUMEe -vcertamente non fornisce una soluzione per ciò che si tenta di realizzare. Quindi, si spera, i seguenti esempi faranno ancora più luce su questi problemi.

Minitutorial: specificare i volumi

Dato questo Dockerfile:

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

(Per il risultato di questo minitutivo, non fa alcuna differenza se specifichiamo vol1 vol2o /vol1 /vol2- non chiedetemi perché)

Costruiscilo:

docker build -t my-openjdk

Correre:

docker run --rm -it my-openjdk

All'interno del contenitore, esegui lsdalla riga di comando e noterai che esistono due directory; /vol1e /vol2.

L'esecuzione del contenitore crea anche due directory, o "volumi", sul lato host.

Mentre il contenitore è in esecuzione, eseguilo docker volume lssul computer host e vedrai qualcosa del genere (per brevità ho sostituito la parte centrale del nome con tre punti):

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

Torna nel contenitore , esegui touch /vol1/weird-ass-file(crea un file vuoto in detta posizione).

Questo file è ora disponibile sul computer host, in uno dei volumi senza nome lol. Mi ci sono voluti due tentativi perché ho provato prima il primo volume elencato, ma alla fine ho trovato il mio file nel secondo volume elencato, usando questo comando sul computer host:

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

Allo stesso modo, puoi provare a eliminare questo file sull'host e verrà eliminato anche nel contenitore.

Nota: la _datacartella viene anche definita "punto di montaggio".

Esci dal contenitore ed elenca i volumi sull'host. Se ne sono andati. Abbiamo utilizzato il --rmflag durante l'esecuzione del contenitore e questa opzione cancella efficacemente non solo il contenitore all'uscita, ma anche i volumi.

Esegui un nuovo contenitore, ma specifica un volume usando -v:

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

Questo aggiunge un terzo volume e l'intero sistema finisce per avere tre volumi senza nome. Il comando si sarebbe arrestato in modo anomalo se avessimo specificato solo -v vol3. L'argomento deve essere un percorso assoluto all'interno del contenitore. Sul lato host, il nuovo terzo volume è anonimo e risiede insieme agli altri due volumi in /var/lib/docker/volumes/.

In precedenza era stato affermato che Dockerfilenon è possibile eseguire il mapping a un percorso host che può rappresentare un problema per noi quando si tenta di portare i file dall'host al contenitore durante il runtime. Una -vsintassi diversa risolve questo problema.

Immagina di avere una sottocartella nella directory del mio progetto ./srcche desidero sincronizzare /srcall'interno del contenitore. Questo comando fa il trucco:

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

Entrambi i lati del :personaggio si aspettano un percorso assoluto. Il lato sinistro è un percorso assoluto sulla macchina host, il lato destro è un percorso assoluto all'interno del contenitore. pwdè un comando che "stampa la directory corrente / di lavoro". Inserire il comando $()prende il comando tra parentesi, lo esegue in una subshell e restituisce il percorso assoluto alla nostra directory di progetto.

Mettendo tutto insieme, supponiamo che abbiamo ./src/Hello.javanella nostra cartella del progetto sul computer host con i seguenti contenuti:

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

Costruiamo questo Dockerfile:

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

Eseguiamo questo comando:

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

Questo stampa "Ciao, mondo!".

La parte migliore è che siamo completamente liberi di modificare il file .java con un nuovo messaggio per un altro output in una seconda esecuzione - senza dover ricostruire l'immagine =)

Osservazioni finali

Sono abbastanza nuovo su Docker e il summenzionato "tutorial" riflette le informazioni che ho raccolto da un hackathon della riga di comando di 3 giorni. Mi vergogno quasi di non essere stato in grado di fornire collegamenti a una chiara documentazione di tipo inglese a supporto delle mie dichiarazioni, ma sinceramente penso che ciò sia dovuto alla mancanza di documentazione e non allo sforzo personale. So che gli esempi funzionano come pubblicizzato usando la mia configurazione attuale che è "Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce".

Il tutorial non risolve il problema "come possiamo specificare il percorso del container nel Dockerfile e lasciare che il comando run specifichi solo il percorso host". Potrebbe esserci un modo, semplicemente non l'ho trovato.

Infine, ho la sensazione che specificare VOLUMEnel Dockerfile non sia solo raro, ma è probabilmente una buona pratica da non usare mai VOLUME. Per due motivi. Il primo motivo che abbiamo già identificato: non è possibile specificare il percorso host, il che è positivo perché i file Docker dovrebbero essere molto agnostici rispetto alle specifiche di una macchina host. Ma il secondo motivo è che le persone potrebbero dimenticare di utilizzare l' --rmopzione durante l'esecuzione del contenitore. Si potrebbe ricordare di rimuovere il contenitore ma dimenticare di rimuovere il volume. Inoltre, anche con il meglio della memoria umana, potrebbe essere un compito scoraggiante capire quale di tutti i volumi anonimi è sicuro da rimuovere.


2
Quando dovremmo usare volumi anonimi / anonimi?
Searene,

10
@Martin grazie mille. Il tuo hackathon e il suo tutorial risultante qui sono molto apprezzati.
Beezer,

6
"Non sono stato in grado di fornire collegamenti per cancellare la documentazione in stile inglese ... Onestamente penso che ciò sia dovuto alla mancanza di documentazione". Posso confermare. Questa è la documentazione più completa e aggiornata che ho trovato e ho cercato per ore.
user697576,

4
docker volume prunepuò essere utilizzato per ripulire i volumi rimanenti che non sono collegati ai contenitori in esecuzione. Per non dire che sarà facile distinguere quelli potenzialmente importanti solo con il solo ID ...
Jeremy,

4
"Per il risultato di questo minitutivo, non fa alcuna differenza se si specifica vol1 vol2 o / vol1 / vol2 - non chiedermi perché". @MartinAndersson è perché l'attuale directory di lavoro è /, quindi vol1è relativa /, a cui si risolve /vol1. Se si utilizza WORKDIRper specificare una directory di lavoro diversa /, vol1e /vol1non è più indicherebbe nella stessa directory.
sebastian

41

Specificare una VOLUMElinea in un Dockerfile configura un po 'di metadati sull'immagine, ma è importante il modo in cui tali metadati vengono utilizzati.

Innanzitutto, cosa hanno fatto queste due linee:

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

La WORKDIRlinea lì crea la directory se non esiste e aggiorna alcuni metadati dell'immagine per specificare tutti i percorsi relativi, insieme alla directory corrente per comandi come RUNsarà in quella posizione. La VOLUMEriga specifica due volumi , uno è il percorso relativo .e l'altro è /usr/src/app, entrambi si trovano nella stessa directory. Molto spesso la VOLUMEriga contiene solo una singola directory, ma può contenere multipli come hai fatto, oppure può essere un array formattato json.

Non è possibile specificare un'origine volume nel Dockerfile : una fonte comune di confusione quando si specificano i volumi in un Dockerfile sta cercando di far corrispondere la sintassi di runtime di un'origine e destinazione al momento della creazione dell'immagine, ciò non funzionerà . Dockerfile può solo specificare la destinazione del volume. Sarebbe un banale exploit di sicurezza se qualcuno potesse definire l'origine di un volume poiché potrebbe aggiornare un'immagine comune sull'hub docker per montare la directory principale nel contenitore e quindi avviare un processo in background all'interno del contenitore come parte di un punto di accesso che aggiunge accessi a / etc / passwd, configura systemd per avviare un minatore bitcoin al successivo riavvio o cerca nel file system carte di credito, SSN e chiavi private da inviare a un sito remoto.

Cosa fa la linea VOLUME? Come accennato, imposta alcuni metadati dell'immagine per dire che una directory all'interno dell'immagine è un volume. Come vengono utilizzati questi metadati? Ogni volta che crei un contenitore da questa immagine, la finestra mobile imporrà a quella directory di essere un volume. Se non si fornisce un volume nel comando di esecuzione o non si compone il file, l'unica opzione per la finestra mobile è quella di creare un volume anonimo. Questo è un volume con nome locale con un lungo ID univoco per il nome e nessun'altra indicazione del motivo per cui è stato creato o quali dati contiene (i volumi anonimi sono dove i dati vanno persi). Se si sovrascrive il volume, indicando un volume con nome o host, i dati andranno invece lì.

VOLUME rompe le cose: non è possibile disabilitare un volume una volta definito in un file Docker. E, soprattutto, il RUNcomando nella finestra mobile è implementato con contenitori temporanei. Quei contenitori temporanei avranno un volume anonimo temporaneo. Quel volume anonimo verrà inizializzato con il contenuto della tua immagine. Qualsiasi scrittura all'interno del contenitore dal tuo RUNcomando verrà effettuata su quel volume. Al termine del RUNcomando, le modifiche all'immagine vengono salvate e le modifiche al volume anonimo vengono eliminate. Per questo VOLUMEmotivo , consiglio vivamente di non definire un file Docker. Il risultato è un comportamento imprevisto per gli utenti a valle dell'immagine che desiderano estendere l'immagine con i dati iniziali nella posizione del volume.

Come dovresti specificare un volume? Per specificare dove si desidera includere i volumi con l'immagine, fornire a docker-compose.yml. Gli utenti possono modificarlo per adattare la posizione del volume al proprio ambiente locale e acquisiscono altre impostazioni di runtime come porte di pubblicazione e reti.

Qualcuno dovrebbe documentarlo! Loro hanno. Docker include avvisi sull'utilizzo di VOLUME nella loro documentazione sul file Docker insieme a consigli per specificare l'origine in fase di esecuzione:

  • Modifica del volume dall'interno del Dockerfile: se qualche passaggio di compilazione modifica i dati all'interno del volume dopo che è stato dichiarato, tali modifiche verranno ignorate.

...

  • La directory host viene dichiarata in fase di esecuzione del contenitore: la directory host (mountpoint) è, per sua natura, dipendente dall'host. Questo per preservare la portabilità dell'immagine, poiché non è possibile garantire la disponibilità di una determinata directory host su tutti gli host. Per questo motivo, non è possibile montare una directory host all'interno del Dockerfile. L' VOLUME istruzione non supporta la specifica di un host-dirparametro. È necessario specificare il mountpoint quando si crea o si esegue il contenitore.

36

Il VOLUMEcomando in a Dockerfileè abbastanza legittimo, totalmente convenzionale, assolutamente perfetto da usare e non è in alcun modo deprecato. Devo solo capirlo.

Lo usiamo per indicare tutte le directory in cui l'app nel contenitore scriverà molto. Non usiamoVOLUME solo perché vogliamo condividere tra host e container come un file di configurazione.

Il comando necessita semplicemente di un parametro; un percorso di una cartella, relativamente a WORKDIRse impostato, dall'interno del contenitore. Quindi la finestra mobile creerà un volume nel suo grafico (/ var / lib / docker) e lo monterà nella cartella nel contenitore. Ora il contenitore avrà un posto dove scrivere con alte prestazioni. Senza il VOLUMEcomando la velocità di scrittura nella cartella specificata sarà molto bassa perché ora il contenitore sta usando la sua copy on writestrategia nel contenitore stesso. La copy on writestrategia è una delle ragioni principali per cui esistono volumi.

Se si monta sulla cartella specificata dal VOLUMEcomando, il comando non viene mai eseguito perché VOLUMEviene eseguito solo all'avvio del contenitore, in qualche modo simile ENV.

Fondamentalmente con il VOLUMEcomando si ottengono prestazioni senza montare all'esterno alcun volume. Anche i dati verranno salvati su esecuzioni di container senza montaggi esterni. Quindi, quando sei pronto, monta semplicemente qualcosa su di esso.

Alcuni buoni esempi di utilizzo:
- registri
- cartelle temporanee

Alcuni casi di cattivo uso:
- file statici
- configurazioni
- codice


2
Per quanto riguarda i casi d'uso positivi e negativi, la pagina "best practice" di Dockerfile di Docker dice: "Sei fortemente incoraggiato ad usare VOLUME per qualsiasi parte mutabile e / o riparabile dall'utente della tua immagine". Penso che ci siano config.
OmerSch,

2
È OK essere espliciti sulle VOLUMEdirectory per le configurazioni. Tuttavia, una volta che hai effettivamente montato una configurazione, dovrai montare quella directory e quindi il VOLUMEcomando non verrà eseguito. Pertanto è inutile utilizzare il VOLUMEcomando su una directory specificata per una configurazione. Anche l'inizializzazione di un grafico del volume con un singolo file di sola lettura statica è un grave sovraccarico. Quindi resto fedele a quello che ho detto, senza bisogno di un VOLUMEcomando su configs.
sig. Rifugio

I volumi possono presentare caratteristiche prestazionali diverse a causa dei dettagli di implementazione. I file di dati del database si adattano a questo caso d'uso, ma quale sarebbe lo scopo di archiviare i dati insieme allo storage (effimero) del contenitore? Vale a dire che l'attribuzione dei volumi all'esecuzione non è corretta.
André Werlang,

33

Per comprendere meglio le volumeistruzioni nel file docker, impariamo il tipico utilizzo del volume nell'implementazione ufficiale del file docker mysql.

VOLUME /var/lib/mysql

Riferimento: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

Il /var/lib/mysqlpercorso predefinito di MySQL in cui sono archiviati i file di dati.

Quando si esegue il contenitore di test solo a scopo di test, non è possibile specificare il punto di montaggio, ad es

docker run mysql:8

quindi l'istanza del contenitore mysql utilizzerà il percorso di montaggio predefinito specificato volumedall'istruzione in dockerfile. i volumi vengono creati con un nome simile a ID molto lungo all'interno della radice Docker, questo volume viene chiamato volume "senza nome" o "anonimo". Nella cartella del sistema host sottostante / var / lib / docker / volumi.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Questo è molto comodo per scopi di test rapidi senza la necessità di specificare il punto di montaggio, ma può comunque ottenere le migliori prestazioni usando Volume per l'archivio dati, non il livello contenitore.

Per un uso formale, sarà necessario specificare il percorso di montaggio utilizzando il volume denominato o bind mount, ad es

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

Il comando monta la directory / my / own / datadir dal sistema host sottostante come / var / lib / mysql all'interno del contenitore. La directory di dati / my / own / datadir non verrà eliminata automaticamente, anche il contenitore verrà eliminato.

Utilizzo dell'immagine ufficiale mysql (consultare la sezione "Dove archiviare i dati"):

Riferimento: https://hub.docker.com/_/mysql/


2
Mi piace molto la tua spiegazione.
Lukasz Taraszka,

La finestra mobile salva comunque le modifiche. Inoltre è possibile impostare il percorso di montaggio -vutilizzarlo senza impostare il volume nel Dockerfile
Alex78191

1

In ogni caso, non considero buono l'uso di VOLUME, tranne se si sta creando un'immagine per se stessi e nessun altro la utilizzerà.

Sono stato influenzato negativamente a causa di VOLUME esposto nelle immagini di base che ho esteso e ho scoperto il problema solo dopo che l'immagine era già in esecuzione, come wordpress che dichiara la /var/www/htmlcartella come VOLUME , e questo significava che qualsiasi file aggiunto o modificato durante la fase di compilazione non viene considerata e le modifiche live persistono, anche se non lo sai. C'è una brutta soluzione per definire la directory web in un altro posto, ma questa è solo una cattiva soluzione per una directory più semplice: basta rimuovere la direttiva VOLUME.

Puoi raggiungere facilmente l'intento del volume usando l' -vopzione, questo non solo chiarisce quali saranno i volumi del contenitore (senza dover dare un'occhiata al Dockerfile e ai Dockerfile padre), ma questo dà anche al consumatore la possibilità di usa il volume o no.

Fondamentalmente è male usare VOLUMES per i seguenti motivi, come detto da questa risposta :

Tuttavia, l'istruzione VOLUME ha un costo.

  • Gli utenti potrebbero non essere a conoscenza dei volumi senza nome che vengono creati e che continuano a occupare spazio di archiviazione sull'host Docker dopo la rimozione dei contenitori.
  • Non è possibile rimuovere un volume dichiarato in un file Docker. Le immagini a valle non possono aggiungere dati ai percorsi in cui esistono volumi.

Quest'ultimo problema comporta problemi come questi.

Avere la possibilità di annullare la dichiarazione di un volume sarebbe di aiuto, ma solo se si conoscono i volumi definiti nel file docker che ha generato l'immagine (e i file docker padre!). Inoltre, un VOLUME potrebbe essere aggiunto nelle versioni più recenti di un Dockerfile e spezzare le cose inaspettatamente per i consumatori dell'immagine.

Un'altra buona spiegazione ( sull'immagine dell'oracolo con VOLUME , che è stata rimossa ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Altri casi in cui VOLUME ha rotto cose per le persone:

Una richiesta pull per aggiungere opzioni per ripristinare le proprietà dell'immagine principale (incluso VOLUME), è stata chiusa e viene discussa qui (e puoi vedere diversi casi di persone colpite negativamente a causa di volumi definiti in file docker), che ha un commento con una buona spiegazione contro VOLUME:

L'uso di VOLUME nel Dockerfile è inutile. Se un utente ha bisogno di persistenza, sarà sicuro di fornire una mappatura del volume quando esegue il contenitore specificato. È stato molto difficile rintracciare che il mio problema di non poter impostare la proprietà di una directory (/ var / lib / influxdb) era dovuto alla dichiarazione VOLUME nel Dockerfile di InfluxDB. Senza un tipo di opzione UNVOLUME, o eliminarlo del tutto, non sono in grado di modificare nulla relativo alla cartella specificata. Questo non è l'ideale, specialmente quando si è consapevoli della sicurezza e si desidera specificare un determinato UID, l'immagine deve essere eseguita come, al fine di evitare un utente casuale, con più autorizzazioni del necessario, eseguendo software sul proprio host.

Considero anche ESPOSI male, ma ha meno effetti collaterali. L'unica cosa positiva che posso vedere su VOLUME ed EXPOSE riguarda la documentazione e li considererei buoni se servissero solo per quello (senza effetti collaterali).

TL; DR

Ritengo che l'uso migliore di VOLUME sia deprecato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.