
Nota di traduzione.: Questo ciclo è stato dedicato alla conoscenza delle funzionalità di Istio e alla loro dimostrazione in azione. Ora si parlerà di aspetti più complessi della configurazione e dell'uso di questo service mesh, in particolare della routing finemente configurabile e della gestione del traffico di rete.
Ricordiamo inoltre che nell'articolo si utilizzano configurazioni (manifesti per Kubernetes e Istio) dal repository .
Gestione del traffico
Con Istio si aprono nuove possibilità nei cluster, consentendo di garantire:
- Routing dinamico delle richieste: rilascio canarini, test A/B;
- Bilanciamento del carico: semplice e senza contraddizioni, basato su hash;
- Ripristino dopo i malfunzionamenti: timeout, retry, circuit breakers;
- Introduzione di malfunzionamenti: ritardi, interruzione delle richieste, ecc.
Nel prosieguo dell'articolo, queste possibilità saranno mostrate con un esempio di un'applicazione selezionata e introdurranno nuovi concetti. Il primo di questi concetti sarà DestinationRules (ossia regole sul destinatario del traffico/ richieste — nota dell'autore), attraverso le quali attiviamo il test A/B.
Test A/B: DestinationRules in pratica
Il test A/B si applica nei casi in cui esistono due versioni di un'applicazione (di solito si differenziano visivamente) e non siamo sicuri al 100% di quale delle due migliori l'interazione con l'utente. Pertanto, eseguiamo contemporaneamente entrambe le versioni e raccogliamo metriche.
Per il deployment della seconda versione del frontend, necessaria per dimostrare il test A/B, esegui il seguente comando:
$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions/sa-frontend-green creatoIl manifesto del deployment per la "versione verde" differisce in due punti:
- L'immagine è basata su un'altra etichetta —
istio-green, - I pod hanno l'etichetta
version: green.
Poiché entrambi i deployment hanno l'etichetta app: sa-frontend, le richieste instradate dal servizio virtuale sa-external-services verso il servizio sa-frontend, verranno deviate su tutte le sue istanze e il carico sarà distribuito tramite , il che porterà alla seguente situazione:

File richiesti non trovati
Questi file non sono stati trovati perché sono chiamati diversamente nelle varie versioni dell'applicazione. Assicuriamoci di questo:
$ curl --silent http://$EXTERNAL_IP/ | tr '"' 'n' | grep main
/static/css/main.c7071b22.css
/static/js/main.059f8e9c.js
$ curl --silent http://$EXTERNAL_IP/ | tr '"' 'n' | grep main
/static/css/main.f87cd8c9.css
/static/js/main.f7659dbb.js Ciò significa che index.html, richiedendo una versione di file statici, può essere inviato da un bilanciatore di carico a pod che hanno una versione diversa, dove, per motivi comprensibili, tali file non esistono. Pertanto, affinché l'applicazione funzioni, dobbiamo impostare una limitazione: «la stessa versione dell'applicazione che ha restituito index.html deve gestire anche le richieste successive».
Raggiungeremo l'obiettivo con un bilanciamento del carico coerente basato su hash (Consistent Hash Loadbalancing). In questo caso le richieste da un singolo cliente vengono inviate allo stesso esemplare del backend, per il quale viene utilizzata una proprietà predeterminata — ad esempio, l'intestazione HTTP. Questo è implementato tramite DestinationRules.
DestinationRules
Una volta che VirtualService ha indirizzato la richiesta al servizio giusto, tramite DestinationRules possiamo definire le politiche che verranno applicate al traffico destinato agli esemplari di quel servizio:

Gestione del traffico con le risorse Istio
Nota: L'impatto delle risorse Istio sul traffico di rete è presentato qui in una forma semplificata per una migliore comprensione. Se vogliamo essere precisi, la decisione su quale esemplare inviare la richiesta viene presa da Envoy all'Ingress Gateway, configurato in CRD.
Con Destination Rules possiamo configurare il bilanciamento del carico in modo che vengano utilizzati hash coerenti e garantiti risposte dallo stesso esemplare del servizio allo stesso utente. La seguente configurazione consente di raggiungere questo obiettivo ():
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: sa-frontend
spec:
host: sa-frontend
trafficPolicy:
loadBalancer:
consistentHash:
httpHeaderName: version # 1 1 — l'hash verrà generato in base al contenuto dell'intestazione HTTP versione.
Applica la configurazione utilizzando il seguente comando:
$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io/sa-frontend creato Ora esegui il comando qui sotto e assicurati di ricevere i file corretti quando specifichi l'intestazione versione:
$ curl --silent -H "version: yogo" http://$EXTERNAL_IP/ | tr '"' 'n' | grep mainNota: Per aggiungere diversi valori all'intestazione e testare i risultati direttamente nel browser, puoi utilizzare per Chrome (o per Firefox — nota del traduttore).
In generale, le DestinationRules offrono ulteriori possibilità nel bilanciamento del carico — per dettagli chiedere. .
Prima di approfondire VirtualService, rimuoviamo la "versione verde" dell'applicazione e la regola di instradamento del traffico corrispondente eseguendo i seguenti comandi:
$ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions "sa-frontend-green" eliminato
$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io "sa-frontend" eliminatoMirror: Virtual Services in pratica
Shadowing ("esecuzione in parallelo") o Mirroring ("mirror") è utilizzato quando vogliamo testare una modifica in produzione senza coinvolgere gli utenti finali: per fare questo, dupliciamo ("mirroring") le richieste su una seconda istanza, dove sono state apportate le modifiche necessarie, e osserviamo le conseguenze. In parole semplici, è quando il tuo collega sceglie il problema più critico e fa una pull request in forma di un enorme groviglio che nessuno può in realtà revisionare.
Per testare questo scenario in azione, creiamo una seconda istanza di SA-Logic con bug (buggy), eseguendo il seguente comando:
$ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service-buggy.yaml
deployment.extensions/sa-logic-buggy creato E ora eseguiamo il comando per assicurarci che tutte le istanze con app=sa-logic abbiano anche etichette con versioni corrispondenti:
$ kubectl get pods -l app=sa-logic --show-labels
NOME PRONTO ETICHETTE
sa-logic-568498cb4d-2sjwj 2/2 app=sa-logic,version=v1
sa-logic-568498cb4d-p4f8c 2/2 app=sa-logic,version=v1
sa-logic-buggy-76dff55847-2fl66 2/2 app=sa-logic,version=v2
sa-logic-buggy-76dff55847-kx8zz 2/2 app=sa-logic,version=v2 Servizio sa-logic mirato ai pod con etichetta app=sa-logic, quindi tutte le richieste saranno distribuite tra tutte le istanze:

… ma vogliamo che le richieste siano dirette verso le istanze con versione v1 e mirrorate verso le istanze con versione v2:

Otterremo questo tramite VirtualService in combinazione con DestinationRule, dove le regole definiranno i sottoinsiemi e i percorsi del VirtualService verso un sottoinsieme specifico.
Definizione dei sottoinsiemi nelle Destination Rules
Sottoinsiemi (subsets) sono definiti dalla seguente configurazione ():
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: sa-logic
spec:
host: sa-logic # 1
subsets:
- name: v1 # 2
labels:
version: v1 # 3
- name: v2
labels:
version: v2- L'host (
host) definisce che questa regola si applica solo ai casi in cui il percorso è diretto verso il serviziosa-logic; - I nomi (
name) dei sottoinsiemi sono utilizzati durante l'instradamento verso le istanze del sottoinsieme; - Etichetta (
label) determina le coppie chiave-valore a cui devono conformarsi le istanze per fare parte di un sottoinsieme.
Applica la configurazione utilizzando il seguente comando:
$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic creatoOra che i sottoinsiemi sono stati definiti, possiamo andare avanti e configurare il VirtualService per applicare le regole alle richieste a sa-logic in modo che:
- Vengano instradate al sottoinsieme
v1, - Vengano mirroring al sottoinsieme
v2.
Il seguente manifesto permette di ottenere quanto previsto ():
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: sa-logic
spec:
hosts:
- sa-logic
http:
- route:
- destination:
host: sa-logic
subset: v1
mirror:
host: sa-logic
subset: v2Non sono necessarie spiegazioni qui, quindi vediamo semplicemente all'opera:
$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic creatoAggiungiamo carico chiamando questo comando:
$ while true; do curl -v http://$EXTERNAL_IP/sentiment
-H "Content-type: application/json"
-d '{"sentence": "Amo yogobella"}';
sleep .8; done Esaminiamo i risultati in Grafana, dove possiamo vedere che la versione con bug (buggy) causa il fallimento di circa il 60% delle richieste, ma nessuno di questi fallimenti colpisce gli utenti finali, poiché ricevono risposte dal servizio funzionante.

Successo delle risposte delle diverse versioni del servizio sa-logic
Qui abbiamo visto per la prima volta come viene applicato il VirtualService ai sidecar dei nostri servizi: quando sa-web-app fa una richiesta a sa-logic, passa attraverso il sidecar Envoy, che — tramite il VirtualService — è configurato per instradare la richiesta al sottoinsieme v1 e mirroring della richiesta al sottoinsieme v2 del servizio sa-logic.
So: avete già pensato che i Virtual Services siano semplici. Nella prossima sezione espanderemo questa opinione dicendo che sono anche davvero magnifici.
Distribuzioni canary
Il Canary Deployment è il processo di distribuzione di una nuova versione di un'applicazione a un numero ristretto di utenti. Viene utilizzato per assicurarsi che non ci siano problemi nel rilascio e solo dopo, avendo la certezza della qualità sufficiente di quest'ultimo, viene diffuso a ununpubblico più ampio.
Per dimostrare i rilasci canary, continueremo a lavorare con il sottoinsieme buggy a sa-logic.
Non facciamo le cose per metà e inoltriamo subito il 20% degli utenti alla versione con bug (che rappresenterà il nostro rilascio canary), mentre l'80% restante alla normale servizio. Per fare ciò applichiamo il seguente VirtualService ():
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: sa-logic
spec:
hosts:
- sa-logic
http:
- route:
- destination:
host: sa-logic
subset: v1
weight: 80 # 1
- destination:
host: sa-logic
subset: v2
weight: 20 # 1 1 — è il peso (weight), che determina la percentuale di richieste che saranno inviate al destinatario o a un sottoinsieme del destinatario.
Aggiorniamo la configurazione precedente del VirtualService con sa-logic il seguente comando:
$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configurato… e subito vedremo che parte delle richieste porta a errori:
$ while true; do
curl -i http://$EXTERNAL_IP/sentiment
-H "Content-type: application/json"
-d '{"sentence": "I love yogobella"}'
--silent -w "Time: %{time_total}s t Status: %{http_code}n"
-o /dev/null; sleep .1; done
Time: 0.153075s Status: 200
Time: 0.137581s Status: 200
Time: 0.139345s Status: 200
Time: 30.291806s Status: 500I VirtualServices attivano i deploy canary: in questo caso abbiamo ridotto le potenziali conseguenze dei problemi al 20% della base utenti. Ottimo! Ora, ogni volta che non siamo certi del nostro codice (in altre parole, sempre…), possiamo utilizzare il mirroring e i deploy canary.
Timeout e tentativi
Ma non sempre i bug si trovano nel codice. Nella lista di “”, il primo errore comune è l'opinione errata che “la rete sia affidabile”. In realtà, la rete non è affidabile, ed è per questo che abbiamo bisogno di timeout (timeouts) e tentativi (retries).
Per la dimostrazione, continueremo a utilizzare il problema attuale sa-logic (buggy), mentre simuleremo l'inaffidabilità della rete con errori casuali.
Supponiamo che il nostro servizio con bug abbia 1/3 di probabilità di rispondere troppo tardi, 1/3 di chiudere con un errore Internal Server Error e 1/3 di restituire la pagina con successo.
Per attenuare le conseguenze di tali problemi e migliorare l'esperienza degli utenti, possiamo:
- aggiungere un timeout, se il servizio risponde oltre 8 secondi,
- riprovare, se la richiesta fallisce.
Per implementarlo, utilizzeremo la seguente definizione della risorsa ():
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: sa-logic
spec:
hosts:
- sa-logic
http:
- route:
- destination:
host: sa-logic
subset: v1
weight: 50
- destination:
host: sa-logic
subset: v2
weight: 50
timeout: 8s # 1
retries:
attempts: 3 # 2
perTryTimeout: 3s # 3- Il timeout per la richiesta è impostato a 8 secondi;
- I tentativi di richieste vengono effettuati 3 volte;
- Ogni tentativo è considerato non riuscito se il tempo di risposta supera i 3 secondi.
In questo modo abbiamo ottimizzato, poiché l'utente non dovrà attendere più di 8 secondi e faremo tre nuovi tentativi di ottenere una risposta in caso di errori, aumentando le possibilità di una risposta positiva.
Applica la configurazione aggiornata con il seguente comando:
$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts-vs.yaml
virtualservice.networking.istio.io/sa-logic configuratoE controlla nei grafici Grafana che il numero delle risposte riuscite sia aumentato:

Miglioramenti nelle statistiche delle risposte riuscite dopo aver aggiunto timeout e ripetizioni
Prima di passare alla sezione successiva (o meglio — già alla parte successiva dell'articolo, poiché in questa non ci saranno più esperimenti pratici — ndr), rimuovi sa-logic-buggy e VirtualService eseguendo i seguenti comandi:
$ kubectl delete deployment sa-logic-buggy
deployment.extensions “sa-logic-buggy” eliminato
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io “sa-logic” eliminatoPattern Circuit Breaker e Bulkhead
Si tratta di due importanti pattern nell'architettura dei microservizi che permettono il recupero autonomo (self-healing) dei servizi.
Circuit Breaker («interruttore automatico») viene utilizzato per interrompere le richieste a un'istanza di servizio considerata non sana, ripristinandola mentre le richieste dei clienti vengono reindirizzate a istanze sane di questo servizio (ciò aumenta il tasso di risposte riuscite). (Nota del traduttore: Una descrizione più dettagliata del pattern può essere trovata, per esempio, .)
Bulkhead («paratia») isola i guasti nei servizi dall'impatto sull'intero sistema. Ad esempio, se il servizio B è guasto e un altro servizio (cliente del servizio B) invia una richiesta al servizio B, esso consumerà il suo pool di thread e non potrà gestire altre richieste (anche se non riguardano il servizio B). (Nota del traduttore: Una descrizione più dettagliata del pattern può essere trovata, per esempio, .)
Ometterò i dettagli sull'implementazione di questi pattern, perché è facile trovarli in , e ho già voglia di mostrare l'autenticazione e l'autorizzazione, di cui si parlerà nella parte successiva dell'articolo.
P.S. dal traduttore
Leggi anche nel nostro blog:
- «Tornare ai microservizi con Istio»: , ;
- «»;
- «».
Fonte: habr.com
