Le vacanze sono finite e ritorniamo con il nostro secondo post della serie su Istio Service Mesh.

Il tema di oggi è il Circuit Breaker, che in italiano significa "interruttore automatico" e in gergo si riferisce a un "dispositivo di protezione". Solo che in Istio questo dispositivo non scollega un circuito in cortocircuito o sovraccarico, ma disabilita i contenitori difettosi.
Come dovrebbe funzionare in teoria
Quando i microservizi sono gestiti da Kubernetes, ad esempio all'interno della piattaforma OpenShift, si scalano automaticamente su e giù a seconda del carico. Poiché i microservizi operano all'interno di pod, su un singolo endpoint possono esserci più istanze di un microservizio containerizzato, e Kubernetes instraderà le richieste e bilancerà il carico tra di esse. E – in teoria – tutto questo dovrebbe funzionare perfettamente.
Ricordiamoci che i microservizi sono piccoli ed efimeri. L'efimerità, qui, che significa la facilità di nascita e scomparsa, è spesso sottovalutata. La nascita e la morte di un'istanza di microservizio dentro un pod sono eventi piuttosto previsti, OpenShift e Kubernetes li gestiscono bene, e tutto funziona splendidamente – ma, di nuovo, in teoria.
Come funziona realmente
Ora immaginate che un'istanza specifica di un microservizio, ovvero un contenitore, sia diventata inservibile: o non risponde (errore 503), oppure – il che è peggio – risponde, ma troppo lentamente. In altre parole, sta dando problemi o non risponde alle richieste, ma non viene automaticamente rimosso dal pool. Cosa fare in questo caso? Riprovare? Rimuoverlo dallo schema di routing? E cosa significa "troppo lentamente" – quanto è questo in numeri, e chi li definisce? Forse, semplicemente dargli una pausa e riprovare più tardi? Se sì, quanto tardi?
Che cos'è il Pool Ejection in Istio
Ed ecco che Istio interviene con i suoi interruttori di protezione Circuit Breaker, che rimuovono temporaneamente i contenitori difettosi dal pool delle risorse di routing e bilanciamento del carico, implementando la procedura di Pool Ejection.
Utilizzando la strategia di rilevamento delle anomalie (outlier detection), Istio rileva i pod problematici che si distaccano dalla norma e li rimuove dal pool delle risorse per un periodo di tempo definito, chiamato "finestra di sonno" (sleep window).
Per mostrare come funziona in Kubernetes sulla piattaforma OpenShift, iniziamo con uno screenshot di microservizi funzionanti dall'esempio nel repository. Qui abbiamo due pod, v1 e v2, ognuno con un contenitore in esecuzione. Quando non si usano le regole di instradamento di Istio, Kubernetes applica per impostazione predefinita un'instradamento ciclico bilanciato in modo uniforme:

Prepararsi a un guasto
Prima di effettuare il Pool Ejection, è necessario creare una regola di instradamento di Istio. Supponiamo di voler distribuire le richieste tra i pod in un rapporto 50/50. Inoltre, aumenteremo il numero di contenitori v2 da uno a due, in questo modo:
oc scale deployment recommendation-v2 --replicas=2 -n tutorial
Ora impostiamo la regola di instradamento affinché il traffico venga distribuito tra i pod in un rapporto 50/50.

Ecco come appare il risultato di questa regola:

Si potrebbe obiettare che in questo screenshot non sia 50/50, ma 14:9, ma con il tempo la situazione si sistemerà.
Generiamo un guasto
Ora metteremo fuori uso uno dei due contenitori v2, in modo da avere un contenitore v1 funzionante, un contenitore v2 funzionante e un contenitore v2 non funzionante:

Ripariamo il guasto
Quindi, abbiamo un contenitore non funzionante, e ora è tempo di Pool Ejection. Con una configurazione molto semplice, escluderemo questo contenitore guasto da qualsiasi schema di instradamento per 15 secondi, con l'aspettativa che si ripristini da solo (o si riavvii, o recuperi le prestazioni). Ecco come appare questa configurazione e i risultati della sua esecuzione:


Come si vede, il contenitore non funzionante v2 non viene più utilizzato nell'instradamento delle richieste, poiché è stato rimosso dal pool. Dopo 15 secondi, tornerà automaticamente nel pool. In effetti, abbiamo appena mostrato come funziona il Pool Ejection.
Iniziamo a costruire l'architettura
Il Pool Ejection insieme alle capacità di monitoraggio di Istio consente di iniziare a costruire un framework per la sostituzione automatica dei contenitori guasti, per ridurre o addirittura eliminare i tempi di inattività e i guasti.
La NASA ha un famoso motto: Failure Is Not an Option, attribuito al flight director . In russo si può tradurre come «La sconfitta non è un'opzione», e il significato qui è che tutto può funzionare se si ha sufficiente volontà. Tuttavia, nella vita reale i fallimenti non accadono solo occasionalmente, ma sono inevitabili, ovunque e in tutto. E come affrontarli in caso di microservizi? A nostro avviso, è meglio non fare affidamento sulla forza di volontà, ma piuttosto sulle capacità dei container, , , e .
Istio, come abbiamo già scritto sopra, implementa una concetto di interruttori automatici che ha dimostrato di funzionare bene nel mondo fisico. E proprio come un interruttore elettrico disconnette la parte problematica di un circuito, così il Circuit Breaker software in Istio interrompe la connessione tra il flusso di richieste e il container problematico, quando c'è qualcosa che non va con il punto finale, ad esempio, quando il server è andato giù o ha cominciato a rallentare.
Nel secondo caso ci sono solo più problemi, poiché i rallentamenti di un container non solo causano un effetto a cascata di ritardi nei servizi che vi si connettono e, di conseguenza, riducono le prestazioni complessive del sistema, ma generano anche richieste ripetute a un servizio già lento, aggravando ulteriormente la situazione.
Circuit Breaker in teoria
Circuit Breaker è un proxy che controlla il flusso di richieste verso il punto finale. Quando questo punto smette di funzionare o – a seconda delle impostazioni fornite – inizia a rallentare, il proxy interrompe la connessione con il container. Il traffico dopo viene reindirizzato verso altri container, proprio per motivi di bilanciamento del carico. La connessione rimane aperta (open) per un determinato intervallo di sonno, diciamo due minuti, e poi viene considerata semi-aperta (half-open). Il tentativo di inviare la successiva richiesta determina lo stato successivo della connessione. Se il servizio è OK, la connessione torna in stato operativo e diventa nuovamente chiusa (closed). Se il servizio continua a non funzionare, la connessione si interrompe e viene riattivato l'intervallo di sonno. Ecco come si presenta un diagramma semplificato dello stato del Circuit Breaker:

È importante sottolineare che tutto questo avviene a livello di architettura sistemica. Pertanto, a un certo punto dovrete insegnare alle vostre applicazioni a lavorare con il Circuit Breaker, ad esempio fornendo un valore predefinito in risposta o, se possibile, ignorando l'esistenza del servizio. A tal fine si utilizza il pattern bulkhead, ma questo esula dall'argomento di questo articolo.
Circuit Breaker in pratica
Per esempio, avvieremo su OpenShift due versioni del nostro microservizio di raccomandazione. La versione 1 funzionerà normalmente, mentre nella v2 inseriremo un ritardo per simulare un rallentamento del server. Per visualizzare i risultati si utilizza lo strumento :
siege -r 2 -c 20 -v customer-tutorial.$(minishift ip).nip.io

Tutto sembra funzionare, ma a quale costo? A prima vista abbiamo il 100% di disponibilità, ma osservate bene: la durata massima della transazione è di ben 12 secondi. Questo è chiaramente un collo di bottiglia e deve essere risolto.
Per questo motivo, tramite Istio, escluderemo le chiamate ai contenitori lenti. Ecco come appare la configurazione corrispondente utilizzando il Circuit Breaker:

L'ultima riga con il parametro httpMaxRequestsPerConnection indica che la connessione deve essere interrotta quando si tenta di creare un'altra connessione – una seconda – oltre a quella già esistente. Poiché il nostro contenitore simula un servizio che rallenta, situazioni di questo tipo si verificheranno periodicamente, e in tal caso Istio restituirà un errore 503, ecco cosa mostrerà siege:

Ok, ora abbiamo il Circuit Breaker, e adesso?
Quindi, abbiamo implementato l'autodisconnessione senza toccare il codice sorgente dei servizi stessi. Utilizzando il Circuit Breaker e la procedura di Ejection del Pool descritta sopra, possiamo rimuovere dai pool delle risorse i contenitori problematici fino a quando non tornano alla normalità e controllarne lo stato a intervalli regolari – nel nostro esempio, ogni due minuti (parametro sleepWindow).
Si noti che la capacità dell'applicazione di reagire all'errore 503 è ancora definita a livello del suo codice sorgente. Esistono diverse strategie per lavorare con il Circuit Breaker, che vengono applicate a seconda della situazione.
Nel prossimo post: parleremo di tracciamento e monitoraggio, che sono già integrati o facilmente aggiungibili in Istio, oltre a come introdurre errori nel sistema deliberatamente.
Fonte: habr.com
