Tornare ai microservizi insieme a Istio. Parte 2

Tornare ai microservizi insieme a Istio. Parte 2

Nota del traduttore.: Prima parte 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 istio-mastery.

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 creato

Il manifesto del deployment per la "versione verde" differisce in due punti:

  1. L'immagine è basata su un diverso tag — istio-green,
  2. 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 algoritmo round-robin, il che porterà alla seguente situazione:

Tornare ai microservizi insieme a Istio. Parte 2
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:

Tornare ai microservizi insieme a Istio. Parte 2
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ò (destinationrule-sa-frontend.yaml):

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 main

Nota: Per aggiungere valori diversi nell'intestazione e testare i risultati direttamente nel browser, puoi utilizzare questo plugin per Chrome (o questo per Firefox — nota del traduttore.).

In generale, le DestinationRules hanno maggiori possibilità nel bilanciamento del carico — per dettagli chiedere. documentazione ufficiale.

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” deleted

Mirroraggio: 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:

Tornare ai microservizi insieme a Istio. Parte 2

… ma vogliamo che le richieste vengano dirette alle istanze con versione v1 e mirrorate alle istanze con versione v2:

Tornare ai microservizi insieme a Istio. Parte 2

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 (sa-logic-subsets-destinationrule.yaml):

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

  1. L'host (host) definisce che questa regola si applica solo quando il percorso va verso il servizio sa-logic;
  2. I nomi (name) dei sottoinsiemi vengono utilizzati nel routing verso le istanze del sottoinsieme;
  3. 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 created

Ora che i sottoinsiemi sono definiti, possiamo procedere e configurare il VirtualService per applicare le regole alle richieste a sa-logic, in modo che:

  1. Vengano instradate verso il sottoinsieme v1,
  2. Siano mirrorate verso il sottoinsieme v2.

Il seguente manifesto consente di ottenere quanto desiderato (sa-logic-subsets-shadowing-vs.yaml):

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: v2

Non 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 created

Aggiungiamo 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.

Tornare ai microservizi insieme a Istio. Parte 2
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 (sa-logic-subsets-canary-vs.yaml):

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: 500

I 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 “8 miti del calcolo distribuito” 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:

  1. aggiungere un timeout se il servizio risponde dopo 8 secondi,
  2. effettuare un retry se la richiesta si interrompe.

Per implementarlo, utilizzeremo questa definizione di risorsa (sa-logic-retries-timeouts-vs.yaml):

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

  1. Il timeout per la richiesta è impostato su 8 secondi;
  2. I retry per le richieste vengono effettuati per 3 volte;
  3. 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 configurato

E controlla nei grafici di Grafana che il numero di risposte di successo sia aumentato:

Tornare ai microservizi insieme a Istio. Parte 2
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” eliminato

Pattern 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, qui.)

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, qui.)

Tralascerò i dettagli sull'implementazione di questi pattern, poiché è facile trovarli in documentazione ufficiale, e voglio davvero mostrare autentificazione e autorizzazione, di cui discuteremo nella prossima parte dell'articolo.

P.S. dal traduttore

Leggete anche nel nostro blog:

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster