Perché Docker non è una macchina virtuale

Per anni avevo sentito ripetere che Docker è una virtualizzazione leggera. È una frase comoda per iniziare, ma in realtà è fuorviante.

L’ho capito nel modo migliore: cercando di capire perché MySQL si comportasse in modo apparentemente assurdo.

Il problema

Sul mio PC era in esecuzione un container Docker con MySQL (ma non me lo ricordavo):

$ docker ps

CONTAINER ID   IMAGE       PORTS
6d36d6599ad3   mysql:8.0   0.0.0.0:3306->3306/tcp

Provando a collegarmi senza specificare l’host ottenevo un errore:

$ mysql -u root -p

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'

Specificando invece l’indirizzo IP locale tutto funzionava:

$ mysql -u root -p -h 127.0.0.1

La stessa password, lo stesso server, due comportamenti completamente diversi.

La prima falsa pista

Ho controllato i processi:

$ ps ax | grep mysql

3265 ?  Ssl  3:39 mysqld

«Perfetto» ho pensato. «C’è un processo mysqld locale. Perché allora non trova il socket?»

Ho verificato:

$ ls -l /var/run/mysqld/mysqld.sock

ls: impossibile accedere...

Il socket non esisteva.

Ed è stato proprio questo dettaglio a farmi capire cosa stava realmente succedendo.

Il punto chiave

Quel processo mysqld era sì visibile dall’host, ma apparteneva al container Docker.

Il socket Unix, invece, si trovava nel filesystem del container e non in quello dell’host.

Quando il client MySQL viene eseguito senza specificare -h, tenta automaticamente una connessione tramite socket Unix.

Il file cercato è:

/var/run/mysqld/mysqld.sock

Ma nel filesystem dell’host quel file non esiste.

Specificando invece:

mysql -h 127.0.0.1

la connessione non avviene più tramite socket, bensì tramite TCP/IP sulla porta 3306, che Docker inoltra correttamente al container.

Ed ecco la rivelazione

Mi sono reso conto che stavo ragionando come se Docker fosse una macchina virtuale.

In una macchina virtuale avrei un sistema operativo completamente separato, con un proprio kernel. I processi della VM non sarebbero visibili dall’host.

Con Docker succede qualcosa di completamente diverso.

I processi del container sono normali processi Linux eseguiti dal kernel dell’host.

Per questo motivo un semplice:

ps ax

mostra tranquillamente anche mysqld.

Ciò che Docker isola non sono i processori o il kernel, ma il punto di vista del processo: filesystem, rete, PID, utenti, hostname e altre risorse vengono separati mediante i namespace del kernel Linux.

Una rappresentazione mentale

                Kernel Linux
                     │
      ┌──────────────┼──────────────┐
      │              │              │
   Host          Container      Container
   bash            mysqld          nginx

Esiste un solo kernel.

Esiste una sola CPU.

Esiste una sola RAM.

Esistono invece più ambienti isolati all’interno dei quali i processi vengono eseguiti.

La differenza rispetto a una macchina virtuale

Con una macchina virtuale:

  • il sistema guest possiede un proprio kernel;
  • dall’host si vede solo il processo del software di virtualizzazione (QEMU, VirtualBox, VMware…).

Con Docker:

  • il kernel è condiviso;
  • i processi del container sono processi reali dell’host;
  • l’isolamento è ottenuto tramite namespace e cgroup.

Una lezione imparata sul campo

Paradossalmente, non l’ho capito leggendo la documentazione.

L’ho capito perché un semplice socket Unix non veniva trovato.

È uno di quei casi in cui un piccolo problema pratico costringe a costruirsi il modello mentale corretto. Da quel momento molti aspetti di Docker — processi, filesystem, porte, volumi e networking — diventano improvvisamente molto più chiari.

Ed è probabilmente questo il modo migliore per comprendere Docker: non come una “macchina virtuale leggera”, ma come un insieme di processi Linux che condividono lo stesso kernel e vengono isolati dal sistema operativo mediante i namespace e i cgroup.

Due parole su namespace e cgroup

Docker non introduce un nuovo meccanismo di virtualizzazione, ma sfrutta alcune funzionalità già presenti nel kernel Linux.

Namespace

I namespace forniscono l’isolamento. Consentono a un gruppo di processi di avere una propria visione del sistema.

Ad esempio:

  • PID namespace: il container vede solo i propri processi (all’interno mysqld può essere il processo con PID 1, mentre sull’host ha un PID completamente diverso).
  • Mount namespace: il container vede un filesystem separato da quello dell’host.
  • Network namespace: il container possiede una propria interfaccia di rete, un proprio indirizzo IP e una propria tabella di routing.
  • UTS namespace: il container può avere un hostname differente.
  • IPC namespace: isola memoria condivisa, semafori e altre primitive di comunicazione tra processi.

Ogni container, quindi, osserva una realtà diversa pur condividendo lo stesso kernel.

cgroup

I control group (cgroup) non isolano, ma controllano le risorse.

Permettono al kernel di limitare e monitorare quante risorse un insieme di processi può utilizzare:

  • CPU;
  • memoria RAM;
  • I/O su disco;
  • traffico di rete (in alcune configurazioni);
  • numero massimo di processi.

Ad esempio è possibile avviare un container imponendo che utilizzi al massimo 2 GB di RAM e due core della CPU. Se tenta di superare tali limiti, interviene il kernel senza influire sugli altri processi del sistema.

In altre parole, i namespace determinano l’ambiente in cui il processo vive. Ad esempio:

  • quali processi può vedere (PID namespace);
  • quale filesystem considera come radice (mount namespace);
  • quali interfacce di rete e indirizzi IP possiede (network namespace);
  • quale hostname vede (UTS namespace).

I cgroup, invece, determinano quante risorse quel processo può consumare:

  • CPU;
  • memoria RAM;
  • I/O del disco;
  • altri limiti imposti dal kernel.

È proprio la combinazione dei namespace e dei cgroup che consente a Docker di eseguire processi isolati condividendo lo stesso kernel Linux.

A mio avviso, questa è una delle idee più eleganti del kernel Linux. Quando si capisce che un PID non è assoluto ma relativo a un PID namespace, si comprende davvero come i container riescano a isolare i processi senza dover virtualizzare l’intero sistema operativo.

Fonti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.