Parliamo ora di quello che è il vero e proprio motore di Docker, il Docker Engine.

In questa sezione approfondiremo quella che è stata l’evoluzione del motore di Docker e delle principali componenti che lo compongono.

È possibile utilizzare Docker ad un buon livello anche senza conoscere nel dettaglio questa tematica. Incoraggio comunque a conoscere anche questo aspetto per avere un idea più chiara di cosa succede a basso livello. A me è tornato utile per risolvere alcuni bug particolarmente insidiosi.

Il motore di Docker, Docker Engine, ha la responsabilità di gestire ed eseguire i container.
Proprio come il motore di un’auto, è composto da componenti specializzate ed intercambiabili ed è aderente, dove possibile, alle specifiche definite da OCI.

I principali componenti che compongono il Docker engine sono: “Docker Client“, “Docker Daemon“, “containerd” e “runc“.

Per quanto riguarda il demone, in particolare, la storia è un po’ travagliata.
All’inizio il demone era monolitico, accorpando quindi molte funzionalità nella stessa codebase.
Esso si serviva di LXC per avere accesso alle principali primitive relative i container presenti nel kernel Linux. Questa dipendenza ha fatto nascere un problema serio: le funzionalità core di Docker erano dipendenti da un servizio di terze parti, LXC, sul quale Docker non aveva nessuna tipologia di controllo.

La soluzione è stata l’implementazione in casa un framework in grado di prendersi in carico le responsabilità che fino a quel momento erano state demandate a LXC. Questo strumento proprietario si chiama libcontainer ed è utilizzato da Docker a partire dalla versione 0.9.
Il modulo libcontainer, oltre al non essere più dipendente da LXC, è anche cross platform, quindi può venire eseguito in diverse piattaforme e non più solo su linux.

Anche il fatto di utilizzare un demone, Docker daemon, monolitico ha portato non poche problematiche, soprattutto alla luce della grande diffusione. Un codice non modulare è per definizione difficile da estendere, correggere e, più in generale, da mantenere.
Preso atto della situazione, Docker Inc. ha iniziato un refactor molto accurato, che per altro è ancora in corso. L’obiettivo è quello di scomporre il demone in moduli più piccoli, che siano specializzati, intercambiabili e soprattutto – manutenibili.

Alcune funzionalità di primo piano sono già state scorporate dal demone, quali container runtime e esecuzione dei container in moduli chiamati rispettivamente runc e containerd. Di questi ultimi parleremo tra pochissimo.

Componenti principali

Prima di spiegarle ad una ad una, cerchiamo di capire come sono in relazione tra loro aiutandoci con la seguente figura. 

Interazioni tra le componenti di Docker

CLI

La componete più in alto nella figura rappresenta il client, dove è possibile eseguire comandi della CLI (Command Line Interface) di Docker. Qui il link alla documentazione.

La CLI viene utilizzata per controllare il demone di Docker, il quale si avvale del container supervisor “containerd” per avviare, fermare o sospendere i container.
I container vengono eseguiti all’interno di un container runtime chiamato “runc”.

Per il momento lasciamo un attimo da parte “shim”, ci arriveremo tra un attimo. Per ora è importante che siano chiare le funzionalità a cui assolvono i moduli appena descritti e, dato che ci siamo, cerchiamo di capire qualcosa di più su di essi.

Runc

Runc è l’implementazione del container runtime aderente alle specifiche definite da OCI. 
Si tratta di un modulo standalone che ha il compito di creare container

Più in generale si tratta di un’interfaccia a linea di comando per libcontainer.

Containerd

Containerd gestisce invece la logica di esecuzione dei container.

Questo modulo gestisce i container ma non li crea. La loro creazione è gestita mediante l’apposito modulo runc.

Gestisce routine come start, stop, pause, rm, ed alcune altre per le quali vi lascio il link al sito ufficiale.

Come potrete notare dal sito, oltre ai container, containerd gestisce anche alcune funzionalità relative alle immagini. Questo per esigenze dettate da sistemi di Orchestration, Kubernetes in primis.
Containerd è stato sviluppato da Docker Inc. ma è stato donato a CNCF (Cloud Native Computing Foundation).

Shim

Runc crea i container, ma una volta assolto questo compito, esso termina, lasciando il container senza un processo padre.

Per prevenire ciò, viene creato un processo shim per ogni container. Il processo shim diventa il padre del container a cui è riferito.

Alcune delle responsabilità che assume inerenti al processo riferito container figlio sono, Gestione degli stream di IO e notifica degli eventi ad esso pertinenti al demone.


Prosegui su: Container – Le basi

Immagine: mrsiraphol – it.freepik.com