Nei due articoli precedenti abbiamo dato un’occhiata a un approccio integrato per lo sviluppo e il rilascio di software, in contesti di dimensioni ragguardevoli, parlando di Test Driven Development, Continuous Integration, Continuous Deployment e DevOps. In questo breve articolo, diamo un’occhiata alle attività tipiche di un tale scenario, con particolare riferimento all’integrazione tra Dev e Ops.
Riprendiamo le fila…
Nei due articoli precedenti abbiamo presentato una serie di considerazioni in ordine allo sviluppo di software in contesti grandi, alla sua distribuzione e alla sua configurazione. Ci teniamo a ribadire che, in situazioni come queste, occorre avere una visione “globale”. Metodologie di sviluppo, aspetto “umano” e organizzativo, infrastrutture tecnologiche, policy aziendali sono facce diverse di uno stesso elemento e i problemi grossi nascono proprio quando ci si dimentica di ciò e ci si concentra, magari con le migliori intenzioni, su un solo aspetto trascurando l’integrazione con gli altri. La lezione appresa dalla Continuous Integration va riportata, a più alto livello di astrazione, sul piano organizzativo e gestionale.
Infrastrutture tecnologiche efficienti sono fondamentali, ma da sole non garantiranno che il nostro software sia sviluppato e testato nel modo migliore. Gruppi di sviluppatori organizzati e motivati sono la chiave di volta della produzione, ma da soli non potranno fare molto se non sono inseriti in un più ampio contesto che tenga presenti le metodologie da loro adottate e fornisca loro le necessarie infrastrutture… E così via. Le solite banalità, dirà giustamente qualche lettore. Ma il fatto che siamo sempre a parlarne… indica proprio che la realtà è spesso diversa da quanto si legge sui blog o si ascolta alle conferenze.
Test Driven Development, Continuous Integration e Continuous Deployment, DevOps dovrebbero essere oramai termini ben conosciuti anche a chi non sia inserito in questo tipo di contesto. Abbiamo visto nello scorso articolo anche i prodotti tecnologici che servono per la nostra infrastruttura, presentando a sommi capi Jenkins [1] come server di CI e Chef [2] come tool per la configurazione sincronizzata nei deployment. È arrivato il momento di descrivere, almeno sommariamente, come queste cose entrano in relazione con loro.
Il flusso del codice
Di fatto, il “materiale” di cui ci occupiamo nel nostro flusso di produzione è codice. Le applicazioni sono fatte di codice, e i dati sono fatti di codice. Ci stiamo spostando anche verso un mondo in cui l’infrastruttura è codice (il concetto di Infrastructure-as-Code).
Dal lato degli sviluppatori, abbiamo visto la grande importanza dei test. Dal lato “operations” abbiamo visto come negli ultimi anni le cose siano andate cambiando drasticamente, sebbene per fortuna sia arrivata in “soccorso” di chi ci lavora una serie di strumenti e di buone pratiche che ne hanno semplificato la quotidianità.
La “giornata tipo”
Discutere del calciomercato, complimentarsi con il più giovane del team per la la nuova moto acquistata, fare considerazioni di tipo vagamente filosofico davanti alla macchina del caffè… No, non ci riferiamo a questo, ovviamente. Parliamo invece di workflow, del flusso di processo che porta dallo sviluppo al deployment e che rappresenta le attività tipiche di una giornata in cui si procede a sviluppare con questi criteri.
Passare la palla
Nei precedenti articoli ci siamo focalizzati principalmente sugli aspetti legati allo sviluppo. TDD, importanza dei test, integrazione continua: sono tutti elementi che attengono primariamente al lato sviluppo. Poi abbiamo iniziato anche a guardare un po’ il lato “operations”. E adesso ci concentreremo maggiormente su questo secondo ramo, nel momento in cui gli sviluppatori passano la palla a chi il software lo deve “deployare” e configurare.
Dallo schema concettuale agli strumenti reali
Nell’articolo precedente avevamo presentato lo schema concettuale dell’infrastruttura che, attraverso la CI, porta dal dipartimento di sviluppo al ramo operations. La riprendiamo qui (figura 1) allargata e ampliata. Non c’è più solo il modello concettuale (la fascia centrale) ma sono riportati anche gli strumenti pratici che consentono di concretizzare nella realtà tutte le varie operazioni.
Figura 1 – Lo schema concettuale dell’infrastruttura CI/CD si allarga a mostrare gli strumenti concreti utilizzati [3].
La parte Dev ha come compito fondamentale quello di mettere codice dentro il repository (“repo” nella figura) e procedere alla build, in maniera che, iterazione dopo iterazione, si arrivi alle applicazioni.
La parte Ops, invece deve occuparsi fondamentalmente del processo di deployment che porterà il tutto fino ai test di accettazione utente (UAT, User Acceptance Tests) e alla collocazione in produzione.
Configurazione dell’ambiente
Dal punto di vista delle operations, uno dei principali risultati cui bisogna puntare nell’ambito del deployment dell’applicazione è la “coerenza” della configurazione d’ambiente. L’ambiente di esecuzione, infatti, deve essere tale da mantenere certe caratteristiche facilmente individuabili e ripetibili. Occorrono quindi stabilità, uniformità e una standardizzazione facilmente replicabile.
Per questo ci vengono in aiuto le famose “ricette” (recipes) di Chef [2]. Con queste, avremo la possibilità di creare tramite programmazione delle modalità ripetibili per effettuare la build e completare il deployment dell’applicazione verso gli UAT e la produzione. In pratica, manteniamo un ambiente d’esecuzione coerente e uniforme per tutto il flusso di lavoro, usando la stessa ricetta per UAT e produzione. Questa uniformità rappresenta una facilitazione e una “assicurazione” per sviluppatori, amministratori e, non ultimi, utenti finali.
Dall’approvazione al rilascio: un’occhiata al flusso del processo
I passi finali di UAT e rilascio devono essere attivati da un’approvazione. Fin qui, tutto il processo è automatizzato (o perlomeno dovrebbe esserlo…). Il codice effettua dei passaggi controllati a partire dalla scrittura, dal superamento dei test di unità, fino alla build: ne abbiamo parlato nel primo articolo, ed è un workflow piuttosto conosciuto e adottato. I test automatici di accettazione vengono passati e gli sviluppatori ottengono il via libero (la “luce verde”): quindi le build negli ambienti di sviluppo sono OK. Però a questo punto occorre passare verso le build negli ambienti di User Acceptance Tests fino a quelli della produzione. Qui occorre stare attenti, ma abbiamo oggi a disposizione strumenti e pratiche che ci vengono in aiuto.
Maggiordomi e cuochi
Con una metafora che si rifà all’organizzazione di una casa signorile di altri tempi, prodotti quali Jenkins [1] e Chef [2] entrano in gioco per consentirci una gestione lineare di questa delicata fase.
Il server Jenkins può lanciare le azioni di Chef sulla base di un “trigger” di approvazione: e questo processo viene fatto partire da chiunque abbia adeguati diritti all’interno del server Jenkins. In tal modo si è sicuri che solo alcune persone all’interno del gruppo di lavoro saranno “autorizzate ad autorizzare” il deployment, senza venire meno a quei vincoli dettati dalle più elementari regole di sicurezza e relative alle liste di controllo degli accessi.
Le ricette del giorno
Bene, siamo al deployment grazie al via libera dato a Chef. Abbiamo visto come questo sistema di gestione delle configurazioni si basi sul concetto di “ricetta” ossia recipe: una raccolta completa di parametri e informazioni relativi ad ambiente di esecuzione, versioni, dipendenze etc. che possiamo salvare e richiamare. Il principio è: se ho una ricetta ben definita, posso effettuare il deployment in maniera uniforme e costante, ottenendo lo stesso risultato a parità di stesse condizioni. Questa è una delle ragioni per cui le recipes andrebbero sempre salvate con un sistema di versionamento, in maniera da poter andare a controllare quali modifiche siano intervenute nel frattempo tra diversi deploy.
Molte “ricette base” di Chef possono essere trovate a partire dalla pagina di sviluppo di questo sistema di gestione della configurazione. Ma il punto d’arrivo di un workflow ben organizzato è il seguente: sviluppatori e amministratori dovrebbero mettere a punto insieme le recipes che saranno impiegate nel processo. In pratica, la parte Dev e il ramo Ops lavorano a stretto contatto in maniera che quest’ultimo possa creare le ricette giuste per mettere in produzione le applicazioni sviluppate, secondo l’adeguata configurazione degli ambienti di sviluppo. È, oltretutto, un processo che consente un’ampia scalabilità, fattore da non sottovalutare.
Finalmente ci siamo…
Il punto d’arrivo del deployment è rappresentato dai test funzionali: Jenkins registra la cronologia delle build e mostra tutti i via libera che sono stati ottenuti nel corso di questo processo, oltre a inviare email automatizzate che avvertono i diversi attori coinvolti nel processo del fatto che è stata effettuata la build nell’ambiente di produzione. E ciò che succede nell’infrastruttura di produzione viene a sua volta sottoposto a test automatizzati o anche con utenti reali.
Iterazioni e feedback
Abbiamo descritto per sommi capi le operazioni che si svolgono in un processo di tipo DevOps. Non intendevamo in alcun modo illustrare nei dettagli la metodologia DevOps, su cui magari torneremo in futuro in queste pagine. Ci interessava invece spiegare alcuni meccanismi per far emergere certi principi, specie a beneficio di chi non conosce la metodologia.
E uno dei principi più importanti che deve emergere è quello della “retroazione circolare” che, diciamolo francamente, suona meglio in inglese: feedback loop.
Ogni volta che si descrive a sommi capi un processo, infatti si tende a vedere il workflow come uno svolgimento lineare: una sorta di fiume con una sorgente, un corso e una foce. In realtà, e lo sappiamo bene, il processo è al tempo stesso lineare (dall’inizio alla fine) e circolare (il risultato finale rientra in circolo influenzando l’intero processo). A questo possiamo aggiungere il fatto che il feedback non deve rientrare in circolo solo alla fine, ma che ci deve essere una continua attenzione a prendere in considerazione il feedback in ogni fase del processo (figura 2).
Figura 2 – Di un processo si può vedere lo svolgimento lineare (a), considerare il feedback alla fine (b) e prendere in considerazione i numerosi feedback rintracciabili a mano a mano che il flusso procede (c).
Conclusioni
In questa miniserie abbiamo cercato di mostrare a grandi linee cosa accade in un processo di sviluppo che, partendo dal Test Driven Development, attraverso la Continuous Integration e il Continuous Deployment, arriva a prospettarsi in un approccio DevOps. Molti aspetti di questo processo andrebbero certamente approfonditi, ma ci premeva più che altro mettere in luce i principi che ne sono alla base e certe considerazioni relative al flusso di lavoro.