Nell’articolo precedente abbiamo visto come Test Driven Development e Continuous Integration siano due dei pilastri su cui impostare una corretta ed efficace organizzazione dello sviluppo, che tenga presenti le esigenze dei diversi dipartimenti dell’azienda. In questo articolo vediamo brevemente come si possa passare dalla CI al cosidetto Continuous Development.
Un panorama mutevole
Diciamocelo onestamente: da un lato, in quanto professionisti delle tecnologie IT, spesso sinceramente appassionati alle nostre discipline, le novità, le tendenze appena delineate, le “rivoluzioni” tecnologiche e di processo ci affascinano e ci motivano a svolgere al meglio il nostro lavoro. Dall’altro, tutti i cambiamenti e i vari trend che negli anni si sono succeduti, un po’ ci mettono in crisi, perche’ richiedono studio e aggiornamento, un po’ a volte ci hanno deluso perche’ in certi casi le iniziali promesse di magnifiche sorti progressive non si sono poi concretizzate in reali passi avanti. E, lo ribadisco, questo vale sia per le tecnologie che per le metodologie.
A fronte di questa dichiarazione di scetticismo e di moderazione, diciamo anche che certe novità, anche se inizialmente passate quasi come “mode”, si sono invece dimostrate molto sensate ed efficaci in termini di soluzioni reali per il mondo reale del lavoro. I concetti DevOps, ad esempio, stanno godendo in questi ultimi anni di grande visibilità. E se, come in tutto, c’è anche una piccola parte di “hype”, il fatto che intere aziende importanti si siano spostate su quel binario non può essere dovuto solo a una moda…
Il punto di partenza, quindi, è quello che abbiamo visto nel nostro primo articolo di questa miniserie: Test Driven Development (TDD) e Continuous Integration (CI). Ma c’è un ulteriore passo da fare.
TDD e CI: una visione più ampia
Gli sviluppatori si concentrano sul codice, ed è giusto così, perche’ il codice è la base di tutto e, senza codice, non si consegna nulla, per quanto possano essere ottime le infrastrutture che ci sono intorno. Un buon framework per il deployment serve a ben poco se non c’è il codice… Ma, in presenza del codice, un buon framework per il deployment invece serve eccome e non solo per il deployment, poiche’ è in grado di fornire un feedback e di aiutarci a spostare il nostro modo di pensare verso un’ottica DevOps.
Il ciclo del TDD
Torniamo un attimo a quanto dicevamo nell’articolo precedente a riguardo del Test Driven Development. Lo schema del ciclo TDD è il seguente
- aggiungere un test
- eseguire i test
- sviluppare il codice
- ri-eseguire i test
- modificare il codice di conseguenza (refactoring)
- e così via.
Ma questo schema non va visto “alla cieca”: occorre una proiezione in avanti e l’inserimento di tale processo all’interno di un più ampio workflow che preveda anzitutto la Continuous Integration. Se non abbiamo una comprensione chiara dell’intero flusso di lavoro, e di tutti i passi che esso prevede, non avremo neanche le informazioni necessarie per automatizzare quanto più possibile il processo stesso. E questa automatizzazione è uno degli obiettivi di un approccio basato su TDD, CI e CD.
Strumenti per la Continuous Integration
Fortunatamente, non mancano oggigiorno degli adeguati framework che possano gestire al meglio la Continuous Integration: se guardiamo quello che viene usato nelle varie aziende, gli strumenti più diffusi sono Jenkins, Travis e Bamboo, ma esistono altre soluzioni per chi, ad esempio, utilizza .NET. Tutto questo per dire che, a differenza di alcuni anni fa, non mancano certo le possibilità di scelta…
Jenkins CI
Jenkins CI [1] è un tool di integrazione continua, è scritto in Java ed è open source. Funziona lato server e può essere acceduto da remoto, tramite un normale browser. È compatibile con gran parte dei tool con cui si gestisce il codice sorgente: Concurrent Versions System, Subversion, Git, Mercurial, AccuRev e altri ancora. È inoltre in grado di eseguire progetti realizzati in Ant o Maven. Può essere mandato in esecuzione sia in sincrono con ogni commit che a intervalli di tempo prestabiliti. Grazie alla sua architettura open, sono nati per Jenkins molti plugin, che consentono l’utilizzo di Jenkins tra l’altro con diverse suite di creazione di test automatizzati: JUnit, invece, è supportato nativamente.
Travis CI
Travis CI [2] è anch’esso open source ed è scritto in Ruby. Si tratta precisamente di un servizio cui si può ricorrere per effettuare le build e i test di progetti ospitati su GitHub. Si accede via web, previa registrazione su GitHub, e il servizio è gratuito per i progetti open source, mentre esistono dei piani a pagamento per progetti “privati”. A livello di funzionamento, Travis si comporta individuando automaticamente ogni commit che viene effettuato ed facendo partire di conseguenza una build del progetto e una serie di test. È comunque possibile effettuare anche delle richieste di build e test. I risultati saranno inviati allo sviluppatore nel modo che è stato configurato (ad esempio, per email). Travis supporta progetti scritti in numerosi linguaggi di programmazione: C, C++, C#, Go, Groovy, Java, JavaScript, PHP, Python, Ruby, Scala e molti altri.
Bamboo
Bamboo [3] è un prodotto per l’integrazione continua prodotto da Atlassian, la stessa azienda che realizza JIRA. È un tool lato server che presenta molte funzionalità e la capacità di effettuare build di progetti scritti in quasi tutti i linguaggi o con strumenti come Ant o Maven, oltre a un sistema di notifica molto configurabile per tutte le esigenze. A fronte di questo, si tratta di un prodotto rivolto a un utilizzo quasi esclusivamente a pagamento, anche se esistono particolari piani di sconto per situazioni come l’uso accademico o filantropico.
Cosa offrono i framework di CI al dipartimento sviluppo
Usare un framework di Continuous Integration come Jenkins ci permette di effettuare i processi di test tutte le volte che il codice viene modificato e inviato con il commit all’interno del repository di sviluppo. Sulla base di questi test, avremo dei risultati che verranno notificati tramite dashboard. Questo feedback diventa cruciale nell’ambito del ciclo di sviluppo perche’ ci assicura che abbiamo codice ben testato che viene integrato nel repository.
E da qui comincia la seconda parte della nostra storia.
Da Continuous Integration a Continuous Deployment
Fin qui, tutto bene: il codice è stato scritto, i test automatici lo hanno condotto a destinazione e hanno consentito di rimuovere bug e problemi. Qui ci viene in aiuto il framework di CI prescelto poiche’ è in grado di effettuare una build e di effettuarne il deployment nell’ambiente UAT.
User Acceptance Test
UAT sta per User Acceptance Test, ossia quei test che non sono più “meccanici” o di sistema, ma che si rivolgono a un utente vero e proprio che deve valutare non più gli aspetti strettamente programmatici, ma verificare che la soluzione cui si è arrivati funzioni per… l’utente finale. In pratica, visto che il software viene (o dovrebbe essere) sviluppato per svolgere alcune funzioni che soddisfino alcune necessità dell’utente, si va a verificare proprio questo, effettuando dei test che replicano quello che si farebbe con quell’applicazione (o con quella parte di applicazione) in uno scenario reale di utilizzo. Gli User Acceptance Test servono come verifica finale che le funzionalità previste nei famosi requisiti di business siano presenti e, soprattutto, che funzionino risolvendo il bisogno per cui quel software è stato pensato e sviluppato.
Una volta che il processo di UAT è stato completato, siamo pronti per l’approvazione prima della vera e propria release.
Figura 1 – Il diagramma di processo della Continuous Delivery [4].
Qui si comincia a parlare di Continuous Development in cui il codice viene immesso in un ambiente di lavoro operativo. Ed è qui che entra in gioco. il personale delle operations che in effetti usa (e magari vende) queste soluzioni e che deve impiegare alcuni strumenti (Chef, Puppet, System Center Configuration Manager e altri) per configurare e gestire tale software.
Gestione della configurazione con Chef
Tra gli strumenti più utili per la gestione della configurazione, Chef [6] è uno di quelli più potenti e versatili, e non a caso è utilizzato da grandissime aziende: Mozilla, Facebook, HP, Prezi sono solo alcuni dei nomi che dovrebbero bastare a dare un’idea. Di fatto, Chef si può integrare con piattaforme cloud (Amazon EC2, Google Cloud Platform, Microsoft Azure, Open Stack e altre) per effettuare in maniera automatica il provisioning e la configurazione di nuove macchine, sia “fisiche” che virtuali.
Chef è scritto in Ruby e in Erlang e si basa su una metafora che ne spiega il nome: l’utente scrive delle ricette (“recipes”) per la configurazione del sistema usando un DSL che è puro Ruby. Queste ricette indicano come devono essere gestite e configurate le applicazioni lato server, le utilità, gli elementi quali Apache HTTP Server o MySQL, i pacchetti che devono essere installati, i servizi che devono essere in esecuzione e i file che devono essere scritti. Chef, proprio come un bravo capocuoco, sorveglia che tutti gli ingredienti della ricetta siano presenti nelle giuste dosi e che siano combinati nell’ordine esatto in cui devono essere cucinati. Queste ricette possono oltretutto essere raccolte in maniera organizzata all’interno di ricettari o libri di cucina (“cookbook”): ad esempio può essere utile avere una serie di ricette per cucinare lo stesso “piatto” usando però versioni diverse degli elementi che serviranno.
Chef consente di automatizzare e semplificare la configurazione dei server dell’azienda, inviando automaticamente le nuove ricette a tutte le macchine previste, che in tal modo rifletteranno i cambiamenti di configurazione in maniera sincronizzata.
Non si deve pensare, però, che una soluzione come Chef vada bene solo per i colossi IT che abbiamo citato sopra: Chef si presta bene a essere utilizzato anche in ambienti operativi di dimensioni molto più ridotte, laddove comunque sia necessario mantenere “allineate” diverse macchine e siano richiesti frequenti cambiamenti di configurazione. I costi della soluzione variano di conseguenza.
Cosa offre Chef al dipartimento operations
Usare un sistema di gestione della configurazione come Chef rende i processi del ramo Ops decisamente meno costoso… e meno complicato: tutte le operazioni diventano più efficienti. Ma c’è un altro aspetto da considerare: la “misurabilità“. Con Chef, è possibile effettuare misurazioni che ci danno un quadro generale sull’efficienza e sull’affidabilità di tutto questo processo. E sappiamo che, così come succede con l’integrazione gestita da Jenkins, con Chef il nostro deployment sarà pulito e (abbastanza) indolore.
In pratica, stiamo prendendo buone pratiche dal settore sviluppo (Dev) e le stiamo portando al settore operations (Ops). I riflessi di queste azioni saranno evidenti: deployment più “prevedibili”, applicazioni che si comportano sostanzialmente nel modo in cui erano state sviluppate; il codice iniziale viene “tradotto” in una esperienza finale dell’applicazione piuttosto affidabile.
Il flusso e l’infrastruttura
Il server Jenkins riceverà il messaggio di “via libera” sulla base dei test automatizzati e dell’approvazione dei test UAT. Questo farà partire il processo di build e di deployment. In questo senso, il deployment in produzione è reso relativamente semplice poiche’ si usa Chef per garantire processi ripetibili (le “ricette”) che costruiscono i nostri ambienti di produzione in maniera conforme e allineata. Questa non è garanzia di infallibilità ma, se non altro, ci permette di avere un deployment del codice applicativo in ambienti di produzione di buona qualità e con caratteristiche note. Il classico “Ma come? Sulla mia macchina funziona…” diventa un’espressione più rara ed è possibile più facilmente capire cosa cambia e dove sta la differenza.
In figura 2 è possibile vedere uno schema concettuale dell’infrastruttura. Nel prossimo articolo allergheremo lo sguardo ai vari prodotti (oltre a Jenkins e Chef) che si possono inserire in questo schema.
Figura 2 – Uno schema concettuale dell’infrastruttura che, attraverso la CI, porta dal dipartimento di sviluppo al ramo operations [5].
In sostanza, abbiamo creato una sorta di “conduttura” obbligata in cui far scorrere il nostro flusso di lavoro. All’imbocco della “tubatura” c’è il codice, e alla fine del percorso esce la nostra applicazione funzionante nell’ambiente di produzione. Ma il processo non è solo lineare: esiste infatti la possibilità di ricevere feedback ai diversi stadi del flusso (sia nella parte Dev che nella parte Ops), dai vari tipi di test. E questo feedback deve essere reinserito in circolo per apportare modifiche e miglioramenti a tutto il flusso (sia nella parte di sviluppo che in quella di produzione operativa). Non siamo più concentrati solo sullo sviluppo o sul deployment in produzione o sulla configurazione: siamo concentrati sull’applicazione nel suo complesso e lungo tutto il suo ciclo di vita, dallo sviluppo alla produzione. È questo il passaggio da Continuous Integration a Continuous Development. Certo, anche qui a volte le cose andranno male… ma se non altro abbiamo un flusso ben chiaro e possiamo risalire all’interno della “conduttura” per capire dove sta il problema.
Conclusioni
Mettere insieme integrazione continua e deployment continuo ci porta verso il concetto di sviluppo continuo. Nel prossimo articolo vedremo le attività tipiche di una giornata di lavoro in un ambiente CI/CD, in cui analizzeremo i diversi passi all’interno del workflow descritto qui.