Come montare i volumi host in contenitori docker in Dockerfile durante la compilazione


236

Domanda originale: come utilizzare l'istruzione VOLUME in Dockerfile?

La vera domanda che voglio risolvere è: come montare i volumi host nei container docker in Dockerfile durante la compilazione, ovvero avere la docker run -v /export:/exportcapacità durante docker build.

Il motivo alla base, per me, è quando si costruiscono cose in Docker, non voglio che quelle ( apt-get install) cache siano bloccate in una singola docker, ma per condividerle / riutilizzarle. Questa è la ragione principale per cui sto ponendo questa domanda.

Ultimo aggiornamento:

Prima della finestra mobile v18.09, la risposta corretta dovrebbe essere quella che inizia con:

C'è un modo per montare un volume durante una compilazione, ma non coinvolge Dockerfile.

Tuttavia, quella era una risposta mal dichiarata, organizzata e supportata. Quando stavo reinstallando la mia finestra mobile contiene, mi è capitato di imbattermi nel seguente articolo:

Dockerize un servizio apt-cacher-ng
https://docs.docker.com/engine/examples/apt-cacher-ng/

Questa è la soluzione del docker per questa / mia domanda, non direttamente ma indirettamente. È il modo ortodosso che docker ci suggerisce di fare. E ammetto che è meglio di quello che stavo cercando di chiedere qui.

Un altro modo è la risposta appena accettata , ad esempio Buildkit in v18.09.

Scegli quello che preferisci.


Era: c'era stata una soluzione: il rocker, che non era di Docker, ma ora che il rocker è stato interrotto, ho riportato di nuovo la risposta su "Non possibile" .


Vecchio aggiornamento: la risposta è "Non possibile". Posso accettarlo come risposta poiché so che il problema è stato ampiamente discusso su https://github.com/docker/docker/issues/3156 . Posso capire che la portabilità è un problema di primaria importanza per gli sviluppatori docker; ma come utente docker, devo dire che sono molto deluso da questa funzione mancante. Consentitemi di chiudere il mio argomento con una citazione da una discussione di cui sopra: " Vorrei usare Gentoo come immagine di base ma sicuramente non voglio che> 1 GB di dati dell'albero di Portage si trovino in uno dei livelli una volta che l'immagine è stata creata. potrebbe avere dei bei contenitori compatti se non fosse per il gigantesco portage tree che doveva apparire nell'immagine durante l'installazione."Sì, posso usare wget o curl per scaricare tutto ciò di cui ho bisogno, ma il fatto che una semplice considerazione sulla portabilità ora mi sta costringendo a scaricare> 1 GB di Portage tree ogni volta che costruisco un'immagine di base Gentoo non è né efficiente né facile da usare. inoltre, il repository dei pacchetti SARÀ SEMPRE in / usr / portage, quindi SEMPRE PORTATILE sotto Gentoo. Ancora una volta, rispetto la decisione, ma per favore mi permetta di esprimere la mia delusione e nel frattempo. Grazie.


Domanda originale in dettaglio:

A partire dal

Condividi le directory tramite i volumi
http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/

afferma che la funzionalità dei volumi di dati "è disponibile dalla versione 1 dell'API Docker Remote". La mia finestra mobile è della versione 1.2.0, ma ho riscontrato che l'esempio fornito nell'articolo precedente non funziona:

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

Qual è il modo corretto in Dockerfile di montare volumi montati su host in contenitori docker, tramite il comando 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

Apparentemente richiesta di funzionalità più attuale (non che mi aspetto che venga implementata, ma per ogni evenienza): docker / docker # 14080
Jesse Glick

in effetti si discute molto sul fatto che non dovrebbe essere consentito collegare una directory host e una directory container durante la compilazione, ovvero qualcosa del genere VOLUME ~/host_dir ~/container_dir. La discussione è piuttosto ampia, c'è un modo breve per riassumere qual è la ragione?
Charlie Parker,

Risposte:


34

Innanzitutto, per rispondere "perché non VOLUMEfunziona?" Quando si definisce a VOLUMEnel Dockerfile, è possibile definire solo la destinazione, non l'origine del volume. Durante la compilazione, otterrai solo un volume anonimo da questo. Quel volume anonimo verrà montato ad ogni RUNcomando, prepopolato con il contenuto dell'immagine e quindi scartato alla fine del RUNcomando. Vengono salvate solo le modifiche al contenitore, non le modifiche al volume.


Da quando è stata posta questa domanda, sono state rilasciate alcune funzionalità che potrebbero essere utili. Il primo è build multistadio che consentono di creare uno spazio su disco inefficiente per il primo stadio e copiare solo l'output necessario nello stadio finale fornito. E la seconda funzionalità è Buildkit che sta cambiando radicalmente il modo in cui le immagini vengono costruite e le nuove funzionalità vengono aggiunte alla build.

Per una build multi-stage, avresti più FROMlinee, ognuna che inizia la creazione di un'immagine separata. Solo l'ultima immagine è taggata per impostazione predefinita, ma è possibile copiare file dalle fasi precedenti. L'uso standard è di avere un ambiente di compilazione per costruire un artefatto binario o di altro tipo e un ambiente di runtime come seconda fase che copia su quell'artefatto. Potresti avere:

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"]

Ciò comporterebbe una build che contiene solo il binario risultante e non la directory full / export.


Buildkit uscirà sperimentale nel 18.09. È una riprogettazione completa del processo di compilazione, inclusa la possibilità di modificare il parser frontend. Una di queste modifiche al parser ha implementato l' RUN --mountopzione che consente di montare una directory cache per i comandi di esecuzione. Ad esempio eccone uno che monta alcune delle directory debian (con una riconfigurazione dell'immagine debian, questo potrebbe velocizzare la reinstallazione dei pacchetti):

# 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

Dovresti adattare la directory della cache per qualsiasi cache dell'applicazione tu abbia, ad esempio $ HOME / .m2 per Maven o /root/.cache per Golang.


TL; DR: La risposta è qui: con quella RUN --mountsintassi, puoi anche associare mount directory di sola lettura dal contesto di compilazione. La cartella deve esistere nel contesto di compilazione e non viene mappata all'host o al client di compilazione:

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

Si noti che poiché la directory è montata dal contesto, è anche montata in sola lettura e non è possibile inviare nuovamente le modifiche all'host o al client. Quando costruisci, vorrai un'installazione 18.09 o più recente e abilitare buildkit con export DOCKER_BUILDKIT=1.

Se ricevi un errore che il flag mount non è supportato, ciò indica che o non hai abilitato buildkit con la variabile sopra, o che non hai abilitato la sintassi sperimentale con la riga di sintassi nella parte superiore del Dockerfile prima qualsiasi altra riga, compresi i commenti. Si noti che la variabile per attivare / disattivare buildkit funzionerà solo se l'installazione della docker dispone del supporto buildkit integrato, che richiede la versione 18.09 o successiva da Docker, sia sul client che sul server.


2
Sfortunatamente, su Windows Buildkit non è ancora supportato nella versione 18.09
Wesley,

1
Sembra che "armhf" non supporti neanche "mount".
Mike

2
Ricevo "Risposta di errore dal demone: Dockerfile analizza la riga di errore xx: Flag sconosciuto: mount" su OSX
ChristoKiwi

1
Il supporto per docker-compose non è ancora disponibile, ma non è necessario comporre per creare immagini. Problema da tenere traccia: github.com/moby/buildkit/issues/685
BMitch


116

Non è possibile utilizzare le VOLUMEistruzioni per dire alla finestra mobile cosa montare. Ciò spezzerebbe seriamente la portabilità. Questa istruzione indica alla finestra mobile che il contenuto di tali directory non va nelle immagini e può essere consultato da altri contenitori utilizzando il --volumes-fromparametro della riga di comando. Devi eseguire il contenitore usando -v /path/on/host:/path/in/containerper accedere alle directory dall'host.

Non è possibile montare volumi host durante la compilazione. Non esiste una build privilegiata e il montaggio dell'host potrebbe anche degradare seriamente la portabilità. Potresti provare a usare wget o curl per scaricare tutto ciò di cui hai bisogno per la build e metterlo in atto.


2
Grazie. Domanda rivista. La vera domanda che voglio risolvere è: come montare i volumi host nei container docker in Dockerfile durante la compilazione. Grazie.
xpt,

2
Non possibile. Vedi risposta rivista.
Andreas Steffan,

3
Posso apprezzare i "potenziali" effetti collaterali negativi sulla portabilità, ma esiste anche un valido caso d'uso per avere questa opzione. Nel mio caso, mi piacerebbe poter dire agli utenti di "Sposta nella directory ed esegui il comando 'docker run'" con $ (PWD) montato su una directory contenitore. $ (PWD) garantisce il mantenimento della portabilità. Anche se questo potrebbe essere un caso angolare, mi aiuterebbe immensamente dove sto distribuendo ambienti di runtime per script forniti dall'utente.
ntwrkguru,

64

AGGIORNAMENTO: Qualcuno semplicemente non accetterà no come risposta, e mi piace moltissimo, specialmente per questa domanda particolare.

BUONE NOTIZIE, c'è un modo ora -

La soluzione è Rocker: https://github.com/grammarly/rocker

John Yani ha dichiarato : "IMO, risolve tutti i punti deboli di Dockerfile, rendendolo adatto allo sviluppo".

sedia a dondolo

https://github.com/grammarly/rocker

Introducendo nuovi comandi, Rocker mira a risolvere i seguenti casi d'uso, che sono dolorosi con Docker semplice:

  1. Montare volumi riutilizzabili in fase di compilazione, quindi gli strumenti di gestione delle dipendenze possono utilizzare la cache tra build.
  2. Condividi le chiavi ssh con build (per estrarre repository privati, ecc.), Senza lasciarle nell'immagine risultante.
  3. Costruisci ed esegui un'applicazione in diverse immagini, puoi passare facilmente un artefatto da un'immagine all'altra, idealmente avere questa logica in un singolo Dockerfile.
  4. Contrassegna / invia immagini direttamente dai file Docker.
  5. Passare le variabili dal comando build della shell in modo che possano essere sostituite in un file Docker.

E altro ancora Questi sono i problemi più importanti che stavano bloccando la nostra adozione di Docker a Grammarly.

Aggiornamento: Rocker è stato sospeso, secondo il repository ufficiale del progetto su Github

All'inizio del 2018, l'ecosistema container è molto più maturo di quanto non fosse tre anni fa quando è stato avviato questo progetto. Ora, alcune delle caratteristiche fondamentali e straordinarie del rocker possono essere facilmente coperte dalla costruzione di docker o da altri strumenti ben supportati, sebbene alcune funzionalità rimangano uniche per il rocker. Vedi https://github.com/grammarly/rocker/issues/199 per maggiori dettagli.


Sto cercando di usare Rocker per risolvere il problema numero 1 ma il comando mount non funzionerà e l'immagine creata non contiene la cartella host. Il mio comando mount Dockerfile è simile a questo - MOUNT ~/code/docker-app-dev/new-editor/:/src/e il mio comando build Rocker è questo - rocker build -f Dockerfile .. Cosa sto facendo di sbagliato?
Yaron Idan,

Forse provare a utilizzare un percorso host reale? ~è un metacarattere conchiglia Bourne.
Jesse Glick,

Rocker buildnon consente le docker runopzioni della riga di comando, quindi attualmente non consente cose come --privileged.
Monty Wild,

Ciao @xpt, possiamo ottenere un altro aggiornamento dal momento che il rocker è ora fuori produzione
Shardj

Ora che il rocker è stato interrotto, torno di nuovo la risposta a "Non possibile". Vedi OP e la risposta selezionata.
xpt,

14

C'è un modo per montare un volume durante una compilazione, ma non coinvolge Dockerfile.

La tecnica sarebbe quella di creare un contenitore da qualunque base tu volessi usare (montare i tuoi volumi nel contenitore con l' -vopzione), eseguire uno script shell per fare il tuo lavoro di costruzione delle immagini, quindi eseguire il commit del contenitore come immagine quando hai finito .

Questo non solo lascerà fuori i file in eccesso che non vuoi (questo è buono anche per i file sicuri, come i file SSH), ma crea anche una singola immagine. Ha degli aspetti negativi: il comando commit non supporta tutte le istruzioni Dockerfile e non ti consente di riprenderlo quando hai interrotto se è necessario modificare lo script di compilazione.

AGGIORNARE:

Per esempio,

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

6
+1 Potresti per favore approfondire un po 'di più le istruzioni nel secondo paragrafo. Ad esempio, se la base è debian:wheezye lo script shell è build.sh, quali istruzioni specifiche si dovrebbero usare?
Drux,

6

Durante l'esecuzione del contenitore, viene creata una directory sull'host e montata nel contenitore. Puoi scoprire con quale directory si tratta

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

Se si desidera montare una directory dal proprio host all'interno del proprio contenitore, è necessario utilizzare il -vparametro e specificare la directory. Nel tuo caso questo sarebbe:

docker run -v /export:/export data

Quindi useresti la cartella hosts all'interno del tuo contenitore.


1
Grazie. Domanda rivista. La vera domanda che voglio risolvere è: come montare i volumi host nei container docker in Dockerfile durante la compilazione. Grazie.
xpt,

Ti preghiamo di non rivedere le tue domande in modo così drastico . Questo rende la mia domanda non valida anche se era perfettamente valida prima delle tue modifiche. Prendi invece una nuova domanda.
Behe,

11
Domanda originale : come utilizzare l'istruzione VOLUME in Dockerfile? È ancora all'inizio della domanda fino ad oggi. La tua risposta è stata per il tempo di esecuzione , e la mia domanda è sempre stata sul tempo di costruzione , che è ciò che Dockerfile è per.
xpt,

4

Penso che tu possa fare quello che vuoi fare eseguendo la build tramite un comando docker che viene eseguito all'interno di un contenitore docker. Vedi Docker ora può essere eseguito all'interno di Docker | Blog Docker . È stata utilizzata una tecnica come questa, ma che ha effettivamente avuto accesso alla finestra mobile esterna con un contenitore, ad esempio durante l'esplorazione di come creare il contenitore Docker più piccolo possibile | Blog di Xebia .

Un altro articolo rilevante è l' ottimizzazione delle immagini Docker | CenturyLink Labs , che spiega che se si finisce per scaricare materiale durante una build, è possibile evitare di sprecare spazio da esso nell'immagine finale scaricando, creando ed eliminando il download in un unico passaggio RUN.


3

È brutto, ma ho ottenuto una parvenza di questo in questo modo:

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"

Ho una build java che scarica l'universo in /root/.m2 e lo ha fatto ogni volta . imageBuild.shcopia il contenuto di quella cartella sull'host dopo il build e Dockerfileli copia nell'immagine per il build successivo.

Questo è qualcosa come il funzionamento di un volume (cioè persiste tra le build).


Questa è una soluzione praticabile per l'integrazione continua basata su Docker, nota anche come CI. Configura le librerie e i compilatori ed esegui make tramite i comandi Dockerfile, avvia banalmente l'immagine solo per creare un contenitore e infine copia l'artefatto desiderato come un .deb. Sembra funzionare, grazie per aver pubblicato questo.
chrisinmtown,

Questa soluzione ti lascia un'immagine con TUTTI i file in ./m2/ - quello che ti serve e quello che non ti serve - e questo può portare a immagini di produzione ENORME, che non è desiderato! Con il montaggio nella directory delle dipendenze esterne, solo i file necessari verranno copiati nell'immagine.
Marko Krajnc,

Se hai intenzione di pubblicare l'immagine è probabilmente meglio aspettare e lasciare che Mavven scarichi nuovamente le sue dipendenze ogni volta. Questo hack ha senso solo se stai mettendo in scena un'immagine per il test - un'immagine con cui gli utenti finali non entreranno mai in contatto.
MatrixManAtYrService

1

Ecco una versione semplificata dell'approccio in 2 passaggi usando build e commit, senza script di shell. Implica:

  1. Costruire l'immagine parzialmente, senza volumi
  2. Esecuzione di un contenitore con volumi , apportare modifiche, quindi eseguire il commit del risultato, sostituendo il nome dell'immagine originale.

Con modifiche relativamente minori, il passaggio aggiuntivo aggiunge solo pochi secondi al tempo di creazione.

Fondamentalmente:

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

Nel mio caso d'uso voglio pre-generare un file maven toolchains.xml, ma le mie numerose installazioni JDK si trovano su un volume che non è disponibile fino al runtime. Alcune delle mie immagini non sono compatibili con tutti i JDKS, quindi ho bisogno di testare la compatibilità in fase di compilazione e popolare toolchains.xml in modo condizionale. Nota che non ho bisogno che l'immagine sia portatile, non la sto pubblicando su Docker Hub.


1

Come molti hanno già risposto, non è possibile montare volumi host durante la compilazione. Vorrei solo aggiungere un docker-composemodo, penso che sarà bello avere, principalmente per lo sviluppo / test di utilizzo

Dockerfile

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

finestra mobile-compose.yml

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

E avvia il tuo contenitore docker-compose up -d --build

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.