Comprensione dei livelli Docker


27

Abbiamo il seguente blocco nel nostro Dockerfile:

RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

Mi è stato detto che dovremmo unire questi RUNcomandi per ridurre i livelli della finestra mobile creati:

RUN yum -y update \
    && yum -y install epel-release \
    && yum -y groupinstall "Development Tools" \
    && yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

Sono molto nuovo nella finestra mobile e non sono sicuro di aver compreso appieno le differenze tra queste due versioni di specificare più comandi RUN. Quando si unirebbero i RUNcomandi in uno solo e quando avrebbe senso avere più RUNcomandi?


Risposte:


35

Un'immagine docker è in realtà un elenco collegato di livelli di filesystem. Ogni istruzione in un Dockerfile crea un livello di file system che descrive le differenze nel file system prima e dopo l'esecuzione dell'istruzione corrispondente. Il docker inspectsottocomando può essere utilizzato su un'immagine docker per rivelare la sua natura di essere un elenco collegato di livelli di filesystem.

Il numero di livelli utilizzati in un'immagine è importante

  • quando si spinge o si estraggono immagini, poiché influisce sul numero di upload o download simultanei che si verificano.
  • quando si avvia un contenitore, poiché i livelli vengono combinati insieme per produrre il filesystem utilizzato nel contenitore; più livelli sono coinvolti, peggiore è la prestazione, ma i diversi backend del filesystem ne sono influenzati in modo diverso.

Ciò ha diverse conseguenze su come dovrebbero essere costruite le immagini. Il primo e più importante consiglio che posso dare è:

Consiglio n. 1 Assicurati che i passaggi di compilazione in cui è coinvolto il tuo codice sorgente arrivino il più tardi possibile nel Dockerfile e non siano collegati a comandi precedenti usando a &&o a ;.

La ragione di ciò è che tutti i passaggi precedenti verranno memorizzati nella cache e i livelli corrispondenti non dovranno essere scaricati più e più volte. Ciò significa build più veloci e versioni più veloci, che è probabilmente quello che vuoi. È interessante notare che è sorprendentemente difficile fare un uso ottimale della cache docker.

Il mio secondo consiglio è meno importante ma lo trovo molto utile dal punto di vista della manutenzione:

Consiglio n. 2 Non scrivere comandi complessi nel Dockerfile ma piuttosto usare script che devono essere copiati ed eseguiti.

Un file Docker che segue questo consiglio sarebbe simile

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh

e così via. Il consiglio di associare diversi comandi con &&ha solo un ambito limitato. È molto più facile scrivere con gli script, dove è possibile utilizzare le funzioni, ecc. Per evitare ridondanza o per scopi di documentazione.

Persone interessate ai pre-processori e disposte a evitare il piccolo sovraccarico causato dai COPYpassaggi e che in realtà stanno generando al volo un Dockerfile in cui il

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh

le sequenze sono sostituite da

RUN base64 --decode … | sh -x

dove è la versione codificata in base64 di apt_setup.sh.

Il mio terzo consiglio è per le persone che vogliono limitare le dimensioni e il numero di livelli al possibile costo di build più lunghe.

Consiglio n. 3 Usare with-idiom per evitare i file presenti nei livelli intermedi ma non nel file system risultante.

Un file aggiunto da alcune istruzioni della finestra mobile e rimosso da alcune istruzioni successive non è presente nel file system risultante ma viene menzionato due volte negli strati della finestra mobile che costituisce l'immagine della finestra mobile nella costruzione. Una volta, con il nome e il contenuto completo nel livello risultante dall'istruzione aggiungendolo, e una volta come avviso di cancellazione nel livello risultante dall'istruzione rimuovendolo.

Ad esempio, supponiamo che abbiamo temporaneamente bisogno di un compilatore C e di qualche immagine e consideriamo il file

# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc

(Un esempio più realistico costruirebbe un software con il compilatore invece di affermare semplicemente la sua presenza con la --versionbandiera.)

Lo snippet Dockerfile crea tre livelli, il primo contiene la suite gcc completa in modo che anche se non è presente nel filesystem finale i dati corrispondenti fanno ancora parte dell'immagine allo stesso modo e devono essere scaricati, caricati e decompressi ogni volta che l'immagine finale è.

Il with-idiom è una forma comune di programmazione funzionale per isolare la proprietà delle risorse e delle risorse liberando dalla logica utilizzarlo. È facile trasporre questo linguaggio allo shell-scripting e possiamo riformulare i comandi precedenti come il seguente script, da usare con COPY & RUNcome nel Consiglio n. 2.

# with_c_compiler SIMPLE-COMMAND
#  Execute SIMPLE-COMMAND in a sub-shell with gcc being available.

with_c_compiler()
(
    set -e
    apt-get install -y gcc
    "$@"
    trap 'apt-get --purge autoremove -y gcc' EXIT
)

with_c_compiler\
    gcc --version

Comandi complessi possono essere trasformati in funzioni in modo che possano essere inviati al with_c_compiler. È anche possibile concatenare chiamate di più with_whateverfunzioni, ma forse non è molto desiderabile. (Usando più funzioni esoteriche della shell, è certamente possibile fare with_c_compileraccettare comandi complessi, ma è sotto tutti gli aspetti preferibile avvolgere questi comandi complessi in funzioni.)

Se vogliamo ignorare il Consiglio n. 2, lo snippet del file Docker risultante sarebbe

RUN apt-get install -y gcc\
 && gcc --version\
 && apt-get --purge autoremove -y gcc

che non è così facile da leggere e mantenere a causa dell'offuscamento. Guarda come la variante shell-script enfatizza la parte importante gcc --versionmentre la &&variante concatenata seppellisce quella parte nel mezzo del rumore.


1
Potresti includere il risultato della dimensione della casella dopo aver compilato usando uno script e usando i comandi multipli in un'istruzione RUN?
030

1
Mi sembra una cattiva idea mescolare la configurazione della base di immagini (cioè le cose del sistema operativo) e persino le librerie con la configurazione della fonte che hai scritto. Dici "Assicurati che i passaggi di compilazione in cui è coinvolto il tuo codice sorgente arrivi il più tardi possibile". C'è qualche problema nel rendere quella parte un artefatto completamente indipendente?
JimmyJames,

1
@ 030 Cosa intendi con dimensione "scatola"? Non ho idea di quale scatola ti riferisca.
Michael Le Barbier Grünewald,

1
Intendevo la dimensione dell'immagine della finestra mobile
030

1
@JimmyJames Dipende molto dallo scenario di implementazione. Se assumiamo un programma compilato, la "cosa giusta da fare" sarebbe quella di impacchettarlo e installare le dipendenze di quel pacchetto e il pacchetto stesso come due distinti passi quasi definitivi. Questo per massimizzare l'utilità della cache docker ed evitare di scaricare più e più livelli con gli stessi file. Trovo più facile condividere le ricette di costruzione per costruire immagini docker che costruire lunghe catene di immagini di dipendenza, perché quest'ultima rende più difficile la ricostruzione.
Michael Le Barbier Grünewald,

13

Ogni istruzione creata nel Dockerfile comporta la creazione di un nuovo livello immagine. Ogni livello porta dati aggiuntivi che non fanno sempre parte dell'immagine risultante. Ad esempio, se aggiungi un file in un livello, ma lo rimuovi in ​​un altro livello in un secondo momento, la dimensione dell'immagine finale includerà la dimensione del file aggiunto in una forma di un file "whiteout" speciale sebbene sia stato rimosso.

Supponiamo che tu abbia il seguente Dockerfile:

FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release

Le dimensioni dell'immagine risultante saranno

bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB

Al contrario, con Dockerfile "simile":

FROM centos:6

RUN yum -y update  && yum -y install epel-release

Le dimensioni dell'immagine risultante saranno

smallimage     latest        7edeafc01ffe        3 minutes ago    384MB

Otterrai dimensioni ancora più ridotte, se pulisci la cache yum in una singola istruzione RUN.

Quindi vuoi mantenere l'equilibrio tra leggibilità / facilità di manutenzione e numero di strati / dimensioni dell'immagine.


4

Le RUNdichiarazioni rappresentano ciascuno un livello. Immagina che uno scarichi un pacchetto, lo installi e desideri rimuoverlo. Se si utilizzano tre RUNistruzioni, la dimensione dell'immagine non si ridurrà in quanto vi sono livelli separati. Se si eseguono tutti i comandi utilizzando RUNun'istruzione, la dimensione dell'immagine del disco potrebbe essere ridotta.

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.