
Nota del traduttore.: Questo ciclo è stato dedicato alla presentazione delle funzionalità di Istio e alla loro dimostrazione in azione. Ora parleremo di aspetti più complessi della configurazione e dell'uso di questo service mesh, in particolare della configurazione dettagliata del routing e della gestione del traffico di rete.
Ricordiamo anche che nell'articolo vengono utilizzate configurazioni (manifesti per Kubernetes e Istio) dal repository .
Gestione del traffico
Con Istio nel cluster si aprono nuove possibilità per garantire:
- Routing dinamico delle richieste: distribuzioni canary, A/B testing;
- Bilanciamento del carico: semplice e coerente, basato su hash;
- Recupero da guasti: timeout, retry, circuit breakers;
- Introduzione di guasti: ritardi, interruzioni delle richieste, e così via.
Nel seguito dell'articolo, queste capacità saranno illustrate utilizzando un'applicazione scelta e nel contempo verranno presentati nuovi concetti. Il primo di questi concetti sarà DestinationRules (ossia le regole relative al destinatario del traffico/richieste — nota del traduttore), mediante le quali attiveremo l'A/B testing.
A/B testing: DestinationRules in pratica
L'A/B testing è utilizzato nei casi in cui ci siano due versioni di un'applicazione (di solito si differenziano visivamente) e non siamo certi al 100% di quale migliorerà l'interazione con l'utente. Pertanto, lanciamo entrambe le versioni contemporaneamente e raccogliamo metriche.
Per distribuire la seconda versione del frontend, necessaria per dimostrare l'A/B testing, esegui il seguente comando:
$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
department.extensions/sa-frontend-green creatoIl manifesto del deployment per la "versione verde" differisce in due punti:
- L'immagine è basata su un diverso tag —
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 al servizio sa-frontend, saranno reindirizzate a 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é hanno nomi diversi nelle diverse 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, potrebbe essere inviato dal bilanciatore di carico a pod con un'altra versione, dove, per motivi ovvi, tali file non esistono. Pertanto, per far funzionare l'applicazione, è necessario impostare un limite: "la stessa versione dell'applicazione che ha restituito index.html deve servire anche le richieste successive».
Ciò sarà raggiunto mediante un bilanciamento del carico coerente basato su hash (Consistent Hash Loadbalancing). In questo caso le richieste da un singolo cliente vengono inviate alla stessa istanza del backend, per il quale viene utilizzata una proprietà predeterminata — ad esempio, l'intestazione HTTP. Ciò è implementato tramite DestinationRules.
DestinationRules
Una volta che VirtualService ha inviato la richiesta al servizio giusto, tramite le DestinationRules possiamo definire le politiche che saranno applicate al traffico indirizzato alle istanze di quel servizio:

Gestione del traffico con le risorse di Istio
Nota: L'influenza delle risorse di Istio sul traffico di rete è presentata qui in una forma semplificata per la comprensione. Per essere precisi, la decisione su quale istanza inviare la richiesta è presa da Envoy nell'Ingress Gateway, configurato nel CRD.
Con le Destination Rules possiamo configurare il bilanciamento del carico in modo che vengano utilizzati hash coerenti e garantire risposte dalla stessa istanza del servizio allo stesso utente. La seguente configurazione consente di ottenere ciò ():
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: sa-frontend
spec:
host: sa-frontend
trafficPolicy:
loadBalancer:
consistentHash:
httpHeaderName: version # 1 1 — l'hash sarà generato in base al contenuto dell'intestazione HTTP version.
Applica la configurazione con il seguente comando:
$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io/sa-frontend creato E ora esegui il comando sottostante e assicurati di ricevere i file corretti quando specifichi l'intestazione version:
$ curl --silent -H "version: yogo" http://$EXTERNAL_IP/ | tr '"' 'n' | grep mainNota: Per aggiungere valori diversi nell'intestazione e testare i risultati direttamente nel browser, puoi utilizzare per Chrome (o per Firefox — nota del traduttore.).
In generale, le DestinationRules hanno maggiori possibilità nel bilanciamento del carico — per dettagli chiedere. .
Prima di continuare con l'esplorazione di VirtualService, eliminiamo la "versione verde" dell'applicazione e la corrispondente regola per l'instradamento del traffico eseguendo i seguenti comandi:
$ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions “sa-frontend-green” deleted
$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io “sa-frontend” deletedMirroraggio: Virtual Services nella pratica
Shadowing («screening») o Mirroring («mirroraggio») si applica quando vogliamo testare una modifica in produzione senza impattare gli utenti finali: duplicando («mirrorando») le richieste su una seconda istanza dove sono state effettuate le necessarie modifiche e osservando le conseguenze. In altre parole, è quando il tuo collega sceglie il problema più critico e fa una pull request sotto forma di un enorme mucchio di sporcizia, che nessuno può realmente revisionare.
Per testare questo scenario, creeremo 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 created Ora eseguiremo il comando per assicurarci che tutte le istanze con app=sa-logic abbiano anche etichette con le versioni corrispondenti:
$ kubectl get pods -l app=sa-logic --show-labels
NAME READY LABELS
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 vengano dirette alle istanze con versione v1 e mirrorate alle istanze con versione v2:

Otteniamo questo attraverso 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 quando il percorso va verso il serviziosa-logic; - I nomi (
name) dei sottoinsiemi vengono utilizzati nel routing verso le istanze del sottoinsieme; - L'etichetta (
etichetta) definisce le coppie chiave-valore che devono corrispondere alle istanze per far parte del sottoinsieme.
Applica la configurazione con il seguente comando:
$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic createdOra che i sottoinsiemi sono definiti, possiamo procedere e configurare il VirtualService per applicare le regole alle richieste a sa-logic, in modo che:
- Vengano instradate verso il sottoinsieme
v1, - Siano mirrorate verso il sottoinsieme
v2.
Il seguente manifesto consente di ottenere quanto desiderato ():
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 solo in azione:
$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic createdAggiungiamo 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 Guardiamo i risultati in Grafana, dove possiamo vedere che la versione con bug (buggy) causa errori per ~60 % delle richieste, ma nessuno di questi errori colpisce gli utenti finali, poiché rispondono dal servizio funzionante.

Successo delle risposte delle diverse versioni del servizio sa-logic
Qui vediamo per la prima volta come il VirtualService venga applicato agli Envoy 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 verso il sottoinsieme v1 e mirrorare la richiesta verso il sottoinsieme v2 del servizio sa-logic.
So che hai già pensato che i Virtual Services siano semplici. Nella prossima sezione espanderemo questa opinione mostrando che sono anche davvero straordinari.
Deployment canary
Il Canary Deployment è il processo di rilascio di una nuova versione dell'applicazione per un piccolo numero di utenti. Viene utilizzato per garantire che non ci siano problemi nel rilascio e solo dopo, essendo certi della qualità sufficiente, viene distribuito a ununpubblico più ampio.
Per dimostrare i rilasci canary, continueremo a lavorare con il sottoinsieme buggy a sa-logic.
Non faremo piccole cose e manderemo subito il 20 % degli utenti alla versione con bug (che rappresenterà il nostro rilascio canary), mentre il restante 80 % andrà al servizio normale. Per questo applicheremo 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 definisce la percentuale di richieste che saranno indirizzate al destinatario o a un sottoinsieme di destinatari.
Aggiorniamo la configurazione precedente del VirtualService per sa-logic con il seguente comando:
$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configurato… e vediamo subito che alcune richieste portano a errori:
$ while true; do
curl -i http://$EXTERNAL_IP/sentiment
-H "Content-type: application/json"
-d '{"sentence": "Amo yogobella"}'
--silent -w "Tempo: %{time_total}s t Stato: %{http_code}
"
-o /dev/null; sleep .1; done
Tempo: 0.153075s Stato: 200
Tempo: 0.137581s Stato: 200
Tempo: 0.139345s Stato: 200
Tempo: 30.291806s Stato: 500I VirtualServices attivano i rollout canary: in questo caso abbiamo limitato le conseguenze di eventuali problemi al 20% della base utenti. Ottimo! Ora, ogni volta che non siamo sicuri del nostro codice (in altre parole, sempre...), possiamo utilizzare il mirroring e i rollout canary.
Timeout e retry
Ma non sempre i bug si trovano nel codice. Nella lista delle “” il primo posto è occupato dall'erronea convinzione che “la rete sia affidabile”. In realtà, la rete non è affidabile, ed è per questo che abbiamo bisogno di timeout (timeout) e retry (retry).
Per la dimostrazione, continueremo a utilizzare lo stesso problema versione 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 una risposta troppo lunga, 1/3 di chiudersi con un errore Internal Server Error e 1/3 di consegnare una pagina con successo.
Per mitigare le conseguenze di tali problemi e migliorare l'esperienza degli utenti, possiamo:
- aggiungere un timeout se il servizio risponde dopo 8 secondi,
- effettuare un retry se la richiesta si interrompe.
Per implementarlo, utilizzeremo questa definizione di 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 su 8 secondi;
- I retry per le richieste vengono effettuati per 3 volte;
- E ogni tentativo viene considerato fallito se il tempo di risposta supera i 3 secondi.
In questo modo abbiamo ottenuto un'ottimizzazione, poiché l'utente non dovrà aspettare più di 8 secondi e faremo tre nuovi tentativi per ottenere una risposta in caso di errori, aumentando la 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 di Grafana che il numero di risposte di successo sia aumentato:

Miglioramenti nelle statistiche delle risposte di successo dopo l'aggiunta di timeout e retry
Prima di passare alla sezione successiva (in realtà – già alla prossima parte dell'articolo, dato che in questa non ci saranno più esperimenti pratici – nota del traduttore), 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 pattern importanti nell'architettura a microservizi, che consentono di ottenere un'autoconfigurazione (self-healing) dei servizi.
Circuit Breaker ('interruttore automatico') viene utilizzato per interrompere le richieste che arrivano a un'istanza di servizio che è considerata non sana, ripristinandola mentre le richieste dei clienti vengono reindirizzate verso istanze sane di quel servizio (cosa che aumenta la percentuale di risposte positive). (Nota del traduttore: una descrizione più dettagliata del pattern può essere trovata, ad esempio, .)
Bulkhead ('compartimento') isola i guasti nei servizi per non compromettere l'intero sistema. Ad esempio, se il servizio B è rotto, un altro servizio (cliente del servizio B) effettua una richiesta al servizio B, di conseguenza esaurisce il suo pool di thread e non può gestire altre richieste (anche se non riguardano il servizio B). (Nota del traduttore: una descrizione più dettagliata del pattern può essere trovata, ad esempio, .)
Tralascerò i dettagli sull'implementazione di questi pattern, poiché è facile trovarli in , e voglio davvero mostrare autentificazione e autorizzazione, di cui discuteremo nella prossima parte dell'articolo.
P.S. dal traduttore
Leggete anche nel nostro blog:
- «Tornare ai microservizi con Istio»: , ;
- «»;
- «».
Fonte: habr.com
