
Ciao, Habr! Vi presento la traduzione dell'articolo autore .
Oggi parleremo di come Docker utilizza lo spazio su disco della macchina host e vedremo come liberare questo spazio dai rifiuti di immagini e contenitori non utilizzati.

Consumo complessivo
Docker è una cosa fantastica, probabilmente oggi pochi ne dubitano. Solo pochi anni fa, questo prodotto ci ha fornito un modo completamente nuovo di costruire, consegnare e avviare qualsiasi ambiente, permettendo di risparmiare notevolmente le risorse della CPU e della memoria RAM. Oltre a questo (e per alcuni, questo sarà l'aspetto più importante) Docker ci ha permesso di semplificare e uniformare incredibilmente la gestione del ciclo di vita degli ambienti di lavoro utilizzati.
Tuttavia, per tutte queste meraviglie della vita moderna si deve pagare un prezzo. Quando lanciamo contenitori, scarichiamo o creiamo le nostre immagini, implementiamo ecosistemi complessi, dobbiamo pagare. E ciò che paghiamo, tra l'altro, è in termini di spazio su disco.
Se non ti sei mai chiesto quanto spazio sia realmente occupato sul tuo computer da Docker, potresti rimanere sconcertato dal risultato di questo comando:
$ docker system df 
Qui è mostrato l'uso del disco da parte di Docker in vari ambiti:
- immagini (images) – dimensione totale delle immagini che sono state scaricate dai repository di immagini e costruite nel tuo sistema;
- contenitori (containers) – volume totale dello spazio su disco utilizzato dai contenitori in esecuzione (si intende il volume totale degli strati in lettura-scrittura di tutti i contenitori);
- volumi locali (local volumes) – volume delle memorie locali montate sui contenitori;
- cache di build (build cache) – file temporanei generati dal processo di costruzione delle immagini (quando si utilizza lo strumento BuildKit, disponibile a partire dalla versione 18.09 di Docker).
Scommetto che già dopo questo semplice elenco hai un grande desiderio di pulire il disco dai rifiuti e restituire alla vita preziosi gigabyte (nota del traduttore: specialmente se per questi gigabyte paghi mensilmente un affitto).
Uso del disco da parte dei contenitori
Ogni volta che viene creato un contenitore sulla macchina host, nella directory /var/lib/docker vengono creati diversi file e cartelle, tra cui vale la pena segnalare i seguenti:
- La cartella /var/lib/docker/containers/ID_contenitore – quando si utilizza il driver di logging standard, è qui che vengono salvati i log degli eventi in formato JSON. Log eccessivamente dettagliati, così come log che non vengono letti o elaborati in altri modi, spesso causano il riempimento del disco.
- La cartella /var/lib/docker/overlay2 – contiene gli strati di lettura-scrittura dei contenitori (overlay2 è il driver preferito nella maggior parte delle distribuzioni Linux). Se un contenitore salva dati nel proprio file system, è proprio in questa cartella che verranno collocati.
Immaginiamo un sistema su cui è installato Docker vergine, che non è mai stato utilizzato per avviare contenitori o creare immagini. Il suo report di utilizzo dello spazio su disco apparirà così:
$ docker system df
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 0 0 0B 0B
Contenitori 0 0 0B 0B
Volumi Locali 0 0 0B 0B
Cache di Build 0 0 0B 0BAvviamo un contenitore, per esempio, NGINX:
$ docker container run --name www -d -p 8000:80 nginx:1.16Cosa succede al disco:
- le immagini (images) occupano 126 MB, è proprio quel NGINX che abbiamo avviato nel contenitore;
- i contenitori (containers) occupano ridicoli 2 byte.
$ docker system df
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 1 1 126M 0B (0%)
Contenitori 1 1 2B 0B (0%)
Volumi Locali 0 0 0B 0B
Cache di Build 0 0 0B 0BA giudicare dall'output, non abbiamo ancora spazio che possiamo recuperare. Poiché 2 byte sono assolutamente insignificanti, immaginiamo che il nostro NGINX improvvisamente scriva da qualche parte 100 Megabyte di dati e crei all'interno di sé un file test.img di quella dimensione.
$ docker exec -ti www
dd if=/dev/zero of=test.img bs=1024 count=0 seek=$[1024*100]Esploriamo di nuovo l'utilizzo dello spazio su disco sull'host. Vedremo che il contenitore (containers) occupa lì 100 Megabyte.
$ docker system df
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 1 1 126M 0B (0%)
Contenitori 1 1 104.9MB 0B (0%)
Volumi Locali 0 0 0B 0B
Cache di Build 0 0 0B 0BPenso che la tua mente inquisitiva si stia già chiedendo dove si trovi il nostro file test.img. Cerchiamo di trovarlo:
$ find /var/lib/docker -type f -name test.img
/var/lib/docker/overlay2/83f177...630078/merged/test.img
/var/lib/docker/overlay2/83f177...630078/diff/test.imgSenza entrare nei dettagli, si può notare che il file test.img è comodamente posizionato a livello di lettura-scrittura, gestito dal driver overlay2. Se fermiamo il nostro container, l'host ci farà sapere che questo spazio può, in teoria, essere liberato:
# Stopping the www container
$ docker stop www
# Visualizing the impact on the disk usage
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 1 126M 0B (0%)
Containers 1 0 104.9MB 104.9MB (100%)
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0BCome possiamo farlo? Eliminando il container, il che comporterà la pulizia dello spazio corrispondente a livello di lettura-scrittura.
Con il seguente comando puoi eliminare tutti i container installati in un colpo solo e liberare il tuo disco da tutti i file che hanno creato a livello di lettura-scrittura:
$ docker container prune
ATTENZIONE! Questo rimuoverà tutti i container fermati.
Sei sicuro di voler continuare? [y/N] y
Container eliminati:
5e7f8e5097ace9ef5518ebf0c6fc2062ff024efb495f11ccc89df21ec9b4dcc2
Spazio totale recuperato: 104,9MBQuindi, abbiamo liberato 104,9 Megabyte eliminando il container. Ma poiché non stiamo più utilizzando l'immagine scaricata in precedenza, essa diventa anch'essa un candidato per l'eliminazione e il rilascio delle nostre risorse:
$ docker system df
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 1 0 126M 126M (100%)
Container 0 0 0B 0B
Volumi Locali 0 0 0B 0B
Cache di Build 0 0 0B 0BAttenzione: fino a quando l'immagine è utilizzata da almeno un container, non sarai in grado di utilizzare questo trucco.
Il sottocomando prune che abbiamo utilizzato sopra ha effetto solo sui container fermati. Se vogliamo eliminare non solo i container fermati, ma anche quelli in esecuzione, dobbiamo usare uno di questi comandi:
# Historical command
$ docker rm -f $(docker ps –aq)
# More recent command
$ docker container rm -f $(docker container ls -aq)Nota a margine: se durante l'avvio del container si utilizza il parametro --rm, tutto lo spazio su disco che occupava sarà liberato al momento della sua fermata.
Utilizzo del disco da parte delle immagini
Qualche anno fa, la dimensione di un'immagine di alcune centinaia di megabyte era del tutto normale: l'immagine di Ubuntu pesava 600 Megabyte, mentre l'immagine di Microsoft .Net - alcuni Gigabyte. In quei tempi lontani, scaricare anche solo un'immagine poteva infliggere un grande danno al tuo spazio libero su disco, anche se condividevi i livelli tra le immagini. Oggi – grazie a Dio – le immagini pesano molto meno, ma anche in questo caso si possono rapidamente saturare le risorse disponibili se non si prendono alcune misure precauzionali.
Ci sono diversi tipi di immagini che non sono direttamente visibili all'utente finale:
- le immagini intermedie, su cui si basano altre immagini, non possono essere rimosse se si utilizzano contenitori basati su queste 'altre' immagini;
- le immagini non utilizzate sono immagini intermedie a cui non fa riferimento nessun contenitore in esecuzione e possono essere rimosse.
- Con il seguente comando puoi controllare se nella tua macchina ci sono immagini non utilizzate:
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
none none 21e658fe5351 12 minutes ago 71.3MBPuoi rimuoverle nel seguente modo:
$ docker image rm $(docker image ls -f dangling=true -q)Possiamo utilizzare anche il sottocomando prune:
$ docker image prune
ATTENZIONE! Questo rimuoverà tutte le immagini non utilizzate.
Sei sicuro di voler continuare? [y/N] y
Immagini cancellate:
cancellato: sha256:143407a3cb7efa6e95761b8cd6cea25e3f41455be6d5e7cda
cancellato: sha256:738010bda9dd34896bac9bbc77b2d60addd7738ad1a95e5cc
cancellato: sha256:fa4f0194a1eb829523ecf3bad04b4a7bdce089c8361e2c347
cancellato: sha256:c5041938bcb46f78bf2f2a7f0a0df0eea74c4555097cc9197
cancellato: sha256:5945bb6e12888cf320828e0fd00728947104da82e3eb4452f
Spazio totale recuperato: 12.9kBSe volessimo rimuovere tutte le immagini (non solo quelle non utilizzate) con un solo comando, possiamo farlo:
$ docker image rm $(docker image ls -q)Utilizzo del disco con volumi
I volumi vengono utilizzati per archiviare dati al di fuori del file system del contenitore. Ad esempio, se vogliamo conservare i risultati di un'applicazione per usarli in seguito. Un esempio comune sono i database.
Avviamo un contenitore MongoDB, montando un volume esterno rispetto al contenitore e ripristinando un backup del database (disponibile nel file bck.json):
# Running a mongo container
$ docker run --name db -v $PWD:/tmp -p 27017:27017 -d mongo:4.0
# Importing an existing backup (from a huge bck.json file)
$ docker exec -ti db mongoimport
--db 'test'
--collection 'demo'
--file /tmp/bck.json
--jsonArrayI dati si troveranno sulla macchina host nella cartella /var/lib/docker/volumes. Ma perché non a livello di lettura-scrittura del contenitore? Perché nel Dockerfile dell'immagine di MongoDB, la cartella /data/db (dove MongoDB memorizza i suoi dati per default) è definita come un volume.

Nota a margine: molte immagini che devono generare dati utilizzano volumi per preservare questi dati.
Quando ci divertiamo con MongoDB e fermiamo (o forse anche eliminiamo) il contenitore, il volume non verrà rimosso. Continuerà a occupare il nostro prezioso spazio su disco fino a quando non lo rimuoviamo esplicitamente con il seguente comando:
$ docker volume rm $(docker volume ls -q)Oppure possiamo utilizzare il già noto sottocomando prune:
$ docker volume prune
ATTENZIONE! Questo rimuoverà tutti i volumi locali non utilizzati da almeno un container.
Sei sicuro di voler continuare? [y/N] y
Volumi eliminati:
d50b6402eb75d09ec17a5f57df4ed7b520c448429f70725fc5707334e5ded4d5
8f7a16e1cf117cdfddb6a38d1f4f02b18d21a485b49037e2670753fa34d115fc
599c3dd48d529b2e105eec38537cd16dac1ae6f899a123e2a62ffac6168b2f5f
...
732e610e435c24f6acae827cd340a60ce4132387cfc512452994bc0728dd66df
9a3f39cc8bd0f9ce54dea3421193f752bda4b8846841b6d36f8ee24358a85bae
045a9b534259ec6c0318cb162b7b4fca75b553d4e86fc93faafd0e7c77c79799
c6283fe9f8d2ca105d30ecaad31868410e809aba0909b3e60d68a26e92a094da
Spazio totale recuperato: 25.82GB
luc@saturn:~$Utilizzo del disco per la cache della build delle immagini
In Docker 18.09, il processo di creazione delle immagini ha subito alcune modifiche grazie allo strumento BuildKit. Con questo strumento aumenta la velocità del processo e viene ottimizzata la gestione dello storage e della sicurezza. Qui non esamineremo tutti i dettagli di questo straordinario strumento, ci limiteremo a come influisce sull'uso dello spazio su disco.
Supponiamo di avere una semplice applicazione Node.Js:
- Il file index.js avvia un semplice server HTTP che risponde con una stringa a ogni richiesta ricevuta:
- Il file package.json definisce le dipendenze, dove viene usato solo expressjs per avviare il server HTTP:
$ cat index.js
var express = require('express');
var util = require('util');
var app = express();
app.get('/', function(req, res) {
res.setHeader('Content-Type', 'text/plain');
res.end(util.format("%s - %s", new Date(), 'Richiesta ricevuta'));
});
app.listen(process.env.PORT || 80);$ cat package.json
{
"name": "testnode",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.14.0"
}
}Il Dockerfile per la creazione dell'immagine è così:
FROM node:13-alpine
COPY package.json /app/package.json
RUN cd /app && npm install
COPY . /app/
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]Procediamo a costruire l'immagine nel modo consueto, senza utilizzare BuildKit:
$ docker build -t app:1.0 .Se controlliamo l'uso dello spazio su disco, vedremo che lo spazio è occupato solo dall'immagine di base (node:13-alpine) e dall'immagine finale (app:1.0):
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 2 0 109.3MB 109.3MB (100%)
Container 0 0 0B 0B
Volumi locali 0 0 0B 0B
Cache di build 0 0 0B 0BProcediamo a costruire la seconda versione della nostra applicazione, già utilizzando BuildKit. Per farlo, è sufficiente impostare la variabile DOCKER_BUILDKIT a 1:
$ DOCKER_BUILDKIT=1 docker build -t app:2.0 .Se adesso controlliamo l'utilizzo del disco, vedremo che ora è coinvolta la cache di build (build-cache):
$ docker system df
TIPO TOTALE ATTIVO DIMENSIONE RECUPERABILE
Immagini 2 0 109.3MB 109.3MB (100%)
Contenitori 0 0 0B 0B
Volumi Locali 0 0 0B 0B
Cache di Build 11 0 8.949kB 8.949kBPer pulirlo, utilizziamo il seguente comando:
$ docker builder prune
ATTENZIONE! Questo rimuoverà tutte le cache di build in sospeso.
Sei sicuro di voler continuare? [y/N] y
Oggetti della cache di build eliminati:
rffq7b06h9t09xe584rn4f91e
ztexgsz949ci8mx8p5tzgdzhe
3z9jeoqbbmj3eftltawvkiayi
Spazio totale recuperato: 8.949kBPulisci tutto!
Quindi, abbiamo esaminato la pulizia dello spazio su disco occupato da contenitori, immagini e volumi. La sottocomando prune ci aiuta in questo. Ma può essere utilizzato anche a livello di sistema docker, e ripulirà tutto ciò che può:
$ docker system prune
ATTENZIONE! Questo rimuoverà:
- tutti i contenitori fermi
- tutte le reti non utilizzate da almeno un contenitore
- tutte le immagini in sospeso
- tutte le cache di build in sospeso
Sei sicuro di voler continuare? [y/N]Se per qualche motivo stai risparmiando spazio su disco su una macchina con Docker, è consigliabile rendere abituale l'esecuzione periodica di questo comando.
Fonte: habr.com
