Cgroups e LXC come conoscenza propedeutica a Docker
La virtualizzazione è una metodologia che consente la suddivisione delle risorse di un computer in molteplici ambienti di esecuzione, ovvero le Virtual Machine (VM) o Virtual Environments (VEs).
Questo modo di concepire i sistemi operativi è diventato uno standard ed ha rivoluzionato il mercato dell’hosting.
Secondo la vision di neen, il Cloud rappresenta il punto più alto della tecnologia di virtualizzazione e del concetto di IaaS, esattamente come Docker è il vero abilitatore del PaaS e della virtualizzazione a livello di sistema operativo.
Le macchine virtuali di Docker vengono chiamate container e al loro interno si può inserire qualsiasi cosa; non è facile capire esattamente cosa sono i container, in quanto il livello di astrazione rispetto alla virtualizzazione normale è ancora più alto.
Cercheremo in questo articolo di spiegarlo bene partendo dalla storia della virtualizzazione e dei container.
Il PaaS si basa sulla possibilità di eseguire centinaia, migliaia di applicazioni diverse sulla stessa piattaforma; ogni applicazione è isolata, può accrescere o diminuire l’uso delle risorse, può essere creata, cancellata o restare quiescente.
Per permettere questo bisogna fornire al sistema operativo gli strumenti necessari per governare le diverse applicazioni.
In Linux questo si può ottenere con due tecnologie: kernel namespaces e cgroups.
Kernel namespaces
Il principio dei kernel namespaces si basa sulla definizione di un’etichetta (namespace), che viene associata ad un gruppo di risorse di sistema (esempio: mountpoints), al quale assegniamo uno o più processi decidendo quali risorse questi processi possono vedere.
L’utilizzo dei namespaces in Linux si basa sull’uso di tre system calls:
- syscall clone() crea processi o thread;
- syscall unshare() scollega le risorse di un processo dal padre;
- syscall setns() specifica dei namespaces.
Come sempre, tutte le syscall si possono anche chiamare con appositi comandi da shell.
Quando si clona un processo mediante l’utilizzo di clone() è possibile specificare, impostando alcuni flag, se il namespace delle varie categorie (user, net, PID, etc) debba essere o meno condiviso con il processo padre.
Per chiarire: se dichiaro che il processo figlio non deve condividere il namespace dei mountpoint, il kernel duplicherà la struttura dei filesystem visibili al processo figlio (clonato) e da quel momento in poi qualsiasi operazione di mount() e umount() effettuata dal processo figlio non avrà effetto sui filesystem visti dal processo padre.
In teoria, impostando tutti i flag nella maniera appropriata è possibile isolare il processo figlio in un ambiente separato in cui tutte le risorse del sistema operativo sono visibili solo ad esso.
La chiamata di sistema call unshare() permette di “scollegare” un set di risorse dal processo padre. È possibile ottenere lo stesso risultato ottenuto con la clone() impostando un namespace differente, ma a runtime e senza dover chiamare un’altra clone(). Questo concetto perché non tutti i flag possono essere impostati a runtime.
L’ultima chiamata call setns() è stata pensata proprio per l’utilizzo dei namespaces e ci consente di assegnare un processo a uno specifico namespace (in una delle categorie), utilizzando il descriptor del namespace (si trovano sotto /proc/[PID]/ns).
Sei categorie compongono le risorse di sistema dei namespaces:
- PID
- IPC
- UTS
- Mount points
- Network
- Users
È abbastanza facile capire a questo punto che le chiamate appena illustrate ci permettono di condividere le risorse di sistema tra i vari processi in esecuzione ed è il concetto alla base del funzionamento della virtualizzazione a container (Docker.io o LXC).
Cgroups
La possibilità di poter controllare in modo analitico le risorse assegnate a un processo o gruppo di processi è un elemento importante nella definizione di PaaS (non c’è una connessione logica tra queste parole) ed è qui che intervengono i cgroups introdotti da Google nel kernel 2.6.24.
Il funzionamento dei cgroups è molto potente e flessibile:
- si definiscono subsystem, cioè regole associate all’uso delle risorse di sistema. Ogni subsystem può avere varie regole di controllo. Possono esserci regole di limite di utilizzo e/o solo regole di logging di utilizzo. Alcuni subsystem sono disponibili per default nella distribuzione Linux scelta, altri possono essere definiti come moduli del kernel o tramite una specifica API;
- si crea una gerarchia di gruppi di ogni subsystem in modo da poter definire il comportamento dei processi al suo interno. I gruppi vengono definiti come file system montati con il particolare fstype “cgroup”;
- si assegnano i PID dei processi ai vari gruppi scrivendo l’elenco dei PID associati al gruppo in una sottodir “task” all’interno di una delle directory che definisce il gruppo.
Stacktrace ha realizzato una guida divisa in due parti che illustra le funzionalità e dimostra alcuni possibili usi con esempi pratici da provare sulla tua Linux box.
Cgroups, namespaces insieme ad altri facilitatori per sysadmin e a qualche ulteriore trucco per gestire i filesystem sono alla base della soluzione a container Docker e LXC
Conclusioni
La virtualizzazione “hypervisor-based” introduce un overhead considerevole dovuto semplicemente al dover eseguire un’immagine di sistema operativo per ogni macchina virtuale.
Per un’applicazione distribuita, capace ovvero di funzionare veramente in “Cloud”, la virtualizzazione classica ad hypervisor introduce alcuni svantaggi :
1. eseguire un sistema operativo per ottenere resource e security isolation;
2. avvio lento dovuto al boot del sistema operativo.
Con il passare degli anni, si è arrivati alla conclusione che solo la virtualizzazione a livello di sistema operativo poteva garantire un isolamento appropriato per le applicazioni che risiedono su un server.
Grazie a questa maggiore efficienza e velocità di avvio dei servizi, queste tecnologie legate ai container sostituiranno in breve le architetture basate su VMs nell’infrastruttura del Cloud Computing.
Una delle più interessanti applicazioni dell’uso dei container nel mondo del Cloud computing è Google Cloud Platform. Tutto in Google viene eseguito internamente ad un container, utilizzando tutti i benefici che questi offrono.
Google ha iniziato il suo lavoro nel mondo dei container quando è stato presentato il cgroup all‟interno del kernel Linux.
Inizialmente Google si è interessata in modo leggermente differente alla containerizzazione, ponendo l’accento sulle prestazioni piuttosto che sulla facilità d’uso. Google Cloud Platform presenta una variante a LXC nominata lmctfy, “Let me contain that for you” che è la versione open-source del container stack di Google, che fornisce le applicazioni atte alla containerizzazione di Linux; nel dettaglio è un insieme di servizi modulari basati sul modello Cloud che consentono la realizzazione sia di semplici siti web, sia di più complesse e articolate applicazioni.
Nodo centrale del Google Cloud Platform è il Compute Engine che permette di eseguire un carico di lavoro su larga scala all’interno dell’infrastruttura Google. Tale caratteristica determina in particolar modo performance, scalabilità e un alto grado di sicurezza. È possibile, inoltre, realizzare un ampio cluster che può usufruire di una forte e consistente larghezza di banda tra le macchine, connettendosi a macchine appartenenti ad altri data center e ad altri Google services, usufruendo quindi della rete Google.