Powrót do mikroserwisów z Istio. Część 2

Powrót do mikroserwisów z Istio. Część 2

Przyp. tłum.: Pierwsza część Ten cykl był poświęcony zapoznaniu się z możliwościami Istio oraz ich demonstracji w praktyce. Teraz skupimy się na bardziej złożonych aspektach konfiguracji i użytkowania tego service mesh, a szczególnie — na dokładnej konfiguracji routingu i zarządzaniu ruchem sieciowym.

Przypominamy również, że w artykule używane są konfiguracje (manifesty dla Kubernetes i Istio) z repozytorium istio-mastery.

Zarządzanie ruchem

Dzięki Istio w klastrze pojawiają się nowe możliwości, które pozwalają na:

  • Dynamiczne routowanie zapytań: wdrożenia typu canary, A/B-testy;
  • Równoważenie obciążenia: proste i niesprzeczne, oparte na haszach;
  • Odzyskiwanie po awariach: timeouty, powtórzenia, circuit breakers;
  • Wprowadzanie usterek: opóźnienia, zerwanie połączeń itd.

W dalszej części artykułu te możliwości zostaną zaprezentowane na przykładzie wybranej aplikacji, a po drodze przedstawione zostaną nowe koncepcje. Pierwszą z takich koncepcji będą DestinationRules (tj. zasady dotyczące odbiorcy ruchu/zapytań — przyp. tłum.), za pomocą których aktywujemy A/B-testy.

A/B-testy:  DestinationRules w praktyce

A/B-testy są stosowane w przypadkach, gdy istnieją dwie wersje aplikacji (zwykle różnią się wizualnie) i nie jesteśmy w 100% pewni, która z nich poprawi interakcję z użytkownikiem. Dlatego jednocześnie uruchamiamy obie wersje i zbieramy metryki.

Aby zrealizować drugą wersję front-endu, niezbędną do demonstracji A/B-testów, wykonaj następujące polecenie:

$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions/sa-frontend-green created

Manifest deploymentu dla "zielonej wersji" różni się w dwóch miejscach:

  1. Obraz oparty jest na innym tagu — istio-green,
  2. Pod’y mają etykietę version: green.

Ponieważ oba deploymenty mają etykietę app: sa-frontend, zapytania, które są routowane przez wirtualną usługę sa-external-services do usługi sa-frontend, będą przekierowywane do wszystkich jej instancji, a obciążenie zostanie rozdzielone za pomocą algorytmu round-robin, co doprowadzi do następującej sytuacji:

Powrót do mikroserwisów z Istio. Część 2
Żądane pliki nie zostały znalezione

Te pliki nie zostały znalezione, ponieważ różnie nazywają się w różnych wersjach aplikacji. Upewnijmy się o tym:

$ 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

To oznacza, że index.html, żądanie jednej wersji statycznych plików może być wysyłane przez load balancer do podów, które mają inną wersję, gdzie, z oczywistych względów, takich plików nie ma. Dlatego, aby aplikacja działała, musimy wprowadzić ograniczenie: "ta sama wersja aplikacji, która zwróciła index.html, powinna obsługiwać także kolejne żądania».

Osiągniemy ten cel dzięki spójnemu balansowaniu obciążenia na podstawie hashy (Spójne balansowanie obciążenia przy użyciu hasha). W takim przypadku żądania od jednego klienta są wysyłane do tego samego egzemplarza backendu, co jest realizowane za pomocą zdefiniowanej właściwości — na przykład nagłówka HTTP. Realizowane jest z pomocą  DestinationRules.

DestinationRules

Po tym, jak VirtualService pokierował żądanie do odpowiedniej usługi, za pomocą DestinationRules możemy określić polityki, które będą miały zastosowanie do ruchu przeznaczonego dla egzemplarzy tej usługi:

Powrót do mikroserwisów z Istio. Część 2
Zarządzanie ruchem z zasobami Istio

Uwaga: Wpływ zasobów Istio na ruch sieciowy przedstawiony jest tutaj w uproszczony sposób. Mówiąc dokładniej, decyzja, do którego egzemplarza wysłać żądanie, podejmowana jest przez Envoy w Ingress Gateway, skonfigurowanym w CRD.

Za pomocą Destination Rules możemy skonfigurować balansowanie obciążenia tak, aby używane były spójne hashe i gwarantowane były odpowiedzi tego samego egzemplarza usługi dla tego samego użytkownika. Następująca konfiguracja pozwala to osiągnąć (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 — hash będzie generowany na podstawie zawartości nagłówka HTTP version.

Wdróż tę konfigurację następującą komendą:

$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io/sa-frontend utworzony

A teraz uruchom poniższą komendę i upewnij się, że otrzymujesz odpowiednie pliki, gdy określasz nagłówek version:

$ curl --silent -H "version: yogo" http://$EXTERNAL_IP/ | tr '"' 'n' | grep main

Uwaga: Aby dodawać różne wartości w nagłówku i testować wyniki bezpośrednio w przeglądarce, można skorzystać z tego rozszerzenia do Chrome (lub tego dla Firefox — przyp. tłum..

W rzeczywistości, DestinationRules mają więcej możliwości w zakresie balansowania obciążenia — szczegóły uzgodnij w oficjalnej dokumentacji.

Zanim przejdziemy dalej w badaniu VirtualService, usuńmy zieloną wersję aplikacji oraz odpowiednią regułę kierowania ruchem, wykonując następujące polecenia:

$ 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

Mirroring: Virtual Services w praktyce

Cieniowanie ("shadowing") lub Mirroring ("mirroring") stosuje się w sytuacjach, kiedy chcemy przetestować zmiany w produkcji, nie wpływając na użytkowników końcowych: dublujemy ("lustrzujemy") zapytania na drugi egzemplarz, gdzie wprowadzono potrzebne zmiany, i obserwujemy rezultaty. Mówiąc prościej, to sytuacja, w której twój(a) kolega wybiera najważniejszy problem i zgłasza pull request w postaci ogromnej plątaniny, że nikt nie może go rzeczywiście ocenić.

Aby sprawdzić ten scenariusz w akcji, stworzymy drugi egzemplarz SA-Logic z błędami (buggy), wykonując następujące polecenie:

$ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service-buggy.yaml
deployment.extensions/sa-logic-buggy created

A teraz wykonamy polecenie, aby upewnić się, że wszystkie egzemplarze z app=sa-logic mają również etykiety z odpowiednimi wersjami:

$ 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

Serwis sa-logic jest ukierunkowane na pod’y z etykietą app=sa-logic, dlatego wszystkie zapytania będą rozdzielane pomiędzy wszystkie egzemplarze:

Powrót do mikroserwisów z Istio. Część 2

… ale chcemy, aby zapytania były kierowane na egzemplarze z wersją v1 i były lustrzowane na egzemplarze z wersją v2:

Powrót do mikroserwisów z Istio. Część 2

Osiągniemy to za pomocą VirtualService w połączeniu z DestinationRule, gdzie zasady określają podzbiory i trasy VirtualService do konkretnego podzbioru.

Definicja podzbiorów w Destination Rules

Podzbiory (subsets) są definiowane następującą konfiguracją (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. Host (Najciekawsza linia — ostatnia.) określa, że zasada ta stosuje się tylko w przypadkach, gdy trasa prowadzi do usługi sa-logic;
  2. Nazwy (name) podzbiorów są używane do kierowania na egzemplarze podzbioru;
  3. Etykieta (label) definiuje pary klucz-wartość, którym muszą odpowiadać instancje, aby stały się częścią podzbioru.

Wdróż tę konfigurację następującą komendą:

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic utworzono

Teraz, gdy podzbiory zostały zdefiniowane, możemy przejść do konfiguracji VirtualService, aby zastosować zasady do żądań do sa-logic, aby:

  1. były kierowane do podzbioru v1,
  2. były lustrowane do podzbioru v2.

Następny manifest pozwala osiągnąć zamierzony efekt (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

Tutaj nie są potrzebne wyjaśnienia, więc po prostu zobaczmy to w akcji:

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic utworzono

Dodamy obciążenie, wywołując następującą komendę:

$ while true; do curl -v http://$EXTERNAL_IP/sentiment 
    -H "Content-type: application/json" 
    -d '{"sentence": "I love yogobella"}'; 
    sleep .8; done

Zobaczymy wyniki w Grafanie, gdzie można zauważyć, że wersja z błędami (buggy) prowadzi do błędów w ~60 % żądań, ale żadne z tych błędów nie wpływa na użytkowników końcowych, ponieważ odpowiada im działający serwis.

Powrót do mikroserwisów z Istio. Część 2
Skuteczność odpowiedzi różnych wersji serwisu sa-logic

Tutaj po raz pierwszy zobaczyliśmy, jak VirtualService jest stosowany wobec Envoy naszych serwisów: kiedy sa-web-app wysyła żądanie do sa-logic, przechodzi przez sidecar Envoy, który — poprzez VirtualService — jest skonfigurowany do kierowania żądania do podzbioru v1 i lustrowania żądania do podzbioru v2 serwisu sa-logic.

Wiem: już pomyślałeś, że Virtual Services są proste. W następnym rozdziale rozszerzymy to zdanie, pokazując, że są również naprawdę wspaniałe.

Canary releases

Canary Deployment — proces wprowadzania nowej wersji aplikacji dla niewielkiej liczby użytkowników. Używa się go, aby upewnić się, że w wersji nie ma problemów i tylko po tym, będąc pewnym dostatecznej jakości wersji, rozprzestrzenić ją na większą publiczność.większąAby zademonstrować wdrożenie kanaryjne, będziemy kontynuować pracę z podzbiorem

Nie będziemy się ograniczać i od razu skierujemy 20 % użytkowników na wersję z błędami (to będzie nasza kanaryjna wersja), a pozostałe 80 % — na normalny serwis. W tym celu zastosujemy następujący VirtualService ( buggy u sa-logic.

sa-logic-subsets-canary-vs.yamlsa-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 — to waga (waga), która określa procent żądań, które będą kierowane do odbiorcy lub podzbioru odbiorcy.

Zaktualizujemy poprzednią konfigurację VirtualService za pomocą sa-logic następującego polecenia:

$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic skonfigurowany

… i natychmiast zobaczymy, że część żądań kończy się błędami:

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

VirtualServices aktywują wypuszczenia kanaryjskie: w tym przypadku ograniczyliśmy potencjalne konsekwencje problemów do 20% bazy użytkowników. Świetnie! Teraz w każdym przypadku, gdy nie jesteśmy pewni naszego kodu (innymi słowy — zawsze…), możemy użyć mirroringu i wypuszczeń kanaryjskich.

Limity czasowe i próby ponowne

Jednak nie zawsze błędy występują w kodzie. Na liście „8 fałszywych przekonań w obliczeniach rozproszonych” na pierwszym miejscu znajduje się błędne przekonanie, że „sieć jest niezawodna”. W rzeczywistości sieć nie jest niezawodna, i z tego powodu potrzebujemy limitów czasowych (timeouts) i prób ponownych (retries).

Aby zademonstrować, wciąż będziemy używać tej samej problematycznej wersji sa-logic (buggy), a niestabilność sieci symulować losowymi awariami.

Niech nasza usługa z błędami ma 1/3 szans na zbyt długi czas odpowiedzi, 1/3 — na zakończenie błędem wewnętrznym serwera i 1/3 — na pomyślne zwrócenie strony.

Aby złagodzić skutki takich problemów i poprawić życie użytkowników, możemy:

  1. dodać limit czasowy, jeśli usługa odpowiada dłużej niż 8 sekund,
  2. próbować ponownie, jeśli żądanie kończy się błędem.

Aby to zaimplementować, skorzystamy z takiej definicji zasobu (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. Limit czasowy dla żądania wynosi 8 sekund;
  2. Próby ponowne żądań są podejmowane 3 razy;
  3. Każda próba jest uznawana za nieudaną, jeśli czas odpowiedzi przekracza 3 sekundy.

W ten sposób osiągnęliśmy optymalizację, ponieważ użytkownik nie będzie musiał czekać dłużej niż 8 sekund, a my podejmiemy trzy nowe próby uzyskania odpowiedzi w przypadku błędów, zwiększając szansę na udaną odpowiedź.

Zastosuj zaktualizowaną konfigurację następującą komendą:

$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts-vs.yaml
virtualservice.networking.istio.io/sa-logic skonfigurowany

I sprawdź w wykresach Grafana, że liczba udanych odpowiedzi wzrosła powyżej:

Powrót do mikroserwisów z Istio. Część 2
Poprawa w statystykach udanych odpowiedzi po dodaniu timeoutów i ponownych prób

Przed przejściem do następnej sekcji (a dokładniej — do następnej części artykułu, ponieważ w tej części nie będzie już praktycznych eksperymentów — przyp. tłum.), usuń sa-logic-buggy i VirtualService, wykonując następujące komendy:

$ kubectl delete deployment sa-logic-buggy
deployment.extensions “sa-logic-buggy” usunięty
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io “sa-logic” usunięty

Wzorce Circuit Breaker i Bulkhead

Mówimy o dwóch ważnych wzorcach w architekturze mikroserwisów, które pozwalają na samodzielne uzdrawianie (self-healing) usług.

Circuit Breaker („wyłącznik automatyczny”) jest używany do przerywania żądań kierowanych do instancji usługi, która jest uznawana za niezdrową, a jej przywrócenia w momencie, gdy żądania klientów są przekierowywane do zdrowych instancji tej usługi (co zwiększa procent udanych odpowiedzi). (Przyp. tłum.: Bardziej szczegółowy opis wzorca można znaleźć w tutaj.)

Bulkhead („przegroda”) izoluje awarie w usługach od wpłynięcia na cały system. Na przykład, jeśli usługa B jest uszkodzona, a inna usługa (klient usługi B) wysyła zapytanie do usługi B, w rezultacie wykorzysta swoją pulę wątków i nie będzie mogła obsłużyć innych zapytań (nawet jeśli nie dotyczą one usługi B). (Przyp. tłum.: Bardziej szczegółowy opis wzorca można znaleźć w tutaj.)

Pomińmy szczegóły dotyczące implementacji tych wzorców, ponieważ łatwo je znaleźć w oficjalnej dokumentacji, a także naprawdę chcę pokazać autoryzację i uwierzytelnienie, o których mowa będzie w następnej części artykułu.

P.S. od tłumacza

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster