Come funzionano le immagini Docker

Completato

Abbiamo menzionato che l'immagine del contenitore diventa l'unità usata per distribuire le applicazioni. Inoltre, abbiamo indicato che il contenitore ha un formato standardizzato, usato dai team di sviluppo e da quelli operativi.

In questa unità esamineremo le differenze tra software, pacchetti e immagini che vengono usati in Docker. La comprensione delle differenze tra questi concetti aiuterà a capire meglio come funzionano le immagini Docker.

Si vedranno inoltre brevemente i ruoli del sistema operativo in esecuzione sull'host e del sistema operativo in esecuzione nel contenitore.

Software incluso in un contenitore

Il software incluso in un contenitore non è limitato alle applicazioni create dagli sviluppatori. Quando si parla di software, si fa riferimento al codice delle applicazioni, ai pacchetti di sistema, ai file binari, alle librerie, ai file di configurazione e al sistema operativo in esecuzione nel contenitore.

Ad esempio, si supponga di sviluppare un portale per la tracciabilità degli ordini che verrà usato dai diversi punti vendita dell'azienda per cui si lavora. È necessario esaminare lo stack completo del software che eseguirà l'applicazione Web. L'applicazione che si sta creando è un'app .NET Core MVC che si prevede di distribuire usando Nginx come server proxy inverso in Ubuntu Linux. Tutti questi componenti software fanno parte dell'immagine del contenitore.

Che cos'è un'immagine di contenitore?

Un'immagine di contenitore è un pacchetto portabile che contiene software. Quando viene eseguita, l'immagine diventa un contenitore. Il contenitore è l'istanza in memoria di un'immagine.

Un'immagine di contenitore non è modificabile. Dopo aver creato un'immagine, non è possibile modificarla. L'unico modo per modificarla consiste nel crearne una nuova. In questo modo si ha la certezza che l'immagine usata nell'ambiente di produzione sia la stessa usata per lo sviluppo e il controllo di qualità.

Che cos'è il sistema operativo host?

Il sistema operativo host è il sistema operativo in cui viene eseguito il motore Docker. I contenitori Docker in esecuzione su Linux condividono il kernel del sistema operativo host. Non richiedono un sistema operativo specifico, a condizione che il file binario possa accedere direttamente al kernel del sistema operativo.

Diagram showing a Docker image with no base OS and the dependency on the host OS Kernel.

I contenitori di Windows, al contrario, richiedono uno specifico sistema operativo. Il contenitore dipende dal kernel del sistema operativo per gestire servizi quali il file system, la gestione della rete, la pianificazione dei processi e la gestione della memoria.

Che cos'è il sistema operativo del contenitore?

Il sistema operativo del contenitore è il sistema operativo che è incluso nell'immagine del contenitore. Un contenitore offre la possibilità di includere versioni diverse di sistemi operativi Linux o Windows. Questa flessibilità consente di accedere a funzionalità specifiche del sistema operativo o installare software aggiuntivi che possono essere usati dalle applicazioni.

Diagram showing a Docker image with an Ubuntu base OS and the dependency on the host OS Kernel.

Il sistema operativo del contenitore è isolato dal sistema operativo host, fornendo l'ambiente in cui viene distribuita ed eseguita l'applicazione. Oltre a garantire l'immutabilità dell'immagine, questo isolamento è indicativo del fatto che l'ambiente per l'esecuzione dell'applicazione in fase di sviluppo è identico a quello di produzione.

In questo esempio, viene usato Ubuntu Linux come sistema operativo del contenitore, che rimane invariato sia in fase di sviluppo sia in fase di produzione. L'immagine usata è sempre la stessa.

Che cos'è il file system di unificazione impilabile (Unionfs)?

Per creare immagini Docker, viene usato Unionfs. Unionfs è un file system che consente di raggruppare più directory, chiamate rami, facendole apparire come se fossero unite in un unico contenuto, anche se in realtà viene mantenuto fisicamente separato. Unionfs consente di aggiungere e rimuovere rami durante il processo di creazione del file system.

Diagram showing the stacking of layers in a Docker image created with unionfs.

Si supponga, ad esempio, di creare un'immagine per l'applicazione Web precedente. Si definirà un livello per la distribuzione Ubuntu come immagine di base nella parte superiore del file system di avvio. Si installeranno quindi Nginx e l'app Web. Con questa operazione, si definirà un altro livello per Nginx e l'app Web al di sopra dell'immagine Ubuntu originale.

Verrà quindi creato un livello scrivibile finale non appena il contenitore verrà eseguito dall'immagine. Tuttavia, questo livello non viene mantenuto quando il contenitore viene eliminato definitivamente.

Che cos'è un'immagine di base?

Un'immagine di base è un'immagine che usa l'immagine scratch di Docker. scratch è un'immagine di contenitore vuota che non crea un livello di file system. Questa immagine presuppone che l'applicazione che si eseguirà possa usare direttamente il kernel del sistema operativo host.

Che cos'è un'immagine padre?

Un'immagine padre è un'immagine di contenitore in base alla quale si creano le immagini.

Ad esempio, invece di creare un'immagine da scratch e quindi installare Ubuntu, è possibile usare un'immagine già basata su Ubuntu. È anche possibile usare un'immagine che ha già installato Nginx. Un'immagine padre include in genere un sistema operativo del contenitore.

Qual è la differenza principale tra un'immagine di base e un'immagine padre?

Entrambi i tipi consentono di creare un'immagine riutilizzabile. Tuttavia, le immagini di base consentono un maggiore controllo sui contenuti dell'immagine finale. Come si è detto in precedenza, un'immagine non è modificabile. È possibile aggiungere elementi, ma non rimuoverli.

In Windows è possibile creare solo immagini contenitore basate su immagini contenitore di base di Windows. Microsoft fornisce e gestisce queste immagini base dei contenitori Windows.

Che cos'è un Dockerfile?

Un Dockerfile è un file di testo che contiene le istruzioni usate per compilare ed eseguire un'immagine Docker. Nel file sono definiti gli aspetti seguenti dell'immagine:

  • L'immagine di base o padre usata per creare la nuova immagine
  • I comandi per aggiornare il sistema operativo di base e installare software aggiuntivo
  • Gli artefatti della compilazione da includere, ad esempio un'applicazione sviluppata
  • I servizi da esporre, ad esempio una configurazione di archiviazione e di rete
  • Il comando da eseguire all'avvio del contenitore

Di seguito questi aspetti verranno associati agli elementi di un Dockerfile di esempio. Si supponga di creare un'immagine Docker per il sito Web ASP.NET Core. Il Dockerfile potrebbe avere un aspetto simile all'esempio seguente:

# Step 1: Specify the parent image for the new image
FROM ubuntu:18.04

# Step 2: Update OS packages and install additional software
RUN apt -y update &&  apt install -y wget nginx software-properties-common apt-transport-https \
	&& wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
	&& dpkg -i packages-microsoft-prod.deb \
	&& add-apt-repository universe \
	&& apt -y update \
	&& apt install -y dotnet-sdk-3.0

# Step 3: Configure Nginx environment
CMD service nginx start

# Step 4: Configure Nginx environment
COPY ./default /etc/nginx/sites-available/default

# STEP 5: Configure work directory
WORKDIR /app

# STEP 6: Copy website code to container
COPY ./website/. .

# STEP 7: Configure network requirements
EXPOSE 80:8080

# STEP 8: Define the entry point of the process that runs in the container
ENTRYPOINT ["dotnet", "website.dll"]

Qui non verrà illustrata la specifica del Dockerfile né verrà descritto ogni comando incluso nell'esempio precedente. Tuttavia, in questo file sono presenti diversi comandi che consentono di modificare la struttura dell'immagine. Ad esempio, il comando COPY copia il contenuto da una cartella specifica nella macchina locale nell’immagine del contenitore che si sta creando.

In precedenza abbiamo menzionato che le immagini Docker usano unionfs. Ognuno di questi passaggi crea un'immagine di contenitore nella cache nel corso della compilazione dell'immagine del contenitore finale. Queste immagini temporanee vengono sovrapposte alle precedenti e presentate come singola immagine al termine di tutti i passaggi.

Si noti infine il passaggio 8. La voce ENTRYPOINT nel file indica il processo che verrà eseguito all'avvio di un contenitore da un'immagine. Se non viene specificato un ENTRYPOINT o un altro processo, Docker deduce che il contenitore non ha processi da eseguire e quindi il contenitore verrà chiuso.

Come gestire le immagini Docker

Le immagini Docker sono file di grandi dimensioni che vengono inizialmente archiviati nel PC ed è necessario disporre di strumenti specifi per gestirli.

CLI Docker e Desktop Docker consentono di gestire immagini compilandole, elencandole, rimuovendole ed eseguendole. Le immagini Docker vengono gestite tramite il client docker, che non esegue direttamente i comandi e invia tutte le query al daemon dockerd.

Qui non verranno descritti tutti i comandi del client e i relativi flag, ma verranno esaminati alcuni dei comandi usati più di frequente. La sezione Altre informazioni alla fine di questo modulo include collegamenti alla documentazione di Docker, che illustra in dettaglio tutti i comandi e i relativi flag.

Come compilare un'immagine

Per compilare le immagini Docker viene usato il comando docker build. Si supponga di usare la definizione di Dockerfile precedente per compilare un'immagine. Di seguito è riportato un esempio che mostra il comando di compilazione:

docker build -t temp-ubuntu .

Di seguito è riportato l'output generato dal comando di compilazione:

Sending build context to Docker daemon  4.69MB
Step 1/8 : FROM ubuntu:18.04
 ---> a2a15febcdf3
Step 2/8 : RUN apt -y update && apt install -y wget nginx software-properties-common apt-transport-https && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb && add-apt-repository universe && apt -y update && apt install -y dotnet-sdk-3.0
 ---> Using cache
 ---> feb452bac55a
Step 3/8 : CMD service nginx start
 ---> Using cache
 ---> ce3fd40bd13c
Step 4/8 : COPY ./default /etc/nginx/sites-available/default
 ---> 97ff0c042b03
Step 5/8 : WORKDIR /app
 ---> Running in 883f8dc5dcce
Removing intermediate container 883f8dc5dcce
 ---> 6e36758d40b1
Step 6/8 : COPY ./website/. .
 ---> bfe84cc406a4
Step 7/8 : EXPOSE 80:8080
 ---> Running in b611a87425f2
Removing intermediate container b611a87425f2
 ---> 209b54a9567f
Step 8/8 : ENTRYPOINT ["dotnet", "website.dll"]
 ---> Running in ea2efbc6c375
Removing intermediate container ea2efbc6c375
 ---> f982892ea056
Successfully built f982892ea056
Successfully tagged temp-ubuntu:latest

Non è importante comprendere il significato dell’output precedente. Si notino tuttavia i passaggi elencati. Ogni volta che viene eseguito un passaggio, viene aggiunto un nuovo livello all'immagine che si sta compilando.

Si noti inoltre che viene eseguito un certo numero di comandi per installare il software e gestire la configurazione. Nel passaggio 2, ad esempio, vengono eseguiti i comandi apt -y update e apt install -y per aggiornare il sistema operativo. Questi comandi vengono eseguiti in un contenitore in esecuzione creato per tale passaggio. Dopo l'esecuzione del comando, il contenitore intermedio viene rimosso. L'immagine sottostante memorizzata nella cache viene mantenuta nell'host di compilazione e non viene eliminata automaticamente. Questa ottimizzazione consente di assicurarsi che le immagini vengano riutilizzate nelle compilazioni successive per velocizzarne i tempi.

Che cos'è un tag di immagine?

Un tag di immagine è una stringa di testo usata per definire la versione di un'immagine.

Nell'output di esempio precedente è possibile notare l'ultimo messaggio della compilazione in cui viene confermata la corretta assegnazione del tag: "Successfully tagged temp-ubuntu: latest". Quando si compila un'immagine, si assegna un nome e facoltativamente anche un tag usando il flag di comando -t. In questo esempio, all'immagine è stato assegnato il nome usando -t temp-ubuntu, mentre al nome dell'immagine risultante è stato assegnato il tag temp-ubuntu: latest. Se non si specifica un tag, all'immagine viene assegnato automaticamente il tag latest.

A una singola immagine possono essere assegnati più tag. Per convenzione, alla versione più recente di un'immagine viene assegnato il tag latest oltre a un tag che descrive il relativo numero di versione. Quando si rilascia una nuova versione di un'immagine, è possibile riassegnare il tag "latest" per fare riferimento alla nuova immagine.

Per Windows, Microsoft non fornisce immagini del contenitore di base con il tag più recente. Per le immagini del contenitore di base di Windows, è necessario specificare un tag da usare. Ad esempio, l'immagine del contenitore di base di Windows per Server Core è mcr.microsoft.com/windows/servercore. Tra i tag ci sono ltsc2016, ltsc2019 e ltsc2022.

Di seguito è riportato un altro esempio. Si supponga di voler usare le immagini Docker per gli esempi di .NET Core. Di seguito sono elencate quattro versioni di piattaforme tra cui è possibile scegliere:

  • mcr.microsoft.com/dotnet/core/samples:dotnetapp

  • mcr.microsoft.com/dotnet/core/samples:aspnetapp

  • mcr.microsoft.com/dotnet/core/samples:wcfservice

  • mcr.microsoft.com/dotnet/core/samples:wcfclient

Nell'elenco delle immagini precedenti è possibile notare che Microsoft fornisce più esempi di .NET Core. I tag specificano a quali esempi fa riferimento l'immagine: ASP.NET, il servizio WCF e così via.

Come elencare le immagini

Il software Docker configura automaticamente un registro immagini locale nel computer. È possibile visualizzare le immagini presenti nel registro con il comando docker images.

docker images

L'output ha un aspetto simile all'esempio seguente:

REPOSITORY          TAG                     IMAGE ID            CREATED                     SIZE
tmp-ubuntu          latest             f89469694960        14 minutes ago         1.69GB
tmp-ubuntu          version-1.0        f89469694960        14 minutes ago         1.69GB
ubuntu              18.04                   a2a15febcdf3        5 weeks ago            64.2MB

Si noti che per l'immagine sono riportati i valori di Name, Tag e Image ID. Si è visto che è possibile applicare più etichette a un'immagine. L'output precedente mostra un esempio. Anche se i nomi delle immagini sono diversi, gli ID sono gli stessi.

L'ID immagine è un modo utile per identificare e gestire le immagini il cui nome o tag potrebbe essere ambiguo.

Come rimuovere un'immagine

È possibile rimuovere un'immagine dal registro Docker locale con il comando docker rmi. Ciò è utile se è necessario risparmiare spazio sul disco host del contenitore, perché i livelli dell'immagine del contenitore si aggiungono allo spazio totale disponibile.

Specificare il nome o l'ID dell'immagine da rimuovere. In questo esempio viene rimossa l'immagine per l'app Web di esempio usando il nome dell'immagine:

docker rmi temp-ubuntu:version-1.0

Un'immagine non può essere eliminata se un contenitore la usa ancora. Il comando docker rmi restituisce un messaggio di errore, in cui è indicato il contenitore che si basa sull'immagine.

In questa unità sono state esaminate le nozioni di base sulle immagini Docker ed è stato illustrato come gestire queste immagini e come eseguire un contenitore da un'immagine. Nell'unità successiva si vedrà come gestire i contenitori.

Verificare le conoscenze

1.

Docker Desktop è un'app per la creazione e la condivisione di app aggiunte a contenitori e microservizi disponibili in quali dei sistemi operativi seguenti?

2.

Qual è il comando Docker corretto per ricompilare un'immagine di contenitore?

3.

Quale delle frasi seguenti descrive con maggiore precisione un'immagine di contenitore?