Alcuni mesi fa, ho scritto un articolo sull'accelerazione delle build di container all'interno di un container. Quel l'articolo si concentrava sulla velocità di estrazione delle immagini del contenitore e sui diversi modi per precompilare l'archivio immagini, utilizzando i montaggi di volume dall'host e il concetto Buildah di "negozi aggiuntivi".
Buildah è uno strumento da riga di comando per creare immagini compatibili con Open Container Initiative (che significa anche compatibili con Docker e Kubernetes) in modo rapido e semplice. Buildah è facile da incorporare in script e creare pipeline e, soprattutto, non richiede un demone contenitore in esecuzione per creare la sua immagine.
Questo articolo affronterà un secondo problema con la velocità di compilazione quando si utilizza dnf
/yum
comandi all'interno di contenitori. Tieni presente che in questo articolo utilizzerò il nome dnf
(che è il nome a monte) invece di ciò che usano alcuni a valle (yum
) Questi commenti si applicano a entrambi dnf
e yum
.
Velocità di download
L'hai mai notato a volte quando esegui dnf -y update
o dnf -y install
per la prima volta dopo un po', il comando si interrompe per molto tempo prima ancora che inizi a ridurre gli RPM? Cosa sta succedendo?
La prima cosa che dnf
non è altro che scaricare enormi file di cache. Questi file sono scritti in XML e contengono ogni singolo pacchetto nel repository remoto, inclusi molti dati sul pacchetto. Contengono persino tutti i percorsi all'interno del pacchetto. Questi dati sono necessari in modo che quando puoi eseguire qualcosa come dnf -y install /usr/bin/httpd
e poi dnf
determina il pacchetto da installare. Molti pacchetti contengono comandi come (requires: /usr/bin/sendmail
) che sfruttano questa funzionalità, consentendo dnf
per tirare un pacchetto adeguato per soddisfare il bisogno.
L'estrazione di questi file enormi e, soprattutto, l'elaborazione di questi file può richiedere più di un minuto. Questi dati vengono utilizzati da libsolv
, quindi deve essere convertito in solv
formato, che è lento. La velocità non è un grosso problema quando lo fai solo periodicamente sul tuo host, ma quando si tratta di costruire container, questo è un affare molto più grande.
Sintassi del file Docker
Sebbene Buildah ti consenta di creare immagini di contenitori direttamente nella shell, la maggior parte delle persone utilizza Dockerfile e Containerfile per creare contenitori e definire ricette di immagini riproducibili. Buildah cerca automaticamente un Containerfile
e un Dockerfile
adesso. Ognuno condivide la stessa sintassi, quindi userò Containerfile
per il resto di questo documento.
Una cosa comune da fare in un Containerfile
è usare una sintassi come:
FROM ubi8
RUN dnf -y update; dnf -y install nginx; dnf -y clean all
…
RUN dnf -y install jboss; dnf -y clean all
Esaminiamo il dnf
Linee. Il primo dnf
riga:
(dnf -y update; dnf -y install nginx; dnf -y clean all
):
- Aggiorna tutti i pacchetti all'interno del contenitore.
- Installa il pacchetto selezionato
nginx
. - Pulisce tutto.
Dal momento che ubi8
l'immagine è stata probabilmente creata qualche tempo fa e il suo /var/cache/dnf
la directory probabilmente non esiste all'interno dell'immagine del contenitore, dnf
deve estrarre il file della cache XML ed elaborarlo. Quindi, dnf
installa i pacchetti effettivi prima di dnf -y clean all
rimuove tutti i dati in eccesso che i comandi precedenti inserivano nell'immagine, come i file di registro e di cache.
Si consiglia agli utenti di eseguire clean all
per mantenere l'immagine il più piccola possibile. Ogni RUN
il comando crea un nuovo livello e anche se rimuovi il contenuto in un RUN
successivo comando, il livello iniziale conterrà tutto il contenuto. Ciò significa che tutti coloro che estraggono la tua immagine finiranno per estrarre i registri e i file della cache. Ora, se il tuo Containerfile
contiene uno o più dnf
comandi, pagherai il prezzo più e più volte. Non solo, ma ogni volta che ricostruisci questa immagine, pagherai di nuovo quel prezzo. Se ti trovi su un server di compilazione, ogni immagine contenitore che crei scaricherà questi file XML più e più volte, sprecando tonnellate di risorse e tempo.
Buildah con cavalcature sovrapposte
Abbiamo visto il problema sopra descritto e abbiamo pensato di poterlo gestire in un modo migliore. Non potremmo semplicemente estrarre i dati XML sull'host, elaborarli sull'host e montarli in volume nei contenitori? Forse potremmo impostare un cron job o un systemd
timer che esegue una dnf makecache
una volta per ogni versione dei sistemi operativi per cui creerai immagini del contenitore? Puoi eseguire questo lavoro una o più volte al giorno sull'host e quindi fare in modo che tutti i generatori di contenitori montino le cache appropriate nei contenitori buildah.
Bene, Buildah supporta le directory di montaggio del volume dall'host ai container. Questo dovrebbe risolvere il problema, e lo fa. MA, i contenitori spesso vogliono scrivere in questa directory, se devono aggiornare la cache, quindi la cache deve essere montata nei contenitori di lettura/scrittura. Questo provoca un enorme buco di sicurezza. Immagina una situazione in cui una build di container ostile ha scritto contenuto in questa cache che un costruttore di container successivo ha letto. Potrebbe eventualmente indurre il secondo contenitore a installare software violato. Abbiamo bisogno di una soluzione in cui il contenuto è montato nel contenitore, non può essere scritto dal contenitore, ma può comunque essere scritto dalla prospettiva del contenitore. Questo è fondamentalmente ciò che è un punto di montaggio Overlay.
Il file system di overlay monta un lower
directory e quindi allega un upper
directory nel merged
punto di montaggio. Quando un processo scrive in un nuovo file nel merged
directory, il nuovo file viene scritto nella directory upper
directory. Quando un processo modifica un file esistente in lower
directory, il kernel copia il file da lower
nella directory upper
directory e consente al processo di modificare il file nella upper
directory.
Abbiamo introdotto il concetto di cavalcatura Overlay in Buildah. Ora puoi eseguire build con
buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f /tmp/Containerfile /tmp
Dnf
all'interno del contenitore verificherà comunque se sono presenti contenuti più recenti nei repository e tirerà giù il contenuto se esiste. Ma se il contenuto dell'host è aggiornato, utilizzerà rapidamente la cache dell'host. Ti consiglio di aggiornare la cache degli host almeno una volta al giorno.
Una caratteristica aggiuntiva che abbiamo aggiunto per la cavalcatura Buildah Overlay è quella di distruggere la upper
directory su ogni RUN
direttiva. Ricordiamo che nel nostro esempio abbiamo usato più RUN
comandi che eseguivano ciascuno un dnf -y clean all
. Il dnf -y clean all
comando provoca il upper
directory per mostrare tutto il contenuto dal basso come eliminato. Se il prossimo dnf
comando condiviso nella parte superiore precedente vedrebbe la cache vuota e dovrebbe estrarre il datastore XML ed elaborarlo. Rimozione della upper
directory significa che ogni dnf
il comando vedrà di nuovo il lower
directory dall'host e continuare a condividere la cache degli host.
Differenza di velocità
Creerò un semplice Containerfile
contenente due dnf
esegui i comandi.
FROM fedora:31
RUN dnf -y install net-utils; dnf -y clean all
RUN dnf -y install iputils; dnf -y clean all
Eseguirlo localmente sulla mia scatola Fedora 31
# time -f "Elapsed Time: %E" buildah bud -f Containerfile .
STEP 1: FROM fedora:31
STEP 2: RUN dnf -y install procps-ng; dnf -y clean all
Fedora Modular 31 - x86_64 2.0 MB/s | 5.2 MB 00:02
Fedora Modular 31 - x86_64 - Updates 1.6 MB/s | 4.0 MB 00:02
Fedora 31 - x86_64 - Updates 4.2 MB/s | 19 MB 00:04
Fedora 31 - x86_64 1.8 MB/s | 71 MB 00:39
Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:55:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
procps-ng x86_64 3.3.15-6.fc31 fedora 326 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 326 k
Installed size: 966 k
Downloading Packages:
procps-ng-3.3.15-6.fc31.x86_64.rpm 375 kB/s | 326 kB 00:00
--------------------------------------------------------------------------------
Total 218 kB/s | 326 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1
Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1
Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1
Installed:
procps-ng-3.3.15-6.fc31.x86_64
Complete!
33 files removed
STEP 3: RUN dnf -y install iputils; dnf -y clean all
Fedora Modular 31 - x86_64 741 kB/s | 5.2 MB 00:07
Fedora Modular 31 - x86_64 - Updates 928 kB/s | 4.0 MB 00:04
Fedora 31 - x86_64 - Updates 3.8 MB/s | 19 MB 00:05
Fedora 31 - x86_64 7.9 MB/s | 71 MB 00:08
Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:57:13 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 252 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 141 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
33 files removed
STEP 4: COMMIT
Getting image source signatures
Copying blob ac0b803c5612 skipped: already exists
Copying blob 922380d685bc done
Copying config 566e2afbb4 done
Writing manifest to image destination
Storing signatures
566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb
566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb
Elapsed Time: 2:15.00
Questa esecuzione ha richiesto 2 minuti e 15 secondi per creare una nuova immagine del contenitore con i due nuovi pacchetti.
Ora proviamo questo con un montaggio Overlay dall'host.
# dnf -y makecache
# time -f "Elapsed Time: %E" buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f Containerfile .
STEP 1: FROM fedora:31
STEP 2: RUN dnf -y install procps-ng; dnf -y clean all
Last metadata expiration check: 0:02:34 ago on Wed Feb 5 13:51:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
procps-ng x86_64 3.3.15-6.fc31 fedora 326 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 326 k
Installed size: 966 k
Downloading Packages:
procps-ng-3.3.15-6.fc31.x86_64.rpm 496 kB/s | 326 kB 00:00
--------------------------------------------------------------------------------
Total 245 kB/s | 326 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1
Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1
Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1
Installed:
procps-ng-3.3.15-6.fc31.x86_64
Complete!
285 files removed
STEP 3: RUN dnf -y install iputils; dnf -y clean all
Last metadata expiration check: 0:02:41 ago on Wed Feb 5 13:51:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 556 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 222 kB/s | 141 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
285 files removed
STEP 4: COMMIT
Getting image source signatures
Copying blob ac0b803c5612 skipped: already exists
Copying blob 524bb3b83d61 done
Copying config 0f82aa6064 done
Writing manifest to image destination
Storing signatures
0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518
0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518
Elapsed Time: 0.17.44
Con la montatura Overlay, siamo stati in grado di creare una nuova immagine con i due pacchetti aggiuntivi in 17 secondi invece di 2 minuti e 15 secondi. È quasi 8 volte più veloce creare la stessa immagine del contenitore.
Ora questo mostra che se crei immagini su un sistema operativo host che ha il dnf
metadati pre-cache puoi accelerare la velocità di installazione di una quantità ENORME. Ma cosa succede se il tuo sistema di build crea immagini per altre versioni del sistema operativo? Supponiamo di voler costruire immagini per Fedora 30 e Fedora 31. Nota:questo funzionerebbe anche su un sistema RHEL8, dove potresti voler costruire immagini RHEL7 e forse anche RHEL6.Dnf
include un'interessante funzionalità in cui puoi specificare versioni diverse durante il pull dei contenuti, utilizzando il --releasever
opzione. Dnf
ti consente anche di specificare directory alternative per posizionare la cachedir, --setopt=cachedir
.
Nell'esempio seguente, eliminerò due cache sull'host e quindi utilizzerò Buildah in modalità riga di comando.
# dnf -y makecache --releasever=31 --setopt=cachedir=/var/cache/dnf/31
# dnf -y makecache --releasever=30 --setopt=cachedir=/var/cache/dnf/30
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/31:/var/cache/dnf:O ${ctr31} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:41 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 192 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 107 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 0:06.85
# ctr30=$(buildah from fedora:30)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:47 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20180629-4.fc30 fedora 123 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 123 k
Installed size: 351 k
Downloading Packages:
iputils-20180629-4.fc30.x86_64.rpm 370 kB/s | 123 kB 00:00
--------------------------------------------------------------------------------
Total 138 kB/s | 123 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20180629-4.fc30.x86_64 1/1
Running scriptlet: iputils-20180629-4.fc30.x86_64 1/1
Verifying : iputils-20180629-4.fc30.x86_64 1/1
Installed:
iputils-20180629-4.fc30.x86_64
Complete!
Elapsed Time: 0:08.88
Come puoi vedere, siamo stati in grado di eseguire i contenitori Buildah utilizzando il dnf
cache da due diverse versioni di Fedora dallo stesso host di build e i container per Fedora 31 hanno impiegato più di 6 secondi e la build di Fedora 30 ha impiegato più di 8 secondi.
Nota:ho scelto una sottodirectory di /var/cache/dnf
per i file di cache, per assicurarsi che le etichette di SELinux fossero corrette. Basta eseguire dnf clean all
non pulirà /var/cache/dnf/31
. Dovresti eseguire dnf clean all --setopt=cachedir=/var/cache/dnf/31
per pulire correttamente i file memorizzati nella cache del repository, ma alcuni artefatti rimarranno comunque (chiavi gpg, directory vuote).
Ora, solo per vedere quanto tempo ci vorrebbe per eseguire la build su Fedora 31 senza il supporto Overlay.
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run ${ctr31} dnf -y install iputils
Fedora Modular 31 - x86_64 1.2 MB/s | 5.2 MB 00:04
Fedora Modular 31 - x86_64 - Updates 875 kB/s | 4.0 MB 00:04
Fedora 31 - x86_64 - Updates 2.4 MB/s | 19 MB 00:07
Fedora 31 - x86_64 1.7 MB/s | 71 MB 00:41
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 279 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 129 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 1:29.85
In questo caso, ci sono voluti quasi 1,5 minuti per eseguire lo stesso container. Buildah con le cavalcature Overlay è stato eseguito 14 volte più velocemente.
Contenitori senza radici
Tutti i miei esempi fino ad ora hanno eseguito le build utilizzando Containerfiles
come radice. Ma puoi farlo anche con contenitori senza radici. Nei prossimi due esempi, farò in modo che Buildah esegua i container usando buildah run
sintassi per dimostrare l'utilizzo della cache.
In esecuzione
$ buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils
funziona bene. Finché l'utente può leggere il /var/cache/dnf/30
directory, il lower
è possibile leggere la directory. Ma devi fare affidamento su qualcosa sull'host per aggiornare periodicamente la cache.
Se gli utenti lo desiderano possono persino utilizzare dnf
per creare la cache nella loro home directory.
$ dnf -y makecache --releasever=30 --setopt=cachedir=$HOME/dnfcache
$ chcon --reference /var/cache/dnf -R $HOME/dnfcache
$ ctr30=$(buildah from fedora:30)
$ buildah run -v $HOME/dnfcache:/var/cache/dnf:O ${ctr30} dnf -y install iputils
Nota che ho dovuto cambiare l'etichetta SELinux di $HOME/dnfcache
directory in modo che SELinux consenta ai contenitori di leggere il lower
directory per il montaggio Overlay.
Conclusione
L'accelerazione della compilazione dei contenitori richiede la comprensione di ciò che accade durante l'installazione dei pacchetti. Memorizzazione nella cache di dnf
dati sull'host e l'utilizzo di montaggi overlay per montare la cache nel contenitore con Buildah può aumentare notevolmente la velocità delle build e ridurre il numero di risorse necessarie per supportare una build farm.
Buildah è sinonimo di semplicità, ma ha anche alcune fantastiche funzionalità come Overlay mounts
e additional stores
che può aiutarti a velocizzare le build di immagini del contenitore.
[ Nuovo per i container? Scarica Containers Primer e impara le basi dei container Linux. ]