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
© Copyright 2024 Paperzz