Vagrant ricrea una macchina virtuale di sviluppo a partire da un file di testo: vagrant up legge un Vagrantfile, scarica un’immagine base, configura rete e cartelle condivise, esegue gli script di provisioning e lascia una VM raggiungibile via vagrant ssh. Lo stesso file, committato nel repository, dà lo stesso ambiente su ogni macchina del team. La versione 1.1.0 è uscita oggi, 14 marzo 2013 (CHANGELOG).
Contesto
Quando l’ambiente di uno sviluppatore non coincide con quello di un altro — o con quello di produzione — il costo torna a ogni ciclo. Versioni diverse dell’interprete, una libreria di sistema che manca, un demone configurato a mano due anni fa e mai documentato: messe insieme, queste differenze producono il caso classico in cui il codice gira su una macchina e si rompe su quella accanto, senza che la causa compaia da nessuna parte nel repository.
Le risposte di sempre sono due, e nessuna delle due basta. Il documento di setup — il README con la lista dei pacchetti da installare — invecchia nel momento in cui qualcuno aggiorna l’ambiente e si dimentica di aggiornare il testo. La macchina condivisa di staging accentra la configurazione ma fa tornare la contesa: una modifica rompe il lavoro di tutti.
Vagrant, scritto da Mitchell Hashimoto e arrivato alla 1.0 il 14 marzo 2012 (Wikipedia), porta la configurazione dentro il repository e la rende eseguibile. L’ambiente smette di essere una procedura da seguire e diventa un artefatto che si ricostruisce da zero con un comando.
Architettura
Il Vagrantfile è codice Ruby con una DSL dichiarativa. Un esempio minimo:
Vagrant.configure("2") do |config|
config.vm.box = "precise64"
config.vm.network :private_network, ip: "192.168.33.10"
config.vm.synced_folder ".", "/vagrant"
config.vm.provision :shell, path: "setup.sh"
end
La stringa "2" passata a configure indica la versione del formato di configurazione, non quella di Vagrant: la 1.1.0 introduce questo schema lasciando leggibili i Vagrantfile scritti per la 1.0.x che non usano plugin (CHANGELOG).
config.vm.box indica l’immagine base — un box, cioè una VM già pronta ed esportata in un formato che Vagrant sa importare. synced_folder monta dentro la VM la directory di progetto dell’host, così l’editing resta sull’host e l’esecuzione avviene nell’ambiente isolato. provision aggancia il passo di configurazione: una shell, oppure uno dei sistemi di configuration management previsti (Puppet, Chef, Ansible, Salt).
Il ciclo di vita gira su pochi comandi: vagrant up crea e avvia, vagrant ssh apre una shell, vagrant reload riapplica la configurazione, vagrant halt ferma, vagrant destroy cancella la VM e lascia intatto il Vagrantfile. La cancellazione è la primitiva interessante: rifare l’ambiente da zero diventa un’operazione ordinaria, non un incidente da scongiurare.
Il punto critico: provider e plugin
Fino alla 1.0.x Vagrant girava solo su VirtualBox. La 1.1.0 introduce il sistema dei provider: il workflow (up, ssh, provision, destroy) viene scollegato dal motore di virtualizzazione che lo esegue. VirtualBox resta il provider di default, gratuito; gli altri backend si aggiungono come plugin, installabili con vagrant plugin install (CHANGELOG).
Il primo backend alternativo di HashiCorp è il provider AWS, distribuito come plugin con licenza MIT in occasione della 1.1.0 (HashiCorp, Preview: Vagrant AWS Provider). Con lo stesso Vagrantfile — a meno delle specificità del provider — si fa partire un’istanza EC2 invece di una VM locale:
vagrant up --provider=aws
La conseguenza pratica è netta. Il Vagrantfile descrive cosa deve essere l’ambiente — pacchetti, rete, provisioning — e lascia al provider il dove viene materializzato. Lo stesso descrittore vale per la VM sul portatile e per l’istanza in cloud, finché box e provisioning reggono su entrambi i backend.
I plugin sono gem Ruby con un punto d’aggancio noto a Vagrant. La 1.1.0 li gestisce dall’interfaccia vagrant plugin e dichiara l’intenzione di tenerli caricabili anche nelle versioni successive (CHANGELOG). I Vagrantfile della 1.0.x che usavano plugin vanno ripuliti prima dell’aggiornamento; quelli senza plugin restano compatibili.
Implicazioni
Il provisioning è il punto in cui Vagrant tocca il configuration management. Per i casi semplici basta uno script shell; per gli ambienti che riproducono la produzione conviene lo stesso strumento già in uso lì. Se la produzione va a Puppet, gli stessi manifest provisionano la VM di sviluppo, e l’ambiente locale eredita le scelte di configurazione della produzione invece di approssimarle a mano. Vagrant non costringe a una scelta: resta il collante fra il descrittore della macchina e lo strumento di configurazione, qualunque sia.
Poi c’è il versionamento. Tenere il Vagrantfile sotto controllo di versione vuol dire che la storia dell’ambiente coincide con la storia del codice: un commit che aggiunge una dipendenza di sistema tocca il Vagrantfile nello stesso changeset che tocca il codice. Chi fa git pull e vagrant reload si ritrova l’ambiente aggiornato senza istruzioni a parte.
In questo periodo è spuntato anche un altro modo di isolare. Docker, presentato pubblicamente questo mese (dotCloud), distribuisce container che condividono il kernel dell’host: più leggeri di una VM, ma legati alla compatibilità con quel kernel. Vagrant lavora a un livello diverso — una macchina virtuale completa, con kernel proprio — e i due strumenti coprono esigenze distinte: un container non rimpiazza la VM quando devi testare su un sistema operativo diverso da quello dell’host.
Limiti
Una VM completa costa. Avviarne una richiede RAM e un tempo di boot che si misura in decine di secondi; un progetto con più servizi su VM separate satura in fretta un portatile. Dove basta isolare filesystem e processi, il peso di una VM è sproporzionato.
La distribuzione dei box, al 14 marzo 2013, è ancora artigianale: i box stanno su server HTTP indicati per URL, senza un registry pubblico che ne garantisca provenienza e versione. La riproducibilità dipende dalla stabilità di quegli URL e dall’immutabilità delle immagini, due garanzie che oggi nessuno offre in forma esplicita.
Il sistema dei provider è appena nato. Il CHANGELOG della 1.1.0 lo definisce esplicitamente lavoro di base, con cambiamenti attesi lungo tutto il ciclo 1.x. Scrivere oggi un Vagrantfile che dipende da un provider diverso da VirtualBox significa mettere in conto che l’interfaccia si muova nelle versioni vicine.
https://github.com/hashicorp/vagrant/blob/v1.1.0/CHANGELOG.md https://www.hashicorp.com/blog/preview-vagrant-aws-provider https://en.wikipedia.org/wiki/Vagrant_(software) https://www.docker.com/ https://www.noze.it/insights/vagrant-dev-environments/
Immagine di copertina: Mitchell Hashimoto, autore di Vagrant, parla a un microfono durante un intervento alla conferenza RuPy 2012 — foto di Nils Pascal Illenseer, CC BY 3.0 — https://commons.wikimedia.org/wiki/File:Rupy_Talk_Mitchell_Hashimoto_(18945835).jpeg