Di recente ho seguito una presentazione su strumenti e tecniche di configuration management che mi ha lasciato uno spunto interessante da approfondire.
Tutti concordano sul fatto che gli strumenti di configuration management infrastructure-as-code siano essenziali e di grande valore, ma allo stesso tempo la frustrazione nel loro utilizzo resta piuttosto alta. E se i meccanismi attuali di configuration management o di infrastructure-as-code smettessero di essere un disastro o un incubo da usare? Che aspetto avrebbero?

La mappa non è il territorio: eppure si tende a confondere i due piani.
Questo articolo esplora come il DevOps e lo sviluppo software in generale possano trarre vantaggio da un nuovo modo di intendere il provisioning e la configurazione dei sistemi, rileggendo sotto una nuova luce alcuni termini ormai diffusi.
Partiamo da una terminologia che ci aiuterà a dare un senso al mondo che ci circonda e ad affrontare alcuni dei problemi che molti stanno vivendo.
La terminologia spiegata
Operations: la pratica che assicura che i sistemi software siano in buono stato di funzionamento e rispondano alle esigenze degli utenti. L'operations engineer si occupa in misura minore di scrivere nuovo codice; il suo focus principale è il comportamento del software esistente all'interno di un sistema già in funzione.
Definiti questi due termini, possiamo ora spiegare in cosa il "forward engineering" si differenzia dal "reverse engineering".
Forward engineering
Il forward engineering consiste nel costruire un sistema a partire da un modello di ciò che dovrebbe esistere al suo interno. In genere, l'FE crea prima il modello e poi distribuisce e configura quel template in produzione. L'aspettativa è che il sistema effettivamente ottenuto rispecchi fedelmente il modello.
In molti casi, però, il modello dichiarato è ambiguo e il sistema può comportarsi in modi imprevisti, lontani dalle intenzioni iniziali. Già questo rappresenta un ostacolo significativo nell'utilizzo di simili tecniche, soprattutto per chi è alle prime armi.
Come se non bastasse, il sistema stesso si discosta spesso dal modello a causa dell'interazione con il proprio ambiente: ad esempio quando più componenti modificano reciprocamente la configurazione, oppure quando procedure ad-hoc eseguite dagli engineer fanno deviare il sistema in vari modi.
Nel forward engineering manca una vera nozione di feedback verso il modello iniziale. Il modello viene prima "scolpito nella pietra" e poi distribuito. Qualsiasi feedback successivo proveniente dal sistema sul suo comportamento può essere utilizzato solo per nuove evoluzioni di modelli e deployment futuri.
Reverse engineering
Il reverse engineering consiste nel ricavare un modello parziale a partire da un sistema esistente e usarlo come base per le modifiche al sistema stesso. Non c'è alcun modello iniziale: è lo stato attuale del sistema a permettere di scoprire come le cose dovrebbero funzionare.
Le modifiche a un sistema di questo tipo, in molti casi, vengono apportate ad-hoc direttamente sul sistema, spesso senza una comprensione completa dell'insieme. Qui il feedback è immediato e l'esplorazione del sistema avviene principalmente "stuzzicandolo" e osservando il feedback generato.
Dove troviamo il reverse engineering? Avrete probabilmente sentito che gli esperti di sicurezza ricorrono a pratiche di RE per analizzare il software esistente e individuarne le falle. Ad esempio, scansionano servizi di rete o memoria per scovare comportamenti anomali e sfruttarli, inducendo il sistema a comportarsi in modo non previsto.
Il reverse engineering ruota tutto intorno all'osservazione di un sistema o software esistente: a partire da ciò che fa nella pratica, si cerca di capire dove si discosta dall'intento originario.
Dove ritroviamo queste idee nel mondo del DevOps?
Infrastructure as code
Sin dal rilascio della prima versione di CFEngine, nel 1993, l'idea di modellare l'infrastruttura come codice ha conosciuto un'impennata di popolarità nel settore del software engineering. Tutto è cominciato dichiarando come dovesse apparire il contenuto di un sistema operativo server, per poi adattarsi rapidamente a descrivere il layout e la configurazione delle odierne risorse cloud. Negli anni si sono alternate aziende e strumenti di vario successo, come Puppet, Chef, Salt, Ansible, CloudFormation, Terraform, CDK, Pulumi e numerose altre soluzioni artigianali.
Ciò che accomuna la maggior parte degli strumenti citati è la natura dichiarativa. Gli strumenti dichiarativi ci permettono solo di specificare come le cose dovrebbero essere, senza esplicitare i passaggi per arrivarci. Negli ultimi anni sono emersi diversi strumenti che si presentano come più imperativi, ma di fatto restano in larga parte dichiarativi e ben distanti dai tradizionali linguaggi imperativi usati per scrivere codice.
Perché la maggior parte di questi strumenti è dichiarativa?
In breve, perché è il modo più semplice di creare mappe, modelli e template. All'engineer basta dichiarare come le cose dovrebbero apparire e dove ciascun elemento dovrebbe trovarsi, delegando al sistema il compito difficile di capire come dare a ogni elemento la forma giusta. Questo consente miglioramenti iterativi nel tempo e porta, in ultima analisi, a sistemi più robusti e stabili.
Un altro vantaggio comune degli strumenti dichiarativi di infrastructure-as-code è la proprietà di idempotenza. Idempotenza significa ripetere un'azione più volte ottenendo sempre lo stesso risultato. Quando si usa uno strumento idempotente per modificare ripetutamente un sistema, questo arriverà infine allo stato dichiarato dal template. Applicare di nuovo lo stesso template non avrà ulteriori effetti sul sistema. L'idempotenza è la proprietà che ci permette di applicare il modello più volte e di vedere il sistema cambiare solo quando ci sono differenze rispetto al modello stesso.
Un approccio imperativo richiederebbe invece di individuare prima tutte le differenze tra modello e sistema e poi di implementare ciascun passaggio necessario per ogni modifica. Gli strumenti dichiarativi di infrastructure-as-code ottengono lo stesso risultato senza che lo sviluppatore debba scrivere personalmente tutte le azioni.
Gli strumenti di infrastructure-as-code sono diffusi e indispensabili: fanno risparmiare migliaia di ore-persona grazie a template riutilizzabili e permettono di replicare sistemi pressoché identici in ambienti diversi con uno sforzo marginale minimo.
L'infrastructure-as-code promette che il miglioramento iterativo del modello nel tempo conduca a sistemi robusti e stabili basati su quello stesso modello.
Un mio precedente articolo sulla configurazione per più sistemi spiega perché disporre di repliche di sistemi quasi identici sia un vantaggio per le aziende del software.
Però! Questi strumenti adottano una mentalità da forward engineering: sono dichiarativi e non hanno alcun meccanismo per ricevere feedback da un sistema reale. Un approccio del genere ha generato negli anni numerosi problemi e lamentele da parte degli utenti, su cui ora possiamo soffermarci.
I problemi di un'infra-as-code basata solo sul forward engineering
Avete mai sentito parlare di Configuration Drift? Si verifica quando il modello dichiarato non corrisponde più allo stato del sistema. Ogni sistema, dopo un numero sufficiente di aggiornamenti, finirà quasi sempre per discostarsi dal modello con cui è stato creato.
Il drift può manifestarsi quando uno sviluppatore modifica il codice del modello senza aggiornare tutti i sistemi creati a partire da esso, oppure quando un engineer compie operazioni esplorative ad-hoc e modifica un sistema senza poi tornare al template per aggiornarne il codice. Entrambe le attività sono essenziali: gli sviluppatori fanno la prima per introdurre miglioramenti nelle iterazioni future dei deployment, mentre gli operations engineer fanno spesso la seconda per scoprire problemi sconosciuti e risolverli.
Il configuration drift è, ovviamente, un campo minato sempre in agguato, ed è per questo che tutti raccomandano di evitarlo. Ma è realistico vietare agli operatori di intervenire sui sistemi per impedire qualsiasi esplorazione ad-hoc? Sì. Alcune aziende impongono che nessun operatore o sviluppatore possa toccare un sistema "di produzione vivo". Potete immaginare l'effetto di una simile politica sul Mean-time-to-Recover di questi sistemi. Per esperienza personale, quando le cose si mettono male e un sistema di "produzione" si rompe per qualsiasi motivo, questa è la prima regola a saltare, e l'esplorazione ad-hoc viene concessa all'istante a chiunque sia in grado di capire cosa stia succedendo.
Gli engineer attratti dall'adozione dell'infrastructure-as-code lamentano l'eccessivo lavoro necessario per rimodellare da zero un sistema esistente. C'è sempre qualcuno che crea uno strumento per dare una mano. Google ha GCP Terraformer, AWS aveva il dismesso AWS CloudFormer, persino Azure ha il suo ARM disseminato in tutta la console cloud. Per qualsiasi linguaggio di configuration management c'è sempre qualcuno che costruisce uno strumento del genere, perché la domanda è altissima.
Purtroppo, però, una volta provato uno di questi strumenti, gli engineer di solito rimangono delusi. Il risultato è troppo rumore privo di senso oppure, nel migliore dei casi, qualcosa già obsoleto il giorno dopo. Il massimo che si può fare con un template ottenuto via reverse engineering è copiare e incollare qualche frammento in un template scritto a mano altrove.
Migliorare la strada da percorrere
Questo articolo ha definito due termini e spiegato perché un'ingegneria basata solo sul forward engineering sia subottimale per le operations effettive dei sistemi e perché, nella maggior parte dei casi, il reverse engineering resti indispensabile per individuare e risolvere i problemi.
La proposta è dunque quella di dare vita a una nuova generazione di strumenti di Infrastructure-as-Code: strumenti che integrino il reverse engineering come elemento centrale e consentano al feedback proveniente dai sistemi reali di aggiornare i modelli da cui sono stati generati. Un operatore potrà così scegliere se adottare la modifica ad-hoc oppure rifiutarla, riportando il sistema allo stato dichiarato nel modello originale.
Gli strumenti standard di settore oggi disponibili non aiutano gli engineer a risolvere i problemi che emergono nelle operations dei sistemi. È vero, esistono diversi strumenti capaci di analizzare i template Terraform e segnalare errori o configurazioni sbagliate. Questi audit sono utili, ma il vero valore di un simile aiuto sta nell'ispezionare un sistema reale e funzionante, non solo il template che gli engineer hanno usato a suo tempo per crearlo.
Per quanto ne so, non disponiamo ancora di uno strumento abbastanza valido da integrare maggiori pratiche di reverse engineering nel lavoro quotidiano degli operations engineer. Da un lato abbiamo molti strumenti di monitoring e observability, dall'altro tanti strumenti di infrastructure-as-code, con un divario significativo nel mezzo.
Avete idee su come colmare questo divario? Fatecelo sapere!
Immagine di copertina creata da Tabea Schimpf su Unsplash.