Block Image

Cos'è un container

Un container può essere definito come un processo isolato.
Vengono utilizzate delle funzionalità del kernel Linux come cgroup e namespace per consentire a questi processi (i container) di essere indipendenti con gli altri processi e con altri container stessi anche se vengono eseguiti sulla stessa macchina.

Virtual Machine vs Container

Una macchia virtuale (VM) è una virtualizzazione dell'hardware del computer host affinché si possa eseguire un nuovo sistema operativo (un nuovo kernel).
Se si sceglie di utilizzare questa tecnologia per isolare le varie applicazioni in produzione, sarà necessario creare più VM.
Le macchine virtuali sono create grazie ad un software del sistema operativo host, chiamato hypervisor o anche virtual machine monitor (VVM).
L'hypervisor quindi ha il compito di creare ed eseguire VM.

Un container invece è semplicemente un processo isolato che condivide con il sistema host il suo kernel. Di conseguenza, un container è molto più leggero di una VM (ordine di MB vs ordine di GB). Un'altra conseguenza è che il processo di import/export di un container su una nuova macchina è molto più veloce rispetto a quello su una VM.
Di base, ogni nostra applicazione sarà un container.

Qui un disegno che riassume il concetto scritto sopra:

Block Image

Virtual Machine e Container: l'uno esclude l'altro?

No, anzi, spesso in produzione vengono eseguiti container all'interno di VM.

Cos'è Docker

Docker è una tecnologia che permette la creazione di container e la gestione del loro ciclo di vita (crearli, avviarli, spegnerli, distruggerli...).

Il motore che fa funzionare Docker è chiamato Docker Engine.
Esso è basato sull'architettura client-server, dove il server è il demone Docker chiamato dockerd, che permette di effettuare le varie operazioni sui container, mentre il client è la CLI, che tramite le API di Docker interagisce col demone Docker, attraverso un canale di comunicazione di tipo socket Unix (il demone Docker è in ascolto su /var/run/docker.sock).

È possibile avere client e server sulla stessa macchina, ma anche in macchine diverse. Oltre alla CLI, possiamo avere anche interfacce grafiche (ad esempio Portainer) come client Docker.

Immagini per creare container

Un'immagine è un template di sola lettura che contiene delle istruzioni per creare container.

Facendo un paragone con la programmazione ad oggetti, potremmo associare le immagini alle classi, mentre i container agli oggetti. Dalla stessa immagine si possono creare più container (così come dalla stessa classe si possono creare più oggetti).

Un'immagine è basata da più layers di sola lettura. La creazione di un container, crea un ulteriore layer, chiamato "container layer", che è l'unico scrivibile (e non è persistente).
Inoltre, spesso un'immagine è basata da una o più immagini. Ad esempio potresti creare un'immagine della tua applicazione partendo da una immagine ubuntu.

Si possono creare immagini custom tramite un file chiamato Dockerfile (vedremo nell'articolo successivo come si usa), oppure si possono scaricare immagini da un registry, come il Docker Hub, che è il registro di default.

Nel prossimo articolo, parlando del Dockerfile, approfondiremo meglio la parte dei layers.

Installazione di Docker

Abbiamo detto che i container sfruttano delle features del kernel Linux, quindi consiglio di provare Docker su una distro Linux, anche in VM.
Altrimenti è possibile installare Docker Desktop per Windows e MacOS, che permette di eseguire Docker in maniera virtualizzata.

A questo link trovate la guida ufficiale per installare Docker su tutti i sistemi operativi: https://docs.docker.com/engine/install/.

Se utilizzate Docker Desktop per Windows, consiglio di scaricare anche dallo Store l'applicazione Ubuntu (o simile), e poi abilitare l'integrazione su Docker Desktop dalle opzioni di quest'ultimo, in modo tale da poter utilizzare i comandi come se fossimo su Linux.

Block Image

Creiamo un container Ubuntu: i comandi pull e run

Per verificare che l'installazione sia andata a buon fine, eseguiamo da terminale:
docker version
In risposta dovremmo avere le info sul client Docker e sul server Docker.
Proviamo ora a scaricare l'immagine di Ubuntu eseguendo questo comando:
docker pull ubuntu
dovremmo avere una risposta simile:

Using default tag: latest
latest: Pulling from library/ubuntu
35807b77a593: Pull complete 
Digest: sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31f
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

L'immagine, non essendo presente in locale, viene scaricata dal registry di default, il Docker Hub (Downloaded newer image). Inoltre da notare il tag latest; se non specifichiamo un tag, Docker di default scarica quella con tag latest.

Il tag indica la versione dell'immagine. Non per forza corrisponde alla versione dell'applicativo. Si consiglia di non utilizzare il tag latest, essendo che punta alla versione ultima (ma non è scontato, è responsabilità dello sviluppatore dell'immagine) di una immagine in quel preciso momento. È sempre buona norma scaricare l'immagine con una precisa versione, per evitare problemi di compatibilità.

Per vedere la lista delle immagini scaricate, possiamo eseguire il comando:
docker images

Block Image

Per creare un container a partire da un'immagine, dobbiamo utilizzare il comando run seguito dal nome dell'immagine o dall'image id:
docker run ubuntu

Vediamo ora la lista di container attivi:
docker ps

La lista è vuota! Vediamo allora la lista di tutti i container, anche quelli non attivi:
docker ps -a

Block Image

Lo stato del container è Exited; questo perché i container eseguono un comando principale (con PID 1), in questo caso "bash", poi terminato il comando, si stoppano.

Eliminamo il container appena creato con il comando rm seguito dal nome del container o dal container id:
docker rm priceless_hellman

Il nome del container, non avendolo specificato durante il comando run, viene scelto da Docker in modo casuale.

Ora creiamo nuovamente il container in questo modo:
docker run --name ubuntu-container -it -d ubuntu bash

Analizziamo il comando:

  1. Con --name specifichiamo il nome del container.
  2. Con -it stiamo unendo le opzioni -i e -t. Con -i, manteniamo lo STDIN anche se non siamo in attached al container (vedremo tra poco cosa significa). Con -t colleghiamo un terminale al container.
  3. Con -d indichiamo a Docker che vogliamo eseguire il container in modalità detach, ovvero in background.
  4. L'ultimo comando, dopo il nome dell'immagine, è il comando che vogliamo eseguire, in questo caso bash.

Eseguiamo di nuovo docker ps per vedere la lista di container attivi:

Block Image

Questa volta il container è UP & Running!

Stop e Start di un container

Per stoppare un container, basta eseguire il comando stop seguito dal nome del container o dal container id:
docker stop ubuntu-container

Per avviarlo, basta eseguire il comando start:
docker start ubuntu-container

Entriamo nel container: il comando attach

Per entrare in un container, possiamo utilizzare il comando attach, seguito dal nome del container:

docker attach ubuntu-container

Ora siamo dentro il container, con l'utente root! Eseguiamo il comando ps aux per vedere quali processi sono attivi nel container:

Block Image

Ci sono due processi attivi, il primo, con PID 1, è il comando che abbiamo utilizzato durante la creazione del container, il secondo è il comando appena eseguito per vedere la lista di processi.

Ora usciamo dal container eseguendo il comando exit.
Vediamo di nuovo la lista di container attivi. Abbiamo una sorpresa: il container di ubuntu non è più attivo!

Questo perché, di default, eseguendo il comando exit, Docker uccide il processo principale, con PID 1. Il container è attivo solo se il processo con PID 1 è attivo.

Per far sì che quando siamo attaccati al container, esso non termini quando usciamo, dobbiamo premere CTRL+p e poi CTRL+q.

Eseguire comandi su un container avviato: il comando exec

Possiamo eseguire comandi su un container senza avere la necessità di entraci dentro.

Ad esempio potremmo scrivere una cosa del genere:

Block Image

Potremmo anche entrare in un container eseguendo un altro comando bash, evitando quindi il comando attach:

Block Image

Da notare che adesso i processi bash sono due. Quando usciremo dal container col comando exit, fermeremo il secondo processo bash, quindi il container continuerà ad essere UP e Running.

Conclusioni

In questo articolo introduttivo su Docker abbiamo visto rapidamente cos'è un container e qualche differenza con le VM.
Abbiamo poi visto le operazioni base di Docker per gestire il ciclo di vita di un container ed altri comandi come attach e exec.
Nel prossimo articolo vedremo come creare un'immagine custom, col Dockerfile.

Articoli su Docker: Docker