Come funzionano i contenitori Docker

Completato

In precedenza, si è scoperto che il contenitore diventa l'unità usata per distribuire le app. Si è anche appreso che il contenitore ha un formato standardizzato usato dai team di sviluppo e da quelli operativi.

Nello scenario di esempio si sta sviluppando un portale per la tracciabilità degli ordini che verrà usato dai diversi punti vendita dell'azienda. Con l'immagine Docker compilata, il team operativo ora è responsabile della distribuzione, dell'implementazione degli aggiornamenti e della gestione del portale di tracciabilità degli ordini.

Nell'unità precedente è stato descritto come viene compilata un'immagine Docker. In questa unità si esaminerà il ciclo di vita di un contenitore Docker e si vedrà come gestire i contenitori. Si vedrà anche come progettare la configurazione dell'archiviazione dei dati e delle opzioni di rete per i contenitori.

Come gestire i contenitori Docker

Un contenitore Docker ha un ciclo di vita che è possibile usare per gestire e monitorare lo stato del contenitore.

Diagram that shows the lifecycle of a container and the transition between the lifecycle phases.

Per attivare lo stato di esecuzione di un contenitore, usare il comando run. È anche possibile riavviare un contenitore già in esecuzione. Quando si riavvia un contenitore, questo riceve un segnale di terminazione che consente l'arresto corretto dei processi in esecuzione prima che il kernel del contenitore venga terminato.

Un contenitore viene considerato in stato di esecuzione fino a quando non viene sospeso, arrestato o interrotto. Tuttavia, lo stato di esecuzione di un contenitore può essere interrotto anche senza eseguire alcun comando, ad esempio quando il processo in esecuzione viene completato o se si verifica un errore del processo.

Per sospendere un contenitore in esecuzione, usare il comando pause. Questo comando sospende tutti i processi del contenitore.

Per arrestare un contenitore in esecuzione, usare il comando stop. Questo comando consente di arrestare correttamente il processo in esecuzione inviando un segnale di terminazione. Il kernel del contenitore termina dopo che il processo è stato arrestato.

Per inviare un segnale nel caso in cui sia necessario terminare il contenitore, usare il comando kill. Il segnale di terminazione non viene acquisito dal processo in esecuzione, ma solo dal kernel del contenitore. Questo comando termina quindi in modo forzato il processo di lavoro nel contenitore.

Infine, per rimuovere i contenitori che si trovano in stato arrestato, usare il comando remove. Dopo la rimozione di un contenitore, tutti i dati archiviati nel contenitore vengono eliminati definitivamente.

Come visualizzare i contenitori disponibili

Per visualizzare l'elenco dei contenitori in esecuzione, eseguire il comando docker ps. Per visualizzare tutti i contenitori in qualsiasi stato, passare l'argomento -a.

Ecco un esempio:

docker ps -a

Di seguito è riportato l'output di tale comando:

CONTAINER ID    IMAGE        COMMAND         CREATED       STATUS           PORTS        NAMES
d93d40cc1ce9    tmp-ubuntu:latest  "dotnet website.dll …"  6 seconds ago    Up 5 seconds        8080/tcp      happy_wilbur
33a6cf71f7c1    tmp-ubuntu:latest  "dotnet website.dll …"  2 hours ago     Exited (0) 9 seconds ago            adoring_borg

L'output precedente include tre elementi da esaminare:

  • Il nome dell'immagine elencato nella colonna IMAGE. In questo esempio tmp-ubuntu: latest. Si noti che è possibile creare più contenitori dalla stessa immagine. Questa è una potente funzionalità di gestione che è possibile usare per abilitare il ridimensionamento delle soluzioni.

  • Lo stato del contenitore elencato nella colonna STATUS. In questo esempio sono presenti un contenitore in esecuzione e uno che è stato terminato. Lo stato del contenitore è in genere il primo indicatore dell'integrità del contenitore.

  • Il nome del contenitore elencato nella colonna NAMES. Oltre all'ID riportato nella prima colonna, i contenitori ricevono anche un nome. In questo esempio non è stato specificato in modo esplicito un nome per ogni contenitore e, di conseguenza, Docker ha assegnato al contenitore un nome casuale. Per assegnare un nome esplicito a un contenitore usando il flag --name, usare il comando run.

Per quale motivo viene assegnato un nome ai contenitori?

Questa funzionalità consente di eseguire più istanze di contenitore della stessa immagine. I nomi dei contenitori sono univoci. In altre parole, se si specifica un nome, questo non può essere riutilizzato per creare un nuovo contenitore. L'unico modo per riutilizzare un nome specifico consiste nel rimuovere il contenitore precedente.

Come eseguire un contenitore

Per avviare un contenitore, usare il comando docker run. Per avviare il contenitore dall'immagine, è sufficiente specificare l'immagine da eseguire con il nome o l'ID. Un contenitore avviato in questo modo offre un'esperienza interattiva.

Nel codice seguente aggiungere il flag -d per eseguire il contenitore con il sito Web in background.

docker run -d tmp-ubuntu

Il comando, in questo caso, restituisce solo l'ID del nuovo contenitore.

Una volta specificata un'immagine da eseguire, Docker trova l'immagine, carica il contenitore dall'immagine ed esegue il comando specificato come punto di ingresso. Da questo momento il contenitore diventa disponibile per la gestione.

Come sospendere un contenitore

Per sospendere un contenitore, eseguire il comando docker pause. Ecco un esempio:

docker pause happy_wilbur

Se si sospende un contenitore, tutti i processi vengono sospesi. Questo comando consente al contenitore di continuare i processi in un secondo momento. Il comando docker unpause annulla la sospensione di tutti i processi nei contenitori specificati.

Come riavviare un contenitore

Per riavviare i contenitori, eseguire il comando docker restart. Ecco un esempio:

docker restart happy_wilbur

Il contenitore riceve un comando di arresto, seguito da un comando di avvio. Se il contenitore non risponde al comando di arresto, viene inviato un segnale di terminazione.

Come arrestare un contenitore

Per arrestare un contenitore in esecuzione, eseguire il comando docker stop. Ecco un esempio:

docker stop happy_wilbur

Il comando stop invia un segnale di terminazione al contenitore e ai processi in esecuzione nel contenitore.

Come rimuovere un contenitore

Per rimuovere un contenitore, eseguire il comando docker rm. Ecco un esempio:

docker rm happy_wilbur

Dopo aver rimosso il contenitore, tutti i dati nel contenitore vengono eliminati definitivamente. È essenziale considerare sempre i contenitori come risorse temporanee quando si pensa ad archiviare i dati.

Configurazione dell'archiviazione dei contenitori Docker

Come spiegato in precedenza, è sempre necessario considerare i contenitori come risorse temporanee quando l'app in un contenitore deve archiviare dati.

Supponiamo che il portale di tracciabilità degli ordini crei un file di log in una sottocartella alla radice dell'app, ovvero direttamente nel file system del contenitore. Quando l'app registra i dati nel file di log, il sistema scrive i dati nel livello scrivibile del contenitore.

Anche se questo approccio funziona, purtroppo presenta alcuni svantaggi.

  • L'archiviazione dei contenitori è temporanea.

    Il file di log non viene salvato in modo permanente tra le istanze di contenitore. Si supponga, ad esempio, di arrestare e rimuovere il contenitore. Quando si avvia una nuova istanza di contenitore, la nuova istanza si basa sull'immagine specificata e tutti i dati precedenti non saranno più presenti. Tenere presente che tutti i dati di un contenitore vengono eliminati definitivamente con il contenitore quando questo viene rimosso.

  • L'archiviazione dei contenitori è associata al computer host sottostante.

    L'accesso al file di log o lo spostamento di tale file dal contenitore non è un'operazione semplice poiché il contenitore è associato al computer host sottostante. Per accedere al file è necessario connettersi all'istanza del contenitore.

  • Le unità di archiviazione dei contenitori sono meno efficienti.

    I contenitori implementano un driver di archiviazione per consentire alle app di scrivere dati. Questo driver introduce un'astrazione aggiuntiva per comunicare con il kernel del sistema operativo host ed è meno efficiente rispetto alla scrittura diretta in un file system di host.

Per salvare i dati in modo permanente i contenitori hanno due possibilità, ovvero usare i volumi o i bind mount.

Che cos'è un volume?

Un volume viene archiviato nel file system dell'host in un percorso di cartella specifico. Scegliere una cartella in cui i dati non verranno modificati da processi non Docker.

Docker crea e gestisce il nuovo volume eseguendo il comando docker volume create. Questo comando può far parte della definizione del Dockerfile, il che significa che è possibile creare volumi come parte del processo di creazione del contenitore. Docker crea il volume se questo non esiste quando si prova a montare il volume in un contenitore per la prima volta.

I volumi vengono archiviati all'interno di directory nel file system dell'host. I volumi nel contenitore vengono montati e gestiti da Docker. Dopo il montaggio, questi volumi vengono isolati dal computer host.

Più contenitori possono usare contemporaneamente gli stessi volumi. Inoltre, i volumi non vengono rimossi automaticamente quando non sono più usati da un contenitore.

In questo scenario di esempio, è possibile creare una directory nell'host del contenitore e montare questo volume nel contenitore quando si crea il contenitore del portale di tracciabilità. Quando il portale registra i dati, è possibile accedere a queste informazioni tramite il file system dell'host del contenitore. Questo file di log sarà accessibile anche se il contenitore viene rimosso.

Docker offre anche la possibilità alle aziende di terze parti di creare componenti aggiuntivi da usare come volumi. Ad esempio, Archiviazione di Azure fornisce un plug-in per montare Archiviazione di Azure come volumi nei contenitori Docker.

Che cos'è un bind mount?

Un bind mount è concettualmente identico a un volume, ma consente di montare qualsiasi file o cartella nell'host invece di usare una cartella specifica. Si prevede che l'host possa modificare il contenuto dei bind mount. Proprio come un volume, il bind mount viene creato se si specifica di montarlo e non è già presente nell'host.

I bind mount hanno funzionalità limitate rispetto ai volumi e, anche se sono più efficienti, dipendono dal fatto che l'host abbia una specifica struttura di cartelle.

I volumi sono considerati la strategia di archiviazione dei dati preferita da usare con i contenitori.

Per i contenitori Windows è disponibile un'altra opzione: è possibile montare un percorso SMB come volume e presentarlo ai contenitori. Ciò consente ai contenitori in host diversi di usare la stessa risorsa di archiviazione permanente.

Configurazione di rete dei contenitori Docker

La configurazione di rete predefinita del Docker prevede l'isolamento dei contenitori nell'host Docker. Questa funzionalità consente di creare e configurare app in grado di comunicare tra loro in modo sicuro.

Docker offre diverse impostazioni di rete per Linux e Windows.

Per Linux, sono disponibili sei opzioni di rete preconfigurate:

  • Bridge
  • Host
  • Sovrapposizione (Overlay)
  • IPvLan
  • MACvLan
  • None

Per Windows, sono disponibili sei opzioni di rete preconfigurate:

  • NAT (Network Address Translation)
  • Trasparente
  • Sovrapposizione (Overlay)
  • L2Bridge
  • L2Tunnel
  • None

È possibile scegliere quali di queste configurazioni di rete applicare al contenitore in base ai requisiti di rete.

Che cos'è la rete bridge?

La rete bridge è la configurazione predefinita applicata ai contenitori quando vengono avviati senza specificare altre configurazioni di rete. Si tratta di una rete interna privata usata dal contenitore, che isola la rete del contenitore dalla rete host Docker.

A ogni contenitore nella rete bridge viene assegnato un indirizzo IP e una subnet mask con il nome host predefinito in base al nome del contenitore. I contenitori connessi alla rete bridge predefinita possono accedere ad altri contenitori connessi tramite bridge in base all'indirizzo IP. La rete bridge non consente la comunicazione tra i contenitori tramite nomi host.

Per impostazione predefinita, Docker non pubblica le porte dei contenitori. Per abilitare il mapping tra le porte di contenitore e le porte di host Docker, usare il flag --publish della porta Docker.

Il flag publish consente di configurare in modo efficace una regola del firewall che esegue il mapping delle porte.

In questo esempio, il portale di tracciabilità è accessibile ai client tramite la porta 80. Sarà quindi necessario eseguire il mapping della porta 80 dal contenitore a una porta disponibile sull'host. Nell'host è aperta la porta 8080, che consente di impostare il flag nel seguente modo:

--publish 8080:80

I client che accedono all'IP dell'host Docker e alla porta 8080 possono entrare nel portale di tracciabilità.

Oltre alle configurazioni specifiche di Linux, la rete NAT negli host Windows funziona come una rete bridge. Inoltre, NAT è la rete predefinita di Windows e tutti i contenitori si connetteranno a questa rete, se non diversamente specificato.

Che cos'è la rete host?

La rete host consente di eseguire il contenitore direttamente sulla rete. Questa configurazione rimuove efficacemente l'isolamento tra l'host e il contenitore a livello di rete.

In questo scenario di esempio si supponga di modificare la configurazione di rete impostando l'opzione di rete host. Il portale di tracciabilità è ancora accessibile tramite l'indirizzo IP dell'host. Ora è possibile usare la porta 80 nota, invece di usare una porta sottoposta a mapping.

Il contenitore può usare solo porte non ancora usate dall'host.

In Windows la rete host non è disponibile. Negli host Windows, non esiste un'opzione per condividere lo stesso indirizzo IP (stack di rete) tra host e contenitore. La rete NAT funziona come una rete bridge e l'opzione Overlay fornisce un indirizzo IP al contenitore dalla stessa rete dell'host, ma non lo stesso indirizzo IP.

Overlay e altre opzioni di rete

Per gli scenari più avanzati, sia Linux che Windows offrono opzioni di rete aggiuntive. Ad esempio, l'opzione Overlay crea un commutatore virtuale dalla rete host, in modo che i contenitori in tale rete possano ottenere indirizzi IP dai server DHCP o operare con indirizzi IP provenienti da quel segmento di rete. Inoltre, Docker consente a fornitori terzi di creare plugin di rete.

Che cos'è la rete none?

Per disabilitare i servizi di rete per i contenitori, usare l'opzione di rete none. Questo potrebbe essere utile se si dispone di un'applicazione che non utilizza la rete o se si vuole semplicemente verificare che un'applicazione funzioni come previsto in un contenitore.

Considerazioni sui sistemi operativi

Tenere presente che esistono differenze tra i sistemi operativi desktop per le opzioni di configurazione di rete Docker. Ad esempio, l'interfaccia di rete Docker0 non è disponibile in macOS quando si usa la rete bridge e l'uso della configurazione di rete host non è supportata per i desktop Windows e macOS.

Queste differenze possono influire sul modo in cui gli sviluppatori configurano il flusso di lavoro per gestire lo sviluppo di contenitori. Inoltre, gli agenti di orchestrazione possono fornire altre configurazioni di rete in aggiunta alla configurazione Docker.

Verificare le conoscenze

1.

Un contenitore viene avviato con il flag --publish 8080:80. Quale delle opzioni seguenti è la configurazione di rete più probabile per il contenitore?

2.

Quale opzione di archiviazione è la scelta migliore che consente all'host e al contenitore di condividere un file per gestire la risoluzione del server dei nomi, ad esempio il file resolve.conf in Linux?