Credo che sia arrivato il momento di condividere con il mondo alcune delle nostre esperienze tecniche durante l'introduzione di nuove funzionalità in Storage OS. Mi soffermerò sull'ultima aggiunta a Storage OS: la virtualizzazione locale.
Ora è possibile eseguire macchine virtuali sul dispositivo di storage stesso senza alcuna infrastruttura aggiuntiva. Questa era un'esigenza reale della maggior parte dei nostri clienti medio-piccoli. Sbarazzarsi dei 2-3 server che forniscono condivisione di file, active directory o altri servizi e virtualizzarli. Ma come possiamo integrare questo aspetto nel nostro sistema operativo di storage?
Ovviamente non si tratta di un concetto nuovo. Oracle ha Virtual Box ed è piuttosto interessante. Abbiamo fatto qualche tentativo, ma non siamo rimasti soddisfatti delle prestazioni complessive e delle capacità di gestione. Abbiamo quindi cercato altre direzioni. La scelta migliore e più stabile è stata KVM (kernel virtual machine). KVM è una tecnologia che gira su Linux da molto tempo ed è uno degli hypervisor più stabili e performanti che esistano. Dobbiamo dare credito a Joyent per aver realizzato il porting iniziale di KVM su Illumos, hanno fatto un lavoro straordinario, tanto di cappello. Anche loro lo usano per le loro smart machine. Quello che mi ha colpito è che durante il porting i ragazzi hanno detto di non aver trovato un solo bug o problema nell'intero KVM. È già qualcosa, considerando che durante un porting si tocca ogni singola riga di codice.
Dopo aver portato la parte del kernel, abbiamo dovuto costruire l'infrastruttura per gestire le macchine virtuali su Storage OS. Il nostro primo dilemma era: quale sarebbe stato il backing store di una macchina virtuale? Inizialmente abbiamo deciso di utilizzare un Vdisk (un volume ZFS) come backing store. All'inizio sembrava una buona idea. È possibile configurare il provisioning come thick o thin provisioning, come si preferisce. Cosa succede se si ha bisogno di più spazio? È possibile espandere facilmente un disco V e allocare il nuovo spazio all'interno del sistema operativo guest. Abbiamo usato /dev/zvol/rdsk come percorso del dispositivo grezzo per l'immagine del disco.
La domanda successiva era: ora che abbiamo il backing store, come possiamo controllare lo stato e la configurazione di una macchina virtuale? Lavorando con Illumos (Storage OS utilizza il kernel Illumos) da alcuni anni, ho capito che esiste un solo modo per gestire i processi in esecuzione prolungata: SMF (Service Management Facility). SMF è una delle cose migliori che ci siano per quanto riguarda la gestione dei servizi nelle distribuzioni basate su Solaris. Per ogni macchina virtuale abbiamo creato un servizio SMF. Abbiamo mantenuto la configurazione in un gruppo di proprietà di configurazione. La RAM configurata, le immagini ISO montate, l'ordine di avvio, la configurazione di rete erano tutte proprietà del servizio. Il metodo di avvio del servizio analizza la configurazione e genera il comando per avviare qemu-kvm per quella macchina virtuale. Una cosa da notare è che nessuno degli strumenti per creare, distribuire o gestire le macchine virtuali, dalle distribuzioni Linux KVM, è presente su Illumos, poiché libvirt non è stato portato. Questo è un altro motivo per cui abbiamo dovuto creare i nostri strumenti.
La sfida successiva era come comunicare con la macchina virtuale in esecuzione. La scelta più ovvia è stata quella di utilizzare Qemu QMP. Si tratta di un protocollo basato su JSON che consente alle applicazioni di comunicare con le istanze Qemu (macchine virtuali). Abbiamo scritto un nuovo modulo che utilizza i socket UNIX per comunicare con le macchine virtuali utilizzando QMP. Abbiamo dovuto affrontare molte sfide, dato che QMP non è ancora finito e mancano alcuni comandi nel protocollo stesso, ma siamo riusciti a superarle. Utilizzando i comandi generati da JSON siamo stati in grado di controllare le macchine virtuali: aggiungere nuovi dispositivi, inviare comandi ACPI e così via. Un problema interessante che abbiamo affrontato è stato quando processi troppo diversi si connettevano allo stesso socket per operare sulla stessa macchina virtuale. Dovevamo fare attenzione a come controllare l'accesso dei processi per essere esclusivi.
Ok, ora che abbiamo tutte queste belle macchine virtuali in esecuzione, come possiamo gestire la memoria dello storage host? Cosa succede quando un utente si scatena e inizia ad accendere un numero di macchine virtuali tale da consumare tutta la memoria dell'host? Un altro problema interessante su uno storage che utilizza ZFS è la gestione della memoria utilizzata da ZFS ARC. ZFS è molto aggressivo nell'utilizzo della RAM. Normalmente non si limitano le dimensioni dell'ARC su uno storage normale, perché ZFS rilascia la memoria quando altre applicazioni la richiedono. Ma quando si sa in anticipo che si ha bisogno di quella memoria, è normale indicare a ZFS di non usarla più. Abbiamo costruito un provisioner di memoria dinamico per le macchine virtuali. Non appena si accende una macchina virtuale, il provisioner di memoria controlla se è disponibile una quantità di memoria sufficiente per il sistema operativo stesso e almeno lo 0,2 della memoria totale disponibile per l'ARC dopo l'accensione della macchina virtuale. Se le condizioni sono soddisfatte, limiterà automaticamente l'ARC al valore più vicino possibile alla memoria totale meno la RAM utilizzata dalla macchina virtuale.
Quando si accendono le macchine virtuali, l'ARC sarà come un palloncino e si ridurrà per liberare memoria per le macchine virtuali in esecuzione. Abbiamo scritto uno strumento che utilizza Kstat e MDB (il debugger modulare) per regolare dinamicamente le dimensioni dell'ARC ZFS. Gestione della memoria: fatto.
Poi abbiamo pensato a cosa significhi disaster recovery per una macchina virtuale. Una delle funzioni più utilizzate in Storage OS è la replica del backup del dataset. Supponiamo di replicare in loco o in remoto il Vdisk (volume ZFS) che rappresenta la macchina virtuale, come si potrebbe ricreare facilmente la macchina virtuale che si aveva prima su questo? Non è così facile, perché la configurazione del servizio SMF che si aveva prima non è replicata e sarebbe una vera seccatura farlo. Mi è venuto in mente di allegare la configurazione come proprietà ZFS del volume. Ma poi abbiamo avuto una dimostrazione con uno dei nostri clienti che ha insistito molto per avere due dischi separati per la macchina virtuale. A quel punto è stato chiaro che l'uso di un disco virtuale come backing store non era più sufficiente. Non avevamo alcuna flessibilità. Cosa fare? Comprare una nuova lavagna grande almeno 2 metri, un sacco di pennarelli e dare inizio al processo di progettazione e scarico delle idee :).
Alla fine abbiamo utilizzato un filesystem ZFS per rappresentare una macchina virtuale, una sorta di bundle. All'interno del mountpoint del filesystem salviamo la configurazione della macchina virtuale in un formato JSON (è abbastanza facile trasformare qualsiasi oggetto in una rappresentazione JSON in qualsiasi linguaggio di programmazione, al giorno d'oggi). Forse chi volesse convertire la configurazione della macchina in una configurazione Joyent smart machine, o trasformarla in un xml per libvirt su Linux, potrebbe farlo molto facilmente. Oltre alla configurazione abbiamo creato le immagini dei dischi. Questa volta abbiamo utilizzato il formato nativo di Qemu, qcow2, e abbiamo effettuato il ridimensionamento utilizzando qemu-img. Questo tipo di rappresentazione della macchina virtuale ci ha permesso di inviare/ricevere facilmente un filesystem che incapsula un bundle di macchina virtuale. All'interno del servizio SMF collegato alla macchina virtuale non conserviamo più alcuna configurazione, ma solo un link al percorso del filesystem ZFS che contiene il bundle. Importando una macchina virtuale, lo storage di backup analizza la configurazione e genera tutti i servizi SMF collegati per controllarla. Questa si è rivelata una soluzione ottima e naturale che ci ha permesso una grande flessibilità.
Abbiamo deciso di fornire un modo per connettersi direttamente alla console di una macchina virtuale dal browser, in alternativa a un client VNC tradizionale. Abbiamo creato dei proxy websocket che inoltrano ogni visualizzazione VNC a un websocket. Le visualizzazioni VNC vengono generate automaticamente all'avvio delle macchine virtuali. Questo ci serve per essere sicuri di avere visualizzazioni uniche per ogni macchina virtuale in esecuzione. Avendo le visualizzazioni VNC inoltrate a websocket, abbiamo utilizzato un client VNC Javascript nel browser che abbiamo completamente portato, modificato e ridisegnato per essere utilizzabile all'interno della nostra applicazione di gestione web. Comunichiamo con la macchina virtuale, scattiamo uno screenshot dello stato attuale del display e, non appena si fa clic sull'immagine, si apre un client VNC in browser per la macchina virtuale. Davvero molto bello, a dire il vero.
Questa è più o meno la storia della nostra ultima iterazione. Un sacco di divertimento, un sacco di nuova tecnologia. Speriamo che le persone apprezzino ciò che abbiamo fatto. Siamo sempre aperti a domande su tutto, quindi sentitevi liberi di chiedere :). Grazie.