lunedì 3 agosto 2015

Container e altre amenità

Salve o lettori! Quest'oggi cercherò di illustrare a quanti di voi non lo conoscono il magico ed affascinante mondo dei Containers.

Per chi non sia un esperto di Information Technology e per coloro che lo sono ma che hanno vissuto sotto ad una roccia negli ultimi tre anni: i container sono una soluzione interessante ad un annoso problema, quello della separazione di diverse applicazioni residenti sulla medesima macchina fisica.

Supponiamo voi siate un allegro e simpatico sistemista amato dai colleghi e invidiato dal management... Fatto? Bene, torniamo ora coi piedi per terra e prendiamo un ben più comune sistemista ignorato dai colleghi e conosciuto unicamente come voce passiva di bilancio dal management.

Il vostro compito è quello di rendere efficace ed efficiente la fruizione dei servizi informatici da parte della vostra azienda e/o da parte dei vostri clienti. Vi è stato chiesto di integrare un nuovo servizio composto da un mix di applicazioni interagenti tra di loro e quasi incompatibili con il vostro attuale stack software. Il budget copre le cialde della macchinetta del caffè e il bonus di produttività dell'anno passato vi ha permesso di comprare la suddetta macchinetta per cui non ci sono soldi per comprare nuovo harware.

A questo punto sareste tentati di optare per una macchina virtuale, ma sapete già per esperienza che le macchine virtuali hanno la terribile tendenza ad essere sovrastimate o sottostimate per il compito che devono assolvere ed inoltre si portano dietro inevitabilmente il layer di virtualizzazione (o richiedono kernel speciali per la paravirtualizzazione). Niente di insormontabile: si è lavorato per un decennio con le macchine virtuali e le prestazioni sono prossime a quelle della macchina fisica su cui girano. Però quella fastidiosa vocina dentro la testa vi dice che si può fare di meglio e che si può sfruttare in maniera più oculata il ferro.

La vocina ha ragione, vediamo se possiamo farla star zitta con i containers.

Gabbie chroot

Prima di addentrarci nel magico mondo dei container vi devo parlare di una syscall e delle implicazioni che questa syscall ha avuto. Tanto tempo fa, quando la Disco music era in declino e il New wave era in ascesa Bill Joy (il creatore dell'editor vi) aggiunse al codice della Berkeley Software Distribution la chiamata di sistema chroot per testare il sistema di installazione di BSD in locale senza dover reinstallare da zero una macchina fisica.

Immagino che gli utenti di Arch Linux a questo punto scattino in piedi come delle molle. Acquietatevi e consentitemi di spiegare anche ai comuni mortali cosa fa chroot e perché è importante in fase di installazione del sistema.

La suddetta chiamata di sistema altro non è che una maniera per far sì che un processo ed i suoi figli vedano un'altra directory come se fosse la radice della gerarchia del filesystem (la famosa /). Cosa significa questo? Significa ad esempio che potreste avere un web server che in /var/www abbia i dati da servire, gli script PHP o i CGI e la sua configurazione completa più tutte le librerie di cui ha bisogno e potreste fare in modo che non possa uscire da /var/www facendogli fare una chiamata a chroot in fase di avvio. Le implicazioni sono palesi: il vostro webserver non potrà modificare nessun file all'infuori di quelli presenti in /var/www e nelle relative sottodirectory e, se la sua nuova radice è montata su un filesystem separato, non potrà impedirvi di lavorare riempiendo completamente il disco di files a causa di un bug nello script che consente agli utenti di caricare files via HTTP POST o HTTP PUT.

Questa condizione si chiama gabbia chroot ed è utilizzata ampiamente da molti software: il server OpenSSH la usa in fase di autenticazione, il server di posta Postfix è diviso in diversi programmi che risiedono in gabbie chroot, il webserver thttpd ha un'opzione per avviarlo e farlo subito entrare in una gabbia chroot e l'elenco potrebbe andare avanti.

Per quanto utili le gabbie chroot non sono perfette. Tanto per comiciare tutti i file descriptor aperti prima della chiamata a chroot vengono mantenuti, anche se riguardano file presenti all'esterno. Inoltre è possibile sfuggire alla gabbia facendo una seconda chiamata a chroot e reimpostando la root a quella di sistema. Le gabbie chroot inoltre non possono nulla contro processi che succhiano RAM e/o cicli di CPU a scapito degli altri processi nel sistema. Senza contare che un processo in una gabbia chroot che abbia i privilegi di root può killare un qualsiasi altro processo presente nel sistema. Infine non c'è alcuna maniera per limitare l'accesso alla rete: un processo in una gabbia chroot può tranquillamente aprire quanti socket vuole verso qualsiasi destinazione sia raggiungibile dall'host da qualsiasi indirizzo IP sia disponibile.

Insomma: le gabbie chroot sono un buon punto di partenza, ma occorre pensare a qualcosa di più se vogliamo realmente isolare un'applicazione dal resto del sistema.

FreeBSD e le jails

Le mancanze di chroot erano note da anni quando Poul-Henning Kamp si trovò davanti all'annoso problema che affligge tutti coloro i quali voglio aprire un servizio di hosting di siti web: come faccio a separare il mio (o i miei) siti da quelli dei miei clienti?

La soluzione efficace ma dispendiosa era (è) quella di ospitare il proprio sito su di un server e quelli dei clienti su altri server. Per darvi un'idea temporale stiamo parlando del 2000: un'epoca in cui la virtualizzazione muoveva i suoi primi passi, il processore di punta di intel era il Pentium III e Windows XP era ancora in fase di sviluppo e si chiamava Whistler. Un'epoca oscura per il mondo dell'IT...

A quei tempi l'unica maniera per avere più di un sito ospitato sullo stesso server fisico consisteva nello sfruttare un campo nell'header HTTP reso obbligatorio a partire da HTTP 1.1: il campo "Host:". Tale campo viene compilato dal client ed indica l'host a cui si vuol fare la richiesta, nella precedente versione di HTTP non era obbligatorio perché si assumeva che l'host a cui ci si collegava fosse lo stesso host a cui si voleva richiedere i dati e che un nome a dominio puntasse a macchine fisicamente diverse (o che, se anche si fosse puntato alla stessa macchina fisica con nomi diversi, questa avrebbe fornito sempre gli stessi dati).

L'avvento dei proxy server (utilizzati per ridurre il traffico dati verso i siti più frequentati dai propri utenti e quindi ridurre anche i costi di connessione) ha reso necessario indicare qual è l'host a cui mi voglio rivolgere perché potrebbe non essere quello a cui sono collegato.

Come effetto collaterale il campo "Host"permette ad un server web di decidere che contenuto mostrare in base al valore del campo stesso tramite il meccanismo dei virtual hosts.

Per quanto i virtual host rendano possibile la convivenza di più siti sul medesimo server web e le gabbie chroot possano essere sfruttate per impedire agli utenti di scrivere via FTP nelle directory degli altri (il server FTP al momento dell'autenticazione crea un nuovo processo che immediamente fa una chiamata a chroot nella home directory dell'utente) questo meccanismo non migliora la situazione agli utenti che richiedono modifiche particolari alla configurazione del webserver e che magari entrano in conflitto con altre modifiche volute da altri utenti o dagli amministratori stessi del server.

La situazione descritta poc'anzi non è così campata per aria, basti pensare a versioni mutualmente incompatibili di PHP (ad esempio PHP 4 e PHP 5) richieste contemporaneamente da due utenti differenti. Si può pensare di compilare staticamente due versioni di PHP, metterle in due gabbie chroot diverse, configurare due istanze del server web affinchè si mettano in ascolto su indirizzi IP diversi e pregare che tutto funzioni. Ma questo non impedisce ai processi di vedere tutti gli altri processi in esecuzione (e potenzialmente di fare disastri).

La soluzione proposta da Paul Henning-Kamp è stata quella di estendere le gabbie chroot in modo da rinchiudere i processi non solo dal punto di vista dell'accesso ai file, ma anche dal punto di vista dei processi con cui possono interagire e dal punto di vista dell'interazione con le connessioni di rete. Nascono così le jails.

Una jail è una gabbia chroot i cui processi non vedono altri processi se non quelli lanciati all'interno della jail ed è associata ad un indirizzo IP (per cui può comunicare solo tramite quell'IP e non tramite tutti gli IP disponibili all'host che ospita la jail). Non solo: l'utente root all'interno della jail non ha la facoltà di uscire dalla jail, solo root dall'esterno della jail può entrare e uscire a piacimento.

In pratica si può creare una userland alternativa in una sottodirectory del proprio filesystem e farci girare quello che si vuole sapendo che il resto del sistema sarà opaco ai processi all'interno della jail.

Iterazioni successive hanno raffinato il meccanismo delle jail migliorandone la sicurezza e la capacità di compartimentazione e rendendole una delle feature più interessanti di FreeBSD.

Linux Containers

Facciamo un bel fast forward di otto anni e raggiungiamo il 2008 quando finalmente il kernel Linux ha un'implementazione matura dei cgroups e può replicare le funzionalità delle jails di FreeBSD. Su questa base comincia a svilupparsi LXC: un insieme di tools in userland che rendono semplice la creazione di quegli ambienti chiusi noti come container.

Se avete resistito fin qui potrete facilmente dedurre cosa sia un container: nient'altro che la versione in salsa Linux delle jails. Tramite chroot e cgroups si ottiene il medesimo effetto, in effetti si ha una granularità maggiore nel limitare ciò che un processo all'interno di un cgroup può fare. L'effetto di questa granularità è stato in parte deleterio: per anni gli unici che usavano queste feature erano pochi iniziati che seguivano attentamente gli sviluppi del kernel.

C'è voluto il 2013, una startup chiamata Docker, e una cospicua dose di marketing perché il mondo si rendesse conto che i container su Linux esistono, sono una tecnologia matura e possono semplificare la vita di chi deve incastrare userland mutualmente incompatibili sullo stesso hardware.

Non sono però la panacea a tutti i problemi: tanto per cominciare il kernel è uno per tutti i container, una macchina virtuale può far girare un kernel diverso da quello dell'host. Stesso discorso vale per i moduli del kernel: tutti i container vedranno gli stessi moduli, anche se il caricamento di un modulo dall'interno dei container può essere inibito (e solitamente è inibito di default per questioni di sicurezza).

Il rovescio della medaglia sono le prestazioni: non c'è bisogno di elaborati artifici con driver virtuali passthrough e simili amenità in un container perché si sta già girando direttamente sul ferro e l'accesso ad una periferica dista appena un mknod ed una modifica al cgroup.

In sintesi per concludere: se siete gente che sa quel che sta facendo e vuole un set di tool minimale per sfruttare rapidamente i container ed avere il massimo della personalizzazione il mio consiglio è di saltare il layer di Docker e di provare LXC direttamente. Se siete interessati ad un sistema che vi consenta di condividere efficacemente le istruzioni per la creazione di containers e che fornisca già migliaia di template già pronti al prezzo della creazione di un account gratuito allora andate tranquilli su Docker. Se dovete far girare kernel diversi o interi sistemi operativi diversi allora la scelta delle macchine virtuali è una scelta obbligata.

Nessun commento:

Posta un commento