La CLI di Docker mette a disposizione un comando per analizzare tutti i layer che compongono un’immagine.

docker image history

Attraverso questo comando è possibile visualizzare tutte le modifiche che un’immagine ha subito. Per essere precisi, va detto che non è una lista precisa di tutti i layer, alcune istruzioni (ad esempio ENV) non saranno tracciate, ma in linea di massima pensare questa lista come l’elenco dei layer è una buona approssimazione.

Credo che il miglior modo di vedere questo aspetto sia con un esempio. Prendiamo l’immagine di ubuntu.

Per scaricarla eseguiamo il comando docker image pull specificando il nome dell’immagine, in questo caso ubuntu.

docker image pull ubuntu

Nel mio caso, l’output è:

Using default tag: latest
latest: Pulling from library/ubuntu
d51af753c3d3: Pull complete 
fc878cd0a91c: Pull complete 
6154df8ff988: Pull complete 
fee5db0ff82f: Pull complete 
Digest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

Non abbiamo specificato il tag, quindi è stata scaricata la versione taggata come latest. Eseguiamo quindi il comando

docker image history ubuntu:latest

Ottenendo questo output:

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
1d622ef86b13        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   811B                
<missing>           4 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     1.01MB              
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:a58c8b447951f9e30…   72.8MB  

Osservando l’output, è possibile notare che alcuni layer lasciano la dimensione dell’immagine pressoché invariata. Il motivo è che questi ultimi aggiungono solo metadati, senza apportare modifiche ai File.

I layer di questo tipo sono facilmente identificabili poché nell’output del comando docker image history, compaiono con la colonna “SIZE” valorizzata con la stringa “0B”, che sta per zero Byte. 

Nella colonna “CREATED” si può osservare la data di creazione. In questo caso sono tutte uguali, ma potrebbe non essere sempre così.

I layer sono parte di un’immagine e vengono caricati su una sorta di repository detto registry. Quello di default per docker si chiama Docker Hub ed è raggiungibile all’indirizzo hub.docker.com. Non mi dilungo oltre sui registry, avremo modo di parlarne più in dettaglio nella lezione dedicata.

Se i layer hanno valorizzazioni diverse nella colonna CREATED, significa che hanno subito variazioni in momenti diversi.

Perché risulti più chiaro, immaginate un repository git. Ogni volta che aggiornate un file esso aggiornerà anche la propria data di ultima modifica. Tuttavia i file che non hanno subito variazioni in quel commit manterranno la data di ultima modifica originaria.

Ora dovrebbe sorgere spontanea una domanda: se parliamo di “ultima modifica”, perché la colonna si chiama “CREATED” e non “UPDATED”?

Teniamo a mente che stiamo guardando la struttura che compone l’immagine e non l’immagine come pacchetto unico.
Fatta questa premessa, la risposta è che un layer costruisce un nuovo ‘strato’ a partire dalla fusione degli ‘strati’ creati negli step precedenti. Ricordate lo Union File System?

Il codice binario deve essere ricostruito, non solo aggiornato. Da qui l’indicazione di creazione e non solo di aggiornamento. 

L’hash che vediamo in testa alla colonna IMAGE è l’hash dell’immagine di ubuntu, tant’è che se eseguiamo docker image ls, vedremo che l’IMAGE ID di ubuntu è lo stesso, ovvero 1d622ef86b13.

docker image ls | grep ubuntu

Ottenendo questo output:

ubuntu                                  latest              1d622ef86b13        4 weeks ago         73.9MB

In questo caso ho usato un grep per filtrare solo le righe dell’output che contengono la stringa ubuntu, per evitare rumore nell’output.

Per ora non mi spingerei oltre. Credo che in questa fase sia opportuno tralasciare la colonna ‘CREATED BY’, diventerà tutto più chiaro quando affronteremo il Dockerfile.

Ogni layer modifica i precedenti, ma il primo cosa modifica?

Abbiamo visto che un’immagine viene costruita a pezzi, mediante dei layer che provocano variazioni successive e gestite mediante Union File System.
Ma qual è il layer da cui tutto inizia?
Il layer in questione si chiama “scratch”. In inglese ‘from scratch’ significa da zero.
Infatti, se proviamo ad eseguire:

docker image pull scratch

vedremo che il demone restituisce un errore indicandoci che il nome ‘scratch’ è riservato.

Error response from daemon: 'scratch' is a reserved name

A dire il vero un immagine può partire anche da un’altra immagine, che può partire da un’altra immagine e così via. Ma la prima di esse partirà sempre “from-scratch”.

Ogni modifica all’immagine a partire da scratch genera un nuovo layer.
Quando parleremo di Dockerfile, costruiremo i layer di un’immagine e capiremo nel dettaglio come lavorare al meglio con essi.

Ogni layer è identificato da un hash (SHA256) univoco. Questo hash è la chiave utilizzata dalle immagini per verificare la presenza o meno dei layer di cui necessitano nella cache locale dell’host.

Questo meccanismo di cache dei layer, snellisce notevolmente le operazioni di upload e download dai registry perché coinvolgono solo i layer nuovi e non ripetono tali operazioni sui layer già presenti.
Questo meccanismo resta valido sia quando carichiamo una nuova versione di un’immagine nell’hub, sia quando la scarichiamo.


Prosegui su: Copy-on-Write (CoW)

Immagine: Viaggio foto creata da freepik – it.freepik.com