Genitori in esilio. Psicopatologia e migrazioni

Scuola Politecnica e delle Scienze di Base
Corso di Laurea in Ingegneria Informatica
Elaborato finale in Sistemi Operativi
Container-based OS virtualization (LXC)
Anno Accademico 2013/2014
Candidato:
Vincenzo Cristiano
matr. N46/996
1
Indice
Indice
2
Introduzione
3
Capitolo 1: Metodi di Virtualizzazione & Progetti
4
1.1 Virtualizzazione a livello di OS (Virtualizzazione basata su Container)
6
1.2 Hypervisor vs Container
7
1.3 Progetti Collegati
8
1.3.1 OpenVZ
8
1.3.2 Docker
8
1.3.3 CRIU
Capitolo 2: Skills & Funzionamento
11
12
2.1 Fault Isolation
13
2.2 Resource Isolation
13
2.3 Security Isolation
13
2.4 LinuX Container (LXC)
15
2.4.1 CGroups (Control Groups)
16
2.4.2 Isolamento Namespace
19
2.4.3 I/O Scheduling : CFQ
23
2.4.4 CPU Scheduling: CFS
25
Capitolo 3: Scenari d’uso
28
3.1 Cloud Computing
28
Conclusioni
31
Bibliografia
32
2
Introduzione
La virtualizzazione è una metodologia che consente la suddivisione delle
risorse di un computer in molteplici ambienti di esecuzioni, le Virtual
Machine (VM) o Virtual Environments (VEs). Garantendo, in questo modo:

risparmi di costi dovuti alla riduzione dell‟hardware, non è più
necessario avere una macchina fisica per ogni applicazione;

crescente affidabilità del sistema, si riducono notevolmente i tempi di
ripristino in caso di guasti. È presente, grazie a questa tecnica, una
gestione differente assimilabile a quella dei file, i quali, sono
facilmente trasportabili;

ambienti di test sicuri, evitando la possibilità di imbattersi in problemi
scaturiti dall‟installazione di nuove applicazioni. [1]
In questo testo ci occuperemo in particolar modo della virtualizzazione a
livello di sistema operativo, meglio nota come virtualizzazione containerbased.
Spiegheremo nel dettaglio le tecniche utilizzate da LinuX Container e le
confronteremo, ove possibile, con le altre tecniche di virtualizzazione.
Presenteremo i progetti che fanno uso di questa tecnica focalizzandoci poi sui
meccanismi chiave che la caratterizzano. Infine concluderemo presentando gli
scenari dove trova maggior spazio presentando soluzioni interessanti nelle
ambienti aziendali e non solo.
3
Capitolo 1: Metodi di Virtualizzazione & Progetti
La virtualizzazione dei sistemi operativi
non è un‟area sviluppata
recentemente, anzi, essa fu studiata inizialmente da IBM nei primi anni ‟60
crescendo e diversificandosi fino ad arrivare nella forma che oggi conosciamo
e utilizziamo.[2] Sono infatti presenti diversi tipi di virtualizzazione che
forniscono caratteristiche simili ma differiscono tra loro nel grado di
astrazione e dai meccanismi usati per virtualizzare. In particolar modo,
possiamo considerare tre particolari tecniche di virtualizzazione che si sono
affermate durante gli anni e sono l‟Emulazione, la Paravirtualizzazione e la
virtualizzazione a livello di Sistema Operativo, meglio nota come
Virtualizzazione basata su Container.
Forniremo di seguito una breve descrizione delle tecniche di Emulazione e
Paravirtualizzazione per poi soffermarci maggiormente sulla tecnica che
rappresenta il cuore di questo trattato, la virtualizzazione Container-based.
Nella tecnica dell‟Emulazione
vengono
simulati
tutti
i
componenti hardware. Questo
approccio, che ha riscontrato
negli anni un ampio utilizzo,
Figura 1 Tecnologie di Virtualizzazione [14]
permette di eseguire un arbitrario sistema operativo “ospite” senza alcuna
modifica, al costo di un overhead aggiuntivo. Questo grazie al fatto che la
macchina virtuale non è conscia di non essere eseguita su di un hardware
reale. Tale approccio presenta però una problematica fondamentale ovvero
alcune istruzioni CPU richiedono dei privilegi aggiuntivi e non potrebbero
essere eseguiti nello spazio utente. Per questo motivo è richiesta la presenza
4
di un Virtual Machine Monitor (VMM), o anche detto Hypervisor, per
analizzare il codice eseguito e renderlo sicuro in run time. La presenza di
questo ulteriore livello appesantisce non poco lo svolgimento delle normali
funzionalità. [3]
Nella tecnologia della paravirtualizzazione la maggior parte del lavoro viene
eseguita nel codice del sistema operativo ospite che viene modificato per
supportare
la presenza del
VMM ed evitare l‟uso superfluo
di istruzioni privilegiate. Per
interfacciarsi con l‟hypervisor
si rendono necessarie modifiche
consistenti al kernel del sistema
“ospite”, quindi da un lato si ha
un
considerevole
nell‟acquisto
risparmio
hardware
ma
dall‟altro si ha una mole di
lavoro aggiuntiva in termini di
Figura 2 Vista schematica di un hypervisor Xen [14]
programmazione a livello kernel.[3]
Riassumendo,
possiamo
notare
che
sia
l‟emulazione
che
la
paravirtualizzazione introducono un overhead ed una “pesantezza” nella
gestione non trascurabili dovuti al fatto che ogni macchina virtuale ha il
proprio sistema operativo indipendente e necessitano di un livello aggiuntivo
che le consente di operare sul kernel della macchina base. Una soluzione a
tutto questo è stata presentata negli anni da un‟ulteriore tecnica denominata
virtualizzazione container-based, la quale ha come sua caratteristica
predominante una leggerezza di virtualizzazione. A differenza delle sue
tecniche concorrenti si avvale di metodi atti al controllo e alla gestione delle
risorse che consentono di preservare l‟efficienza e un ottimo grado di
isolamento tra i vari container.
5
1.1 Virtualizzazione a livello di OS (Virtualizzazione basata su
Container)
Numerose applicazioni odierne, eseguite sulla medesima macchina, possono
facilmente condividere la stessa
struttura
hardware
senza
l‟intervento di livelli aggiuntivi,
come l‟hypervisor presentato
prima, se isolati e messi in
sicurezza.
In
pratica
l‟hypervisor viene sostituito da
un
vero
e
proprio
kernel
modificato in modo da poter
Figura 3 Vista schematica di una CoreOS con LXC[14]
gestire i diversi sistemi semplicemente ospitandoli in “container” differenti.
In questo modo, abbiamo due vantaggi fondamentali: il primo è quello di
utilizzare un singolo kernel riducendo così l‟overhead e il tempo di avvio
dovuto ai caricamenti multipli eseguiti al lancio di ogni macchina virtuale e il
secondo consiste nella separazione dei file dei sistemi guest in directory
separate, mentre il loro spazio di memoria viene mantenuto isolato dal kernel.
Il kernel e i programmi base vengono quindi condivisi dai guest garantendo
una limitazione dell‟overhead intorno all‟1-3%. Inoltre, il singolo kernel
presente in questa tecnica di virtualizzazione consente un consumo esiguo
delle risorse fisiche della macchina.[3]
In particolar modo affiancando le tecniche della virtualizzazione basata su
container e di Docker, uno dei progetti che presenteremo nei successivi
paragrafi e che recentemente sta spopolando negli ambienti di sviluppo, è
possibile fornire agli amministratori di sistema la flessibilità richiesta senza
tralasciare l‟efficienza acquisita dalle proprie applicazioni.
6
1.2 Hypervisor vs Container
Le tecniche pocanzi presentate differiscono per la complessità di
implementazione, ampiezza di supporto per il SO, performance e livello di
accesso alle risorse comuni. Ad esempio, le Virtual Machines hanno un vasto
panorama
di
utilizzo,
ma
delle
performance
molto
povere.
La
paravirtualizzazione fornisce performance migliori ma supporta un numero
limitato di SO, dovuto alle modifiche che dovrebbe subire il sistema
operativo originario. La virtualizzazione a livello di sistema operativo
fornisce le migliori performance e scalabilità confrontate con gli altri
approcci. I containers, infatti, sono semplici da amministrare, basti pensare
che ognuno di essi può essere acceduto e gestito da parte del sistema host.
Figura 4 Confronto tra virtualizzazione Container-based e Hypervisor-based[17]
La figura riportata in alto rappresenta le differenze presenti tra una
virtualizzazione basata su container ed una basata su hypervisor. Come si può
facilmente osservare, mentre una virtualizzazione hypervisor-based fornisce
un‟astrazione dell‟intero sistema operativo “ospite”, uno per ogni macchina
virtuale, la virtualizzazione container-based lavora, invece, sul livello di
sistema operativo fornendo un‟astrazione direttamente ai processi “ospiti”. In
poche parole l‟ hypervisor lavora al hardware abstraction layer mentre i
container lavorano a livello di system call/ABI layer.[17]
In parole povere i container virtualizzano a livello di sistema operativo
mentre la soluzione basata su hypervisor virtualizza a livello hardware.
7
1.3 Progetti Collegati
Di seguito verranno presentati alcuni tra i molteplici progetti che fanno
utilizzo o che applicano la virtualizzazione a livello di sistema operativo o
anche detta Container Based.
1.3.1 OpenVZ
OpenVZ è una virtualizzazione basata su Container
per Linux. Crea molteplici Linux container , noti
come VEs (Virtual Environments) o VPSs (Virtual
Private Servers), su di una singola macchina fisica
abilitando un miglior utilizzo server e assicurandosi
che queste applicazioni non entrino in conflitto tra loro. Ogni container
lavora ed esegue esattamente come un server indipendente; ogni container
può essere riavviato indipendentemente e avere accessi di root, users,
indirizzi IP, memoria, processi, files, applicazioni, librerie di sistema e file di
configurazione.
Il software OpenVZ consiste in un Linux kernel custom, opzionale, e in un
tool di command-line. [22]
1.3.2 Docker
Docker è una piattaforma “open” per
sviluppatori e amministratori di sistemi
per costruire, trasferire e avviare
applicazioni distribuite, in altre parole
consente
di
applicazione
impacchettare
e
tutte
le
una
sue
dipendencies in un unico container.
Interessante è l‟affermazione che Merkel fa del progetto Docker nel suo
articolo “Docker: Lightweight Linux containers for Consistent Developement
and Deployment” , [… Docker container, the lightweight and nimble cousin
of VMs...].
8
L‟obiettivo primario del progetto Docker è quello di poter impacchettare ed
utilizzare le applicazioni
liberandosi di tutti quei problemi legati alle
numerose dipendenze tra componenti.
La virtualizzazione container-based e, in particolar modo, LinuX Container
costituiscono il cuore del progetto Docker. Infatti, senza le tecniche adoperate
da LXC, quali una separazione di namespace avanzata e l‟utilizzo dei gruppi
di controllo (cgroups), Docker non potrebbe garantire le funzionalità sopra
citate perdendo quindi di importanza. In aggiunta a queste tecniche Docker
utilizza anche AuFS, Advanced Multi-Layered Unification Filesystem, come
filesystem per i container. Tale filesystem stratificato copre uno o più
filesystem esistenti. Se un processo ha bisogno di copiare un particolare file
AuFS crea una copia del file desiderato, inoltre è capace di fondere insieme
più strati in un singolo filesystem. Tale processo prende il nome di copy-onwrite. Infine, le funzionalità prevalenti che AuFS fornisce a Docker sono la
possibilità di utilizzare una data immagine come base per più container e
l‟abilità di dare una versione per ogni immagine di container.
Docker tiene traccia e amministra i cambiamenti e le dipendenze, rendendo
semplice, per gli amministratori di sistema, la comprensione del
funzionamento e del lavoro delle applicazioni. Ovviamente le differenze tra
una versione e l‟altra saranno minime, ma questa caratteristica ci consente di
tenere aggiornati e sotto controllo tuti i cambiamenti che le versioni hanno
subito nel passare del tempo. In questo modo, viene generata e gestita una
“build pipeline” delle applicazioni create che potranno essere condivise tra i
vari sviluppatori attraverso repositories pubbliche o private.
Come anticipato in precedenza una delle abilità che ha, e sta costruendo la
gloria di, Docker è la possibilità di cercare, scaricare e avviare un‟immagine
di container che è stata creata da altri sviluppatori in modo semplice e rapido.
Queste immagini sono custodite nel registry (registro) e Docker Inc. offre un
ulteriore registro denominato Central Index.
9
In aggiunta alle varie immagini di base ,offerte dal Docker Registry, che
possiamo utilizzare per costruire il nostro container Docker sono presenti
numerosi altri software pronti all‟uso come database, ambienti di sviluppo,
server Web e molto altro. Inoltre, è possibile realizzare anche un registro
personale e privato che si andrà ad affiancare a quello base offerto da Docker,
in questo modo sarà possibile condividere il proprio codice, e quindi il
proprio lavoro, anche internamente alla propria compagnia.
Bisogna porre attenzione, però, ad una caratteristica centrale di Docker, la
facilità di utilizzo. Infatti, è molto semplice sia scaricare le immagini di cui
abbiamo appena discusso che caricarle nei registri opportuni. [15]
A livello macroscopico Docker può essere visto come composto di due
componenti, il Docker Engine, vero e proprio artefice degli impacchettamenti
e il Docker Hub, servizio cloud che si occupa della condivisione delle
applicazioni e dell‟automatizzazione del carico di lavoro.
Docker può presentarsi a due figure professionali distinte gli sviluppatori, i
quali possono realizzare ogni applicazione in qualsiasi linguaggio utilizzando
ogni toolchain, e gli amministratori di sistema, i quali sono ora in grado di
fornire ai propri sviluppatori un ambiente standardizzato riducendo, in questo
modo, il problema della diversificazione delle varie macchine utilizzate.
Questa standardizzazione consente agli amministratori di ottenere una
maggiore flessibilità sul carico di lavoro. Per di più,
la leggerezza di
esecuzione del Docker Engine permette un rapido scale-up e scale-down in
risposta ai cambiamenti della domanda. Docker aiuta gli amministratori di
sistema a distribuire e eseguire qualsiasi applicazioni su qualsiasi
infrastruttura in modo rapido e sicuro. [24]
Di seguito presenteremo un breve confronto tra le Virtual Machines e la
tecnologia, appena presentata, di Docker.
10
Virtual Machines
Ogni applicazione di Macchina Virtuale include
non solo le applicazioni, che potrebbero includere
alcune decine di MB, le librerie e i file binari
necessari, ma anche un intero sistema operativo
ospite, che potrebbe considerare diverse decine di
GB.
Docker
Il container Docker Engine comprende solo le
applicazioni e le loro dipendenze. Esegue come un
processo isolato in userspace sul sistema operativo
host, condividendo il kernel con tutti gli altri
containers. Perciò, gode della resource isolation e
i benefici di allocazione delle VMs ma è
maggiormente portabile ed efficiente. [24]
1.3.3 CRIU
Checkpoint/Restore in Userspace è un
componente software per il sistema
operativo Linux.
Utilizzando
questo
strumento
è
possibile “congelare” un‟applicazione
in esecuzione, o una sua parte, e
recuperarla in un secondo momento
come una collezione di file. In questo modo è possibile utilizzare questi files
per ripristinare ed eseguire l‟applicazione dal punto in cui era stata bloccata.
Il punto distintivo di CRIU è che lavora prevalentemente nello spazio utente.
Il progetto CRIU inizialmente fu sviluppato come parte del progetto OpenVZ.
Anche se il suo obiettivo principale è il supporto per la migrazione dei
containers consentendo, agli utenti, di “salvare” e “ripristinare” lo stato
corrente del processo in esecuzione e di gruppi di processi. [23]
11
Capitolo 2: Skills & Funzionamento
Numerosi
scenari
emergenti
traggono
beneficio
dalle
tecniche
di
virtualizzazione che isolano diversi gruppi di utenti e le loro applicazioni.
Quello che realmente condividono, però, è il bisogno di efficienza nell‟uso
delle risorse di sistema. Sia in termini di crude prestazioni che in termini di
scalabilità.
Come abbiamo visto in precedenza la virtualizzazione basata su container
lavora a livello di sistema operativo e, quindi, tutte le istanze condividono un
singolo kernel. Per questa ragione questo tipo di virtualizzazione è
considerata “meno isolante” rispetto a quella basata su hypervisor. Al di là di
questo dettaglio, dal punto di vista dell‟utente, ogni container è visto come un
sistema operativo indipendente. L‟isolamento fornito è, normalmente,
garantito dal namespace kernel. Questa è una caratteristica del kernel Linux
che permette a processi differenti di avere una visione eterogenea del sistema.
Dato che i containers non dovrebbero essere in grado di interagire con gli
oggetti esterni molte risorse globali vengono “confezionate” in un livello di
namespace che garantisce l‟illusione che il container è il suo sistema. Un
esempio di risorse che possono essere isolate tramite namespace sono i PID,
ovvero l‟id dei processi, e la rete.
Come possiamo notare due sono i temi maggiormente ricorrenti :efficienza e
isolamento.
Ovviamente l‟efficienza è facilmente misurabile in termini di performance
complessive (throughput, latency ecc.) ed in quelli di scalabilità. D‟altro
canto l‟isolamento non può essere misurato facilmente. Diremo che un
12
sistema procura un completo isolamento quando fornisce una combinazione
di Fault isolation, Resource isolation e Securyti isolation. [16]
2.1 Fault Isolation
Riflette l‟abilità di limitare il contagio, da parte di una VM affetta da bug, di
dati e operazioni, corrette, di altre VM. Una Fault isolation completa, quindi,
indica l‟assenza di condivisione di codice tra VMs. Nei sistemi operativi
basati su container e quelli basati su hypervisor è presente un completo
isolamento dei “guasti” ottenuto mediante l‟utilizzo di uno spazio di indirizzi
privato. L‟unico codice, ed eventuali dati, condivisi è il sistema sottostante
che fornisce appunto la virtualizzazione. Ogni guasto in questo codice può
causare errori nell‟intero sistema.
Se consideriamo, in rispetto alla fault isolation, che i vari sottosistemi come
drivers, filesystem, protocolli di rete ecc. siano dei capisaldi, allora la
principale differenza tra un sistema container-based ed uno hypervisor-based
risiede nelle interfacce che espongono tra le varie VMs. Ogni vulnerabilità
presente in queste interfacce può far passare un errore, o meglio un guasto, da
una VM all‟altra.[16]
2.2 Resource Isolation
Corrisponde all‟inclinazione di assegnare e di far rispettare il consumo di
risorse da parte di una VM, al fine di garantire delle quote eque verso la
totalità delle VM. Interazioni indesiderate vengono chiamate cross-talk.
Affinché il numero di cross-talk venga reso minimo e quindi che la resource
isolation sia garantita bisogna, generalmente, allocare e schedulare
attentamente risorse fisiche e logiche. Entrambi i sistemi hypervisor e
container based incorporano una sofisticata resource isolation. [16]
2.3 Security Isolation
Equivale alla misura in cui un sistema virtualizzato viene limitato
nell‟accesso agli oggetti logici, come file, indirizzi di memoria virtuale, user
id e via dicendo. Per adempiere a questo compito la security isolation
promuove:
13
1. Configuration Independence dove i nomi globali selezionati da una
VM non potranno entrare in conflitto con quelli selezionati da una
diversa VM;
2. Safety, quando il namespace globale è condiviso una VM non è in
grado di modificare dati e codice appartenente ad un‟altra VM,
diminuendo la possibilità di “infettare” le altre VM.
Un sistema virtualizzato che supporta una security isolation parziale
implementa un namespace condiviso con meccanismo di accesso che,
comunque, limita l‟abilità di una VM di manipolare oggetti appartenenti ad
altre VM. Inoltre, una security isolation parziale può consentire una fuga di
informazioni permettendo ad utenti non autorizzati di identificare porte in
uso, user names, numero di processi in esecuzione. [16]
Possiamo ora rinnovare il confronto che abbiamo precedentemente affrontato
tra la virtualizzazione Container-based e quella Hypervisor-based.
Figura 4 Confronto Tassonomico [16]
Come possiamo notare dalla figura posta in alto ora affronteremo un
confronto tassonomico delle security e resource isolation implementate dalle
due diverse tecniche di virtualizzazione.
14
Come si evince la tecnica basata su container approccia la security isolation
coinvolgendo direttamente gli oggetti interni del sistema operativo. Le
tecniche base per utilizzare tali oggetti in sicurezza sono :

Separazione del Namespace;

Controllo di accesso.
Il primo significa che gli identificatori globali (PID, SYS V IPC, user id
eccetera) vivono in spazi completamente differenti, non hanno puntatori ad
oggetti che risiedono in altri spazi non appartenenti alla proprio VM, e non
hanno accesso ad oggetti esterni al proprio namespace. Attraverso la
“contestualizzazione” gli identificatori locali diventano globali per tale VM.
Il secondo, d‟altro canto, controlla gli accessi agli oggetti kernel con checks
runtime per determinare se la VM ha gli opportuni permessi o meno.
Anche per la tecnica basata su hypervisor la security isolation può essere
ottenuta tramite questi metodi ma a costo di un conflitto con l‟hardwareabstraction-layer.
Per quanto riguarda la resource isolation, invece, le due tecniche si muovono
allo stesso modo. Entrambe, infatti, hanno bisogno di multiplexare le risorse
fisiche (CPU, spazio su disco eccetera). [16]
2.4 LinuX Container (LXC)
LinuX Container è un metodo di virtualizzazione container-based predisposto
all‟esecuzione di molteplici sistemi Linux isolati, noti come container, su di
una singola macchina fisica. Attraverso una potente API e dei semplici
strumenti consente agli utenti Linux di creare e gestire container in maniera
rapida e lineare. [4] Sarà di seguito approfondito il suo funzionamento e i suoi
meccanismi per garantire i parametri pocanzi elencati, inoltre sarà presentato
brevemente come realizzare e gestire i container.
Per la creazione di un container in LXC supporremo l‟utilizzo di Ubuntu
14.04. Una volta installato correttamente LXC andremo a creare un container,
facendo attenzione a quanto esso sia semplice e rapido.
15
Per la creazione di un container denominato p1, con l‟utilizzo del template
ubuntu si andrà ad utilizzare la seguente primitiva:
>>sudo lxc-create –t ubuntu –n p1
Mentre per far partire il container in background :
>>sudo lxc-start –n p1 -d
Con altrettanta semplicità è possibile fermare un container e, nel caso lo si
ritenga necessario, eliminare.
>>sudo lxc-stop –n p1
>>sudo lxc-stop –n p1 –k
[25]
LXC utilizza un namespace kernel per ottenere l‟isolamento delle risorse tra
tutti i container. In particolar modo, all‟avvio di ogni container PIDs, IPCs e i
punti di mount sono virtualizzati e isolati attraverso i namespace rispettivi.
Questo isolamento di risorse è possibile tramite l‟utilizzo di cgroups.
Le operazioni di I/O, invece, sono controllate da un particolare livello
scheduler denominato CFQ, Completely Fairness Queuing. Ad ogni
container viene assegnato una priorità di I/O, quindi, lo scheduler decide di
distribuire il bandwidth I/O avviabile a seconda della priorità. In tal modo
nessun container potrà saturare un canale, interferendo con le performance di
isolamento. Introdurremo inoltre lo scheduler CFS Completely Fair Scheduler
per la gestione del processore dove ogni processo avrà assegnato un tempo
denominato virtual runtime per una equa partizione della CPU.
2.4.1 CGroups (Control Groups)
Gruppo di Controllo fornisce un meccanismo per aggregare/partizionare
insiemi di task, e tutti i loro “futuri figli”, in una gerarchia di gruppi con dei
comportamenti specializzati.
Prima di introdurci nelle funzionalità fornite da questa caratteristica del
kernel Linux diamo alcune definizioni:
16
1. CGroup associa un insieme di processi con un insieme di parametri per
uno, o più, sottosistemi;
2. Sottosistema è un modulo che usa dei servizi di raggruppamento task
concessi da cgroups per curare gruppi di task in modo particolare.
Tipicamente, un sottosistema è un “controllore di risorsa” che smista
una particolare risorsa oppure applica dei limiti forniti proprio da
cgroups. Può essere qualsiasi cosa voglia agire su un gruppo di processi,
come un sistema virtualizzato;
3.
Gerarchia è un insieme di cgroups composti ad albero, ogni processo
del sistema è, esattamente, in un cgroups della gerarchia, e in un
insieme di sottosistemi; ognuno di esso ha uno stato specifico del
sistema allegato ad ogni cgroup della gerarchia. Un‟istanza del
filesystem virtuale di cgroup è associata ad ogni gerarchia. Possono,
inoltre, essere presenti molteplici gerarchie attive nel sistema dove
ognuna di essa rappresenta una partizione di tutti i task presenti.
Uno degli obiettivi prepostisi da cgroup è quello di assegnare/limitare gli
accessi ad una particolare risorsa che un particolare task, ubicato in un
cgroup, può effettuare.
Molti sforzi sono stati affrontati per ottenere una buona aggregazione dei
processi nel kernel Linux, molti dei quali sono stati indirizzati per ottenere un
“tracciamento”
delle
risorse.
Questi
richiedono
le
nozioni
base
dell‟aggregazione/partizione dei processi in modo da “far finire” i processi
appena creati, tramite fork per intenderci, nello stesso cgroup dei suoi
genitori. I meccanismi minimi richiesti per creare efficientemente i suddetti
gruppi sono forniti dalle patch kernel di cgroup, inoltre si ha un basso impatto
al sistema complessivo fornendo “agganci” per sottosistemi specifici, come
cpusets, con comportamenti aggiuntivi desiderati.
Quindi, Cgroups amplia il kernel nei seguenti modi:

Ogni task nel sistema ha un puntatore di riferimento ad un css_set;
17

Un css_set contiene un insieme di puntatori di riferimento ad un
cgroup_subsys_state object, uno per ogni sottosistema
(cgroup) registrato nel sistema. Non sono presenti link diretti dal task
al cgroup del quale fa parte, ma è possibile determinarlo seguendo il
puntatore contenuto nel cgroup_subsys_object. Questo perché
accedere allo stato del sottosistema è un qualcosa che dovrebbe
accadere frequentemente, mentre ,accedere ad un codice “critico”, che
richiede un assegnamento tra task e cgroup,
accade meno
frequentemente. È presente una lista linkata attraverso il campo
cg_list di ogni task_struct utilizzando il css_set, ovvero
css_set->tasks;

Una gerarchia di filesystem di cgroup può essere montata per ricerche
e modifiche nell’user space;

Possono essere catalogati tutti i task, mediante i propri PID, ancorati
ad ogni cgroup.
Per implementare adeguatamente cgroup sono necessarie, quindi, alcune
ulteriori agganci nel kernel:

Nel init/main.c, per inizializzare il root cgroup e iniziare css_set
come boot di sistema:

Nel fork/exit, per effettuare l’attach e il detach di un task dal
prorpio css_set.
Non sono necessarie nuove system call per utilizzare cgroup, infatti per
modificare ed attuare le proprie operazioni abbiamo il filesystem di cgroup.
Ogni task sotto /proc ha un file aggiuntivo nominato “cgroup” che mostra,
per ogni gerarchia, il nome del sottosistema e il nome del cgroup come
percorso relativo al root del filesystem di cgroup.
Ogni cgroup è rappresentato da una directory nel filesystem di cgroup
contenente i seguenti descrittori di file:
18

Tasks: lista di task, elencati tramite il loro PID, agganciati al cgroup
a cui si fa riferimento. Non ci sono, però, garanzie di ordine per questa
lista;

Cgroup.procs: lista di ID di gruppi di thread nel cgroup. Come in
precedenza, non siamo certi della presenza di un ordine in tale lista e,
in particolar modo, della presenza di duplicati;

Notify_on_release flag: esegue il release agent in uscita;

Release_agent: il percorso da usare per le notifiche di rilascio
(presente solo nel top cgroup).
L’attachment di ogni task, automaticamente ereditato all‟atto del fork da ogni
“figlio” del task, ad un cgroup, permette di organizzare il carico di lavoro su
un sistema in un relativo insieme di tasks. Quando un task viene spostato da
un gruppo ad un altro ottiene un nuovo puntatore css_set, se è presente un
css_set con la raccolta desiderata di cgroups allora il gruppo verrà riutilizzato,
altrimenti verrà allocato un nuovo css_set. L‟esistenza del css_set può essere
provata cercando nella tabella hash appropriata. [11]
Cgroups, quindi, fornisce:

Limite alle Risorse: i gruppi possono essere settati nel non eccedere
un dato limite di memoria;

Priorità: alcuni gruppi possono avere un più largo utilizzo e
condivisione di CPU o I/O throughput;

Assegnazione: per valutare quante risorse alcuni sistemi consumano
per i loro compiti;

Controllo: “congelando” i gruppi o salvandone lo stato e poi,
successivamente, riavviarle.
2.4.2 Isolamento Namespace
Parliamo di isolamento dello spazio dei nomi quando abbiamo un gruppo di
processi che non è in grado di “vedere” le risorse appartenenti ad un altro
gruppo di processi. Per esempio, il namespace PID assegna una numerazione
separata di identificatori di processo all‟interno di ogni namespace. Di seguito
19
presenteremo tutte le separazioni a livello di spazio di nomi presenti in LinuX
Container.
2.4.2.1 Namespace PID
Namespace PID, fornisce un isolamento per l‟allocazione degli identificatori
di processi, PID per l‟appunto, liste di processi e i loro dettagli. Quindi, questi
insiemi di task possono apparire proprio come residenti su di una macchina
indipendente. In altre parole processi in namespace diversi possono avere lo
stesso PID.
Questa caratteristica è un prerequisito fondamentale per la migrazione dei
container. Senza questa caratteristica sarebbe possibile imbattersi in un
fallimento di migrazione, dal momento che è molto probabile che due
processi differenti, risiedenti prima dell‟operazione su macchine differenti,
abbiamo lo stesso PID una volta ultimata la migrazione.
Ha una struttura gerarchica. Benché il nuovo namespace è completamente
isolato dai suoi “fratelli”, i processi risiedenti nel namespace “genitore”
continua a vedere tutti i processi nei namespace “figli”, sebbene con un
numero PID differente.
Tutti i PIDs appartenenti ad un task sono descritti nella struttura struct
pid. Questa struttura contiene il valore dell‟identificativo, la lista di task
aventi questo ID, il contatore di riferimento e la lista hash di nodo che
successivamente dovrà essere inserita nella tabella hash per una ricerca più
rapida.
Comunemente un task possiede tre identificativi differenti: l‟identificativo di
processo (PID), l‟id del gruppo del processo (PGID) e l‟id di sessione (SID).
Ora, con l‟introduzione del
namespace PID, la struttura
diviene
maggiormente elastica , quindi, ogni tasks può assumere un insieme di valori
che sono ammissibili in un dato namespace e non lo sono in un altro. [7]
Vediamo nello specifico le differenze appena elencate. In particolar modo la
prima struct non prevede il namespace PID mentre la seconda si.
20
struct pid{
atomic_t count;
int nr;
struct hlist_node pid_chain;
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
};
struct upid{
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};
struct pid{
atomic_t count;
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
int level;
struct upid numbers[0];
};
2.4.2.2 Namespace di Rete
Namespace di Rete, isola il network interface controllers e virtualizza tutti gli
accessi alle risorse, come i numeri di porto eccetera, permettendo ad ogni
container l‟accesso alla rete di cui ha bisogno. Come molte difficoltà
nell‟informatica, anche il namespace di rete può essere risolto mediante
l‟utilizzo di un ulteriore strato di indirezione, ma il codice che è stato
implementato per ottenere le performance di rete, che oggi giorno
constatiamo, è un codice vasto e ampiamento utilizzato dagli sviluppatori. I
quali molto
difficilmente potrebbero accettare un cambiamento che,
probabilmente, infici le performace ottenute.
21
In particolar modo LXC consente di utilizzare due possibili configurazioni;
Route based e Bridge based, le quali si differenziano per il livello di
applicazione nello stack di rete rispettivamente 3°(network layer) per la prima
topologia e 2°(datalink layer) per la seconda. [8]
2.4.2.3 Namespace Mount
Namespace dei punti di Mount, è stato introdotto nel 2000 e comprende sia un
bind mount, che consente di accedere a qualsiasi file e directory da una
qualsiasi locazione, e un filesystem namespace dove ogni albero è
completamente separato e associato a diversi processi. Un processo può
richiedere una copia del proprio albero di filesystem
tramite clone(2),
dopodiché il nuovo processo ha una copia identica del filesystem del processo
originale. Ora ogni azione di mount, in ciascuna copia del filesystem, non avrà
affetti sull‟altra copia.
Teoricamente questo aspetto è molto potente ma, in pratica, la completa
separazione è troppo ristrettiva. Ad esempio, una volta che un processo clona
il namespace del filesystem del sistema un daemon, già in esecuzione al
momento della copia, non è in grado di fare l’auto-mount di un semplice CDROM, perché il mount, effettuato nel namespace del filesystem originario, non
viene propagato in quello clonato. Questo particolare disagio è stato risolto
introducendo la tecnica della mount propagation che definisce una relazione
tra gli oggetti di mount. Questa relazione è utilizzata per determinare come un
evento di mount, avvenuto in un particolare mount object, si propaga in un
altro mount object nel sistema.
Un oggetto di mount che permette la propagazione di un evento di mount
viene denominato shared mount, mentre se ne riceve soltanto viene nominato
slave mount, infine, un oggetto di mount disinteressato da questi eventi è
detto private mount. [9] Di default tutti gli oggetti di mount sono privati, per
settarli in una determinata modalità bisogna eseguire i seguenti comandi:
>>mount –-make-shared <mount-object>
>>mount –-make-slave <shared-mount-object>
>>mount –-make-private <mount-object>
22
2.4.2.4 User Namespace
Namespace User consente di mappare gli utenti e gli IDs di gruppo per ogni
namespace. Questo sta a significare che gli utenti di processi e gli IDs di
gruppo all‟interno di un namespace possono essere differenti da quelli
presentati all‟esterno del namespace. In particolar modo, un processo può
avere ID pari a zero all‟interno del namespace, ottenendo così privilegi di
root, mentre gli può essere assegnato un ID diverso da zero perdendo i
privilegi per le operazioni esterne al namespace di appartenenza. [10]
2.4.3 I/O Scheduling : CFQ
CFQ è lo scheduler di I/O più sofisticato in circolazione, consentendo una
distribuzione della larghezza di banda di I/O e delle relative richieste tra tutti
i processi presenti nel sistema. Questo particolare scheduler è in grado di
gestire un numero considerevole di
permettendo, inoltre,
code di richieste di I/O sincrone
la gestione simultanea delle richieste asincrone,
generate da tutti i processi, mediante l‟utilizzo di diversi livelli di priorità
assegnati a questi ultimi.[27]
Lo scopo principale dello scheduler CFQ è quello di fornire un‟allocazione
giusta a tutti i processi che richiedono un utilizzo della larghezza di banda di
I/O. [6]
Durante ogni ciclo lo scheduler soddisfa una richiesta da ogni coda dalla
dispatch queue. Questa operazione viene ripetuta ciclicamente, una volta
soddisfatta una richiesta da una coda si procede verso la prossima richiesta e
così via. Il numero di richieste da schedulare che il CFQ deve scegliere dalla
dispatch queue può essere selezionato mediante il parametro quantum. Dopo
questa tipologia di operazione lo scheduler CFQ può ordinare le richieste
ancora da adempiere in modo da ridurre il tempo di seek del disco.
Il Completely Fairness Queueing garantisce ad ogni coda di richieste un
determinato tempo di accesso al disco. L‟ammontare di tempo garantito
dipende fortemente dalla priorità del processo richiedente. Tramite il
23
parametro ionice possiamo adattare la priorità di scheduling del processo. Per
esempio :
ionice –pprocessPID –cclass -npriority_within_a_class
dove :
1. pprocessPID è , ovviamente, il PID del processo in discussione;
2. cclass è la priorità di schedulazione del processo. Il valore di class
può essere uno di questi tre valori:

1: idle, la priorità più bassa;

2: best effort, la priorità di default;

3: real time, la priorità più alta.
3. npriority_within_a_class
è la priorità di schedulazione
appartenente alle categorie real time e best effort. Il valore di questo
parametro può essere un intero compreso tra 0 e 7, dove 0 è la priorità più
alta e 7 la più bassa.
Uno dei parametri di cui vale la pena discutere è lo slice_idle il quale
permette di abbattere i “tempi morti”, ma andiamo ad osservarlo più nel
dettaglio. [27]
Slice_idle: specifica per quanto tempo CFQ dovrebbe restare fermo fino
alla prossima richiesta in una data coda CFQ, per carichi di lavoro
sequenziali, alberi di servizi, per carichi di lavoro random, prima che la coda
sia esaurita e CFQ selezioni la prossima coda da servire. Per default questo
valore è diverso da zero, ciò significa che rimarremo in idle su code/alberi di
servizi. Porlo, invece, a zero può manifestare una rimozione dei “tempi
morti” sia su code che su alberi dei servizi e, conseguentemente, aumentare il
throughput sui dispositivi con uno storage più rapido. D‟altro canto,
l‟isolamento fornito per le operazioni di scrittura e i concetti di priorità vanno
ad indebolirsi.[6]
24
2.4.4 CPU Scheduling: CFS
L‟idea fondamentale che risiede alla base dello scheduler CFS è il
mantenimento di una condivisione bilanciata, e quindi giusta (fairness), del
tempo concesso ad ogni processo per l‟utilizzo del processore. Ovvero, ad
ogni processo verrà fornito una giusta “fetta” del processore. Quando il tempo
di un task è out-of-balance, questo avviene quando uno o più task non danno
un ammontare di tempo giusto agli altri task, quelli che restano al di fuori del
bilanciamento dovrebbero avere comunque un tempo di esecuzione.
Per determinare questo bilanciamento il CFS mantiene l‟ammontare di tempo
conferito ad ogni task, il virtual runtime. Più piccolo sarà il virtual runtime
più sarà grande la necessità del processore da parte del processo. CFS
introduce inoltre il concetto di sleeper fairness (equità dormiente) per quei
processi che non possono andare in esecuzione subito perché in attesa di
qualche operazione, ad esempio in attesa di un‟operazione I/O. Questi
riceveranno la disponibilità del processore quando sarà necessario.
A differenza dei molteplici scheduler a priorità, che mantengono i task
ordinati per code, CFS mantiene un albero red-black ordinato. Un albero redblack ha due proprietà
molto
prima
interessanti:
è
la
l‟auto-
bilanciamento tramite la
quale
ogni
cammino
dell‟albero non sarà mai
lungo più del doppio di
qualsiasi altro cammino;
Figura 5 Albero red-black [28]
la seconda è la rapidità di applicazione delle operazioni sull‟albero, ovvero
occorre un tempo pari a O(log n) dove n è il numero di nodi dell‟albero.
25
Tramite la gestione appena esposta task aventi una più grave necessità di
processore, quindi un virtual runtime molto bassa, saranno collocati nella
parte sinistra dell‟albero, invece task aventi un minor bisogno del processore,
quindi aventi un virtual runtime elevato, saranno collocati nella parte destra
dell‟albero red-black.
Figura 6 Struttura gerarchica task in red-black tree[28]
Tutti i task interni a Linux sono rappresentati da una struttura denominata
task_struct. Questa struttura rappresenta pienamente il task associato
includendo il suo stato corrente, il suo stack, i flags di processo, priorità e
altro. Dato che non tutti i task sono eseguibili, come accennato in precedenza,
non è possibile trovare nel task_struct tutti i parametri salienti dello
scheduler CFS, per questa ragione è stata ideata una nuova struttura
sched_entity.
Come mostra l‟immagine sovrastante ogni struttura ha un collegamento ben
preciso con le altre. La radice dell‟albero è riferita tramite l‟elemento
rb_root della struttura cfs_rq. I nodi foglia dell‟albero non contengono
alcuna informazione mentre i nodi interni all‟albero rappresentano uno o più
task eseguibili. Ogni nodo nell‟albero red-black è rappresentato da un
26
rb_node, il quale contiene niente di più che il riferimento al nodo figlio e al
colore dei genitori. Il rb_node è contenuto all‟interno della struttura
sched_entity la quale, a sua volta contiene, il riferimento del rb_node,
il load_weight e altre informazioni varie. Molto importante è il vruntime, un
campo di 64-bit,che indica l‟ammontare di tempo cha il task ha a disposizione
per eseguire e serve anche come indice per l‟albero. In fine la struttura
task_struct, situata alla cima di queste relazioni, descrive il task ed
include la struttura sched_entity. [28]
27
Capitolo 3: Scenari d’uso
Numerosi sono i casi d‟uso della virtualizzazione container-based, tra i quali
il più interessante è quello relativo al Cloud Computing. Di seguito
analizzeremo nello specifico come questa particolare tecnologia trae
beneficio dalla virtualizzazione a livello di sistema operativo.
3.1 Cloud Computing
Il cloud computing è un agglomerato di tecnologie che consente di
memorizzare ed elaborare dati mediante l‟utilizzo di risorse sia hardware che
software distribuite e virtualizzate
nella rete in una architettura del
tipo cliente servitore. [26]
Se
consideriamo
computing
ottenuto
il
cloud
con
una
virtualizzazione hypervisor-based,
abbiamo
un
overhead
considerevole e in continua crescita dovuto allo sforzo di dover eseguire
un‟immagine di sistema operativo per ogni macchina virtuale. Come è ben
facile capire questa tipo di virtualizzazione è troppo “pesante” per una
tecnologia come il Cloud. Infatti, due sono le principali penalità presentate da
un cloud basato su di una virtualizzazione hypervisor:
1. Eseguire un sistema operativo completamente separato per ottenere una
resource e security isolation;
2. Un avvio lento dovuto al boot del sistema operativo.
Il sistema operativo consuma molta memoria e spazio su disco rispetto alle
applicazioni che ospita. Anche una piccola quantità, come alcune centinaia di
28
MB, possono essere significative, infatti le applicazioni odierne richiedono
proprio questo quantitativo di MB.
Per accelerare, invece, l‟avvio molti utenti delle infrastrutture cloud utilizzano
molte macchine virtuali di scorta. Soluzione che comunque non riesce a
diminuire considerevolmente i tempi.
Quindi, 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 di un server.
Come abbiamo largamente trattato nel resto della nostra discussione i
Containers, i quali utilizzano una sola immagine kernel sottostante, sono
molto più efficienti rispetto alle virtual machines, particolar modo in questo
scenario. Grazie a questa maggiore efficienza sostituiranno in breve tempo le
architetture basate su VMs nell‟infrastruttura del Cloud Computing.
Confrontate ad una macchina virtuale l‟overhead di un container è
nettamente inferiore. Anche sotto il punto di vista della velocità di avvio del
servizio notiamo un miglioramento importante, infatti alcune configurazioni
possono
essere eseguite
on-demand, proprio come
se il container presente sul
server fosse sull‟host che
stiamo adoperando.
A sostegno dei benefici
sopra elencati presentiamo
l‟immagine qui accanto,
dove viene indicato la
crescente
risposta
velocità
di
nel fornire un
Figura 7Confronto nell'approvvigionamento VM & Container[12]
servizio nel Cloud computing. [12]
29
Uno tra i più interessanti esempi della crescente importanza 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.
[18]
Inizialmente Google si interessa in modo leggermente differente alla
containerizzazione, ponendo l‟accento sulle prestazioni bensì che sulla facilità
d‟uso. Per chiarire le differenze presenta una variante a LXC nominata lmctfy,
Let Me Contain That For You. Il quale è la versione open-source del
container stack di Google, che fornisce le applicazioni atte alla
containerizzazione di Linux. [19]
Più nel dettaglio, Google Cloud Platform è 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. [20]
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 fornisce
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
(bandwidth) tra le macchine, connettendosi a macchine appartenenti ad altri
data centers ed ad altri Google services, usufruendo quindi della rete Google.
[21]
30
Conclusioni
Come osservato in questo elaborato la virtualizzazione container-based, con
le sue caratteristiche e vantaggi, rappresenta il futuro delle tecniche di
virtualizzazione.
In continua crescita è il numero di progetti e di aziende che considerano i
container come il futuro della classica virtualizzazione hypervisor-based.
Questo grazie alla leggerezza di virtualizzazione e alla semplicità di
implementazione e utilizzo che, precedentemente, abbiamo descritto.
Fulcro della crescente popolarità della virtualizzazione container-based è il
diverso modo di vedere e gestire una macchina virtuale. Prima, infatti,
ognuna di essa possedeva e gestiva un proprio sistema operativo, al di sopra
di un altro sistema operativo, quello della macchina host. Questo, con i
container, è stato accantonato. Ora ogni container vive ed opera su di una
singola immagine kernel sottostante, merito delle innovazioni presentate da
cgroup e dall‟isolamento dei namespace del kernel Linux.
Ampio è lo spazio che la virtualizzazione container-based trova nell‟ambito
del Cloud Computing, merito della sua velocità sia in avvio che di impiego.
Come abbiamo precedentemente presentato, infatti, le applicazioni presenti su
di un cloud, gestito tramite container, sono assimilabili ad applicazioni ondemand. Cosa che ovviamente con le macchine virtuali non era affatto
possibile.
31
Bibliografia
[1] Hardware e Networking – Virtualizzazione
http://ict.itsol.it/hardware_networking/focus-scheda.asp?id=31,
[2] La virtualizzazione non è così giovane come sembra
http://www.serverlab.it/2013/11/20/la-storia-della-virtualizzazione/ , 20/11/13
[3]Virtualizzazione basata su container: OpenVZ
http://guide.debianizzati.org/index.php/Virtualizzazione_basata_su_container:_Open
VZ, 09/10/12
[4] LXC – LinuX Containers https://linuxcontainers.org/
[6] CFQ-Complete Fairness Queueing
https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt
[7] P.Emelyanov, K.Kolyshkin, PID namespace in the 2.6.24 kernel
http://lwn.net/Articles/259217/ , 19/11/07
[8] Corbert, Network namespace http://lwn.net/Articles/219794/ , 30/01/07
[9] Serge E. Hallyn, Ram Pai, Applying mount namespace
http://www.ibm.com/developerworks/linux/library/l-mount-namespaces/index.html ,
17/09/07
[10] Michael Kerrisk, Namespace in operation, part 5: User namespaces
http://lwn.net/Articles/532593/ ,27/02/13
[11] Paul Menage, CGroups
https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
[12] David Strauss, Containers-Not Virtual Machines-Are the Future Cloud
http://www.linuxjournal.com/content/containers%E2%80%94not-virtualmachines%E2%80%94are-future-cloud?page=0,0 ,17/01/13
[13] David Strauss, Linux Containers and the Future Cloud
http://www.linuxjournal.com/content/linux-containers-and-future-cloud , 10/01/14
32
[14] Mathijs J. Scheepers, Virtualizaztion and Containerization of Application
Infrastructure : A Compariosion
http://referaat.cs.utwente.nl/conference/21/paper/7449/virtualization-andcontainerization-of-application-infrastructure-a-comparison.pdf
[15] Dirk Merkel, Docker: Lightweight Linux Containers for Consistent
Development and Deployment http://www.linuxjournal.com/content/dockerlightweight-linux-containers-consistent-development-and-deployment?page=0,0 ,
19/05/14
[16] S. Soltesz, Container-based Operating System Virtualization: A Scalable, Highperformance Alternative to Hypervisors
http://www.cs.toronto.edu/~demke/2227S.14/Papers/p275-soltesz.pdf ,23/03/07
[17] M. G. Xavier, Performance Evaluation of Container-based Virtualization for
High Performance Computing Environments
http://marceloneves.org/papers/pdp2013-containers.pdf
[18] Jack Clark, Google: „Everything at Google runs in a container‟
http://www.theregister.co.uk/2014/05/23/google_containerization_two_billion/ ,
23/05/14
[19] LMCTFY https://github.com/google/lmctfy
[20] Google Cloud Platform https://cloud.google.com/
[21] Google Cloud Platform, Compute Engine
https://cloud.google.com/products/compute-engine/
[22] OpenVZ http://openvz.org/Main_Page
[23] CRIU http://openvz.org/Main_Page
[24] Docker https://www.docker.com/
[25] Stephan Graber‟s website https://www.stgraber.org/2013/12/20/lxc-1-0-blogpost-series/ , 20/12/13
[26] Wikipedia: Cloud Computing http://it.wikipedia.org/wiki/Cloud_computing
[27] IBM Knowledge Center- Best Practice: I/O Schedulers http://www01.ibm.com/support/knowledgecenter/linuxonibm/liaat/liaatbpscheduleroverview.ht
m?lang=en
[28] M. Tim Jones, Inside the Linux 2.6 Completely Fair Scheduler
http://www.ibm.com/developerworks/library/l-completely-fair-scheduler/ ,15/12/09
33