Docker
Introduzione
Docker è un progetto open source nato con lo scopo di automatizzare la distribuzione di applicazioni sotto forma di “contenitori” leggeri, portabili e autosufficienti che possono essere eseguiti su cloud (pubblici o privati) o in locale.
I “contenitori” di Docker, d’ora in poi chiamati con il termine inglese Container, sono quindi l’insieme dei dati di cui necessita un’applicazione per essere eseguita: librerie, altri eseguibili, rami del file system, file di configurazione, script, ecc..
Il processo di distribuzione di un’applicazione si riduce quindi alla semplice creazione di una Immagine Docker, ovvero di un file contenente tutti i dati di cui sopra. L’immagine viene utilizzata da Docker per creare un Container, un’istanza dell’immagine che eseguirà l’applicazione in essa contenuta.
I container sono quindi autosufficienti perché contengono già tutte le dipendenze dell’applicazione e non richiedono quindi particolari configurazioni sull’host, ma sono soprattuto portabili perché essi sono distribuiti in un formato standard (le immagini appunto), che può essere letto ed eseguito da qualunque server Docker.
Inoltre, i container sono leggeri perché sfruttano i servizi offerti dal kernel del sistema operativo ospitante, invece di richiedere l’esecuzione di un kernel virtualizzato come avviene nel caso delle VM. Ciò permette di ospitare un gran numero di container nella stessa macchina fisica.
Data la sua stretta relazione con il kernel Linux, Docker è nativamente disponibile in ambiente GNU/Linux. Tuttavia, essendo uno strumento concepito per gli sviluppatori, si è resa necessaria la possibilità di eseguirlo direttamente sulle workstation, senza imporre limiti sul sistema operativo in uso. Docker è pertanto disponibile anche in ambiente macOS e Windows. In entrambi i casi, si ricorre ad una tecnologia di virtualizzazione per eseguire un kernel Linux al quale si appoggia l’engine Docker.
Funzionamento
Il modello di esecuzione basato su container si fonda su una serie di funzionalità sviluppate nel kernel Linux e successivamente adottate anche da altre piattaforme, espressamente studiate per questo scopo.
Un sistema basato su container inteso come alternativa alla virtualizzazione deve garantire prestazioni superiori pur offrendo le stesse caratteristiche in termini di flessibilità nella gestione delle risorse e di sicurezza.
L’incremento prestazionale è dovuto essenzialmente all’eliminazione di uno strato: a differenza di quanto avviene quando un processo viene eseguito all’interno di una macchina virtuale, i processi eseguiti da un container sono di fatto eseguiti dal sistema ospitante, usufruendo dei servizi offerti dal kernel che esso esegue. Viene quindi eliminato l’overhead dovuto all’esecuzione di ogni singolo kernel di ogni VM.
I requisiti di flessibilità e sicurezza in un sistema virtualizzato sono a carico dell’Hypervisor, lo strato software in esecuzione nella macchina ospitante che si occupa di gestire le risorse allocate a ciascuna VM e che adotta (anche con l’ausilio dell’hardware) tutte le politiche necessarie per isolare i processi in esecuzione su VM differenti.
In un ambiente basato su container, dove quindi non è presente un Hypervisor, queste funzionalità sono assolte dal kernel del sistema operativo ospitante. Linux dispone di due caratteristiche progettate proprio per questo scopo: Control Groups (o cgroups) e Namespaces.
I control groups (cgroups)
I control groups sono lo strumento utilizzato dal kernel Linux per gestire l’utilizzo delle risorse di calcolo da parte di un gruppo specifico di processi. Grazie ai cgroups è possibile limitare la quantità di risorse utilizzate da uno o più processi. Ad esempio, è possibile limitare il quantitativo massimo di memoria RAM che un gruppo di processi può utilizzare.
In un sistema Linux esistono più cgroups, ciascuno associato ad uno o più resource controllers (talvolta chiamati anche subsystems), in base alle risorse che gestiscono. Ad esempio, un cgroup può essere associato al resource controller memory per gestire la quantità di memoria allocabile da un certo insieme di processi.
I cgroup sono organizzati in modo gerarchico in una struttura ad albero. Ogni nodo dell’albero rappresenta una gruppo, definito dalle regole che gestiscono la risorsa a cui è associato (ad esempio la dimensione massima di memoria allocabile) e dalla lista dei processi che ne fanno parte. Ciascun processo in quella lista risponderà alle regole definite nel gruppo.
Si può interagire manualmente con i cgroup attraverso il filesystem virtuale /sys. I cgroup attualmente in uso dal kernel sono accessibili come subdirectory di /sys/fs/cgroup. Per creare un nuovo cgroup è sufficiente creare una subdirectory in quel ramo del filesystem.
Ad esempio, si supponga di voler limitare la memoria massima allocabile da un processo, il cui PID è 1234, a 100 MB. Per far ciò è possibile creare un nuovo cgroup sotto memory, impostare il limite, ed aggiungere il processo al cgroup, con i comandi:
mkdir /sys/fs/cgroup/memory/miocgroup
echo 104857600 > /sys/fs/cgroup/memory/miocgroup/memory.limit_in_bytes
echo 1234 > /sys/fs/cgroup/memory/miocgroup/cgroup.procs