Caratteristiche, deployment e vantaggi dell’architettura a microservizi

Caratteristiche, deployment e vantaggi dell’architettura a microservizi

Architettura a microservizi

Il software, per sfruttare le capacità di un’architettura a memoria distribuita, dovrà adottare tecnologie, paradigmi e stili architetturali appositi, che permettano di ottenere tutti i vantaggi che i sistemi distribuiti possono offrire. Esistono alcuni software, denominati middleware, come, ad esempio, i cluster management software, che offrono alle applicazioni un insieme di servizi per facilitare alcune operazioni sul sistema distribuito, solitamente relative alla comunicazione e gestione dei dati.

Sicuramente, questi software sono necessari in un sistema distribuito, poiché permettono all’applicazione di evitare di risolvere problematiche comuni in questo tipo di sistemi. Non possono però consentire all’applicazione di ignorare la presenza del sistema distribuito sottostante: l’applicazione dovrà comunque adottare un’architettura applicativa che ne permetta la distribuzione su più nodi, se si desidera sfruttare al massimo i vantaggi di un sistema distribuito. Tali vantaggi sono riconducibili, principalmente, alle performance ed alla tolleranza ai guasti.

Nel corso degli anni, la diffusione e adozione di sistemi distribuiti è aumentata notevolmente, anche grazie alla facilità della loro realizzazione grazie ai servizi cloud; di conseguenza, sono stati sviluppati e perfezionati anche i paradigmi di programmazione e gli stili architetturali per sfruttarli appieno. Tra questi, quella che attualmente risulta essere la più innovativa, è l’architettura a microservizi.

I problemi del software monolitico

Originariamente, le applicazioni erano sviluppate organizzandole in un’unica unità di deployment; tutta la logica applicativa veniva cioè concentrata in un singolo processo applicativo, che non poteva essere separato.

Un’architettura di questo genere si è rivelata molto problematica, sia dal punto di vista dello sviluppo, che dal punto di vista del deployment e della scalabilità. Infatti, aggiungendo funzionalità all’applicativo si finisce, inevitabilmente, con l’aumentarne la sua complessità, incrementando il numero di dipendenze all’interno del codice. Tale complessità si ripercuote sulla facilità di sviluppo, dato che il programmatore dovrà conoscere l’intero sistema per intervenire sul codice. Nel caso non lo conoscesse, rischierebbe di realizzare un codice di bassa qualità, aumentando ulteriormente la difficoltà di intervento per eventuali modifiche successive.

Inoltre il deployment risulta essere lento e difficoltoso. Infatti, ogni volta che vengono aggiunte determinate funzionalità o modifiche in alcune funzioni dell’applicativo, è necessario effettuare nuovamente il deployment dell’intero sistema, poiché questo non è divisibile.

Lo scaling dell’applicazione risulta, inoltre, difficoltoso ed inefficiente. Infatti, nel caso una singola istanza dell’applicazione non fosse sufficiente a soddisfare le richieste, sarebbe necessario aggiungerne altre. Ad esempio, si potrebbe effettuare il deployment di più istanze su più nodi diversi ed utilizzare un load balancer per bilanciare il carico fra di loro. Sebbene questa modalità possa funzionare, esistono alcuni problemi. Innanzitutto, le varie istanze devono essere programmate in modo tale da supportare la replicazione, per ogni funzione dell’applicazione. Inoltre, non è possibile effettuare lo scaling di singole funzionalità. Ad esempio, se solo una delle funzionalità dell’applicazione non avesse le performance necessarie per soddisfare le richieste, sarebbe comunque necessario effettuare lo scaling dell’intera applicazione, incrementando anche il numero delle altre funzionalità che offrivano già prestazioni accettabili.

Un ulteriore problema riguarda infine le tecnologie utilizzate. Se il monolite è stato utilizzato sfruttando determinate librerie, framework o linguaggi di programmazione, sarà molto difficile cambiare la tecnologia utilizzata, poiché ogni funzionalità potrebbe avere dipendenze con esse. Se invece, le varie funzionalità fossero state separate e rese indipendenti, sarebbe possibile sfruttare tecnologie differenti per ognuna di esse.

Caratteristiche, deployment e vantaggi dell'architettura a microservizi

Caratteristiche dell’architettura a microservizi

L’idea alla base dell’architettura a microservizi è quella di scomporre l’applicazione in più microservizi indipendenti, ognuno con una singola responsabilità ben definita. I vari microservizi coopereranno tra loro per realizzare la logica applicativa. Tale cooperazione avverrà sfruttando una rete di comunicazione, attraverso protocolli standard. La decomposizione viene effettuata sulla base delle funzionalità di business, andando a decomporre il dominio del problema in sottodomini quasi indipendenti.

L’architettura a microservizi risolve gran parte delle problematiche delle architetture monolitiche. Infatti, suddividendo l’applicazione in parti distinte, debolmente accoppiate tra loro, si semplificano le operazioni di sviluppo e di deployment. Gruppi di sviluppatori distinti possono infatti operare indipendentemente sul proprio microservizio, ottenendo un maggior controllo sulla complessità, e possono effettuare il deployment indipendentemente dagli altri microservizi. Anche le operazioni di scaling sono più efficienti: se una specifica funzionalità dell’applicazione ha necessità di prestazioni maggiori, potranno essere aggiunte unità relative esclusivamente a quella funzionalità. Inoltre, le tecnologie utilizzate per implementare ogni singolo microservizio potranno essere differenti, in base alle necessità dello specifico microservizio.

I principi alla base dell’architettura a microservizi sono due:

  1. Responsabilità singola Ogni singola unità, in questo caso microservizio, deve possedere una ed una sola responsabilità. Non deve verificarsi il caso in cui più unità condividono le stesse responsabilità o un’unità possiede più di una responsabilità.
  2. Autonomia Ogni microservizio deve essere fornito in un pacchetto software contenente tutte le dipendenze, tra cui le librerie, e l’ambiente di esecuzione, che potrebbe consistere, ad esempio, nel web server utilizzato o nel sistema operativo necessario. Questo permette al microservizio di avere un deployment indipendente, sia dagli altri microservizi che dall’ambiente di esecuzione, e di avere l’autonomia per implementare ed eseguire una particolare funzionalità di business.

Ogni microservizio dovrà inoltre possedere le seguenti caratteristiche:

  1. Service contract Ogni microservizio deve specificare il proprio contratto agli utilizzatori, che definisce quale servizio offre e quali sono le modalità per usufruirne.
  2. Loose coupling I microservizi devono essere debolmente accoppiati tra loro; le interazioni devono avvenire esclusivamente attraverso le API che il microservizio offre, nascondendo i dettagli implementativi. L’implementazione di ogni microservizio potrà quindi essere modificata in maniera trasparente agli utilizzatori del servizio offerto.
  3. Service abstraction Il microservizio è l’entità di astrazione di base, che incapsula, oltre alla logica applicativa, anche le librerie e l’ambiente di esecuzione.
  4. Service reuse Ogni microservizio deve essere realizzato nell’ottica della riutilizzabilità. Un microservizio dovrebbe quindi offrire, quando possibile, un servizio generico, riutilizzabile da altri microservizi.
  5. Statelessness Ogni microservizio non dovrebbe mantenere uno stato interno, né uno stato relativo alla comunicazione con gli utilizzatori del servizio. Nel caso in cui sia necessario mantenere uno stato, questo deve essere mantenuto da un servizio di database esterno. L’assenza di stato facilita lo scaling e la tolleranza ai guasti, poiché permette di eliminare o aggiungere una qualsiasi istanza del microservizio senza il rischio di perdere dati o di interferire con le comunicazioni esistenti.
  6. Discoverability Ogni microservizio dovrebbe avere la possibilità di annunciare la propria presenza a chi potrebbe avere la necessità di usufruire del servizio offerto.
  7. Interoperability Ogni microservizio dovrebbe essere interoperabile con gli altri, attraverso l’utilizzo di protocolli e messaggi standard. Fra questi, HTTP, REST, JSON o gRPC.
  8. Composeability La possibilità di realizzare microservizi componendo i servizi offerti da altri microservizi.

Qualora si riuscisse a soddisfare i principi e le caratteristiche appena descritte, sarebbe possibile realizzare un’applicazione organizzata in modo tale da poter essere facilmente eseguita su un sistema distribuito. Infatti, ogni microservizio potrebbe essere eseguito su un nodo fisico differente e replicato in base alle performance necessarie. Per distribuire il carico tra un gruppo di microservizi replicati su potrebbe adottare un load balancer. Inoltre, poiché i vari microservizi sono stateless, risulta semplice scalare le performance o fare fronte ai guasti. Infatti, se un nodo ospitante alcune istanze si disconnettesse, non verrebbero persi dati, e le istanze non più in esecuzione potrebbero essere facilmente sostituite. L’unica eccezione riguarda servizi di database, che, per la loro natura, non possono essere stateless. Tali servizi però adottano particolari tecniche di replicazione per garantire la costante disponibilità dei dati, anche a fronte di guasti. Per questo, i microservizi che necessitano di mantenere uno stato, dovrebbero delegare la sua gestione ad un servizio di database esterno.

I microservizi offrono molti vantaggi, ma presentano anche alcuni aspetti negativi. Una difficoltà evidente consiste nel determinare in quale modo suddividere l’applicazione, cioè quali microservizi individuare. Questa scelta non è banale, e non esiste un metodo ben definito e sempre efficace per attuarla.

Inoltre, i sistemi distribuiti sono molto più complessi dei sistemi centralizzati. Gli sviluppatori devono infatti gestire le interazioni tra i vari microservizi, considerando che un determinato microservizio potrebbe non essere sempre disponibile. Ogni microservizio inoltre ha il proprio servizio di database e, ciò, potrebbe causare temporanee inconsistenze dei dati. Inoltre, il deployment e la gestione di un numero molto elevato di microservizi richiede, inevitabilmente, un ambiente di orchestrazione, che deve essere installato e configurato.

Per questo, l’adozione di un’architettura a microservizi non è sempre la scelta migliore; è consigliabile adottarla solamente in quei casi in cui l’applicazione è caratterizzata da un’elevata complessità ed ha necessità di elevate prestazioni e garanzie di tolleranza ai guasti.

Deployment dei microservizi

Effettuare il deployment di un’applicazione monolitica è relativamente semplice, poiché è necessario effettuare il deployment di una singola applicazione che sfrutta un singolo framework ed un singolo linguaggio di programmazione. Nel caso dei microservizi potrebbe essere invece necessario effettuare il deployment di decine o centinaia di applicazioni distinte, ognuna potenzialmente scritta tramite framework e linguaggi di programmazione differenti. Sarebbe improponibile effettuare manualmente il deployment di un numero così elevato di applicazioni, senza considerare i possibili conflitti, fra le librerie ed i framework utilizzati, che potrebbero manifestarsi se più microservizi distinti dovessero essere eseguiti sullo stesso nodo. Per risolvere questi problemi, ogni microservizio dovrebbe incapsulare, oltre alla logica applicativa, anche tutte le dipendenze, tra cui le librerie, e l’ambiente di esecuzione. Inoltre, per evitare, su uno stesso nodo, conflitti tra le diverse tecnologie utilizzate dai vari microservizi, sarebbe necessario un qualche sistema di isolamento.

Una possibile soluzione sarebbe quella di utilizzare una macchina virtuale per ogni microservizio, ma, in tal caso, si manifesterebbe un elevato spreco di risorse, causato dall’overhead introdotto dai sistemi operativi di ogni macchina.

La tecnologia più adatta e diffusa per il deployment di microservizi risulta essere quella dei container. I container, rispetto alle macchine virtuali, introducono molto meno overhead, ma garantiscono comunque un elevato grado di isolamento. Per effettuare il deployment di un container su un nodo, è necessaria la presenza di un container runtime engine, come Docker, containerd o LXC. Per effettuare il deployment dei vari microservizi si potrebbe quindi eseguire il deployment dei vari container sui vari nodi provvisti di un container runtime engine. L’utilizzo dei container permetterebbe, sicuramente, di risolvere il problema dell’installazione e configurazione dell’ambiente di esecuzione, ma, la gestione manuale di ogni container su ogni singolo nodo risulterebbe comunque onerosa. Per questo sono nati i cosiddetti orchestratori di container, come Kubernetes. In generale, questi orchestratori permettono di gestire automaticamente i vari container sui vari nodi del cluster ed offrono servizi aggiuntivi, facilitando notevolmente il deployment di applicazioni composte da microservizi. La possibilità di richiedere un cluster preconfigurato con un orchestratore di container attraverso i servizi cloud, ha reso il deployment delle applicazioni a microservizi ulteriormente più semplice ed accessibile, incrementandone la diffusione.

Pubblicato da Vito Lavecchia

Lavecchia Vito Ingegnere Informatico (Politecnico di Bari) Email: [email protected] Sito Web: https://vitolavecchia.altervista.org

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *