Zasada nieoznaczoności Heisenberga mówi, że nie można jednocześnie zmierzyć położenia obiektu i jego prędkości. Jeśli obiekt się porusza, nie ma określonego położenia. A jeśli położenie istnieje – oznacza to, że nie ma prędkości.

Jeśli chodzi o mikrousługi na platformie Red Hat OpenShift (i zarządzane przez Kubernetes), to dzięki odpowiedniemu oprogramowaniu z otwartym kodem mogą jednocześnie raportować zarówno swoje osiągi, jak i stan. To nie podważa oczywiście zasady Heisenberga, ale eliminuje niepewność w pracy z aplikacjami w chmurze. Istio umożliwia łatwe zorganizowanie śledzenia i monitorowania takich aplikacji, aby mieć nad nimi pełną kontrolę.
Określamy terminologię
Pod śledzeniem (Tracing) rozumiemy logowanie aktywności systemowej. Brzmi dość ogólnie, ale w rzeczywistości jedną z podstawowych zasad jest, aby zrzucać dane śledzenia do odpowiedniego magazynu, nie martwiąc się o ich formatowanie. Cała praca związana z poszukiwaniem i analizą danych spoczywa na ich odbiorcy. W Istio używana jest system śledzenia Jaeger, realizujący model danych OpenTracing.
Śladami (Traces, a słowo „śladami” używane jest tutaj w znaczeniu „odsłon głównych”, jak na przykład w ekspertyzie balistycznej) będziemy nazywać dane, które w pełni opisują przepływ żądania lub jednostki pracy, jak się mówi, „od początku do końca”. Na przykład wszystko, co dzieje się od momentu, gdy użytkownik naciska przycisk na stronie internetowej, aż do momentu zwrócenia danych, włącznie ze wszystkimi mikrousługami, które biorą w tym udział. Można powiedzieć, że jeden ślad w pełni opisuje (lub modeluje) przepływ żądania w obie strony. W interfejsie Jaeger, ślady są rozkładane na składniki wzdłuż osi czasu, podobnie jak łańcuch można rozłożyć na pojedyncze ogniwa. Tylko zamiast ogniw ślad składa się z tak zwanych span’ów.
Span – to przedział od początku wykonania jednostki pracy do jej zakończenia. Kontynuując analogię, można powiedzieć, że każdy span jest osobnym ogniwem łańcucha. Span może mieć (lub nie mieć) jeden lub więcej potomnych span’ów. W związku z tym, span najwyższego poziomu (root span) będzie miał ten sam ogólny czas trwania, co ślad, do którego się odnosi.
Monitoring – to właściwie samo obserwowanie twojego systemu – oczami, przez UI lub za pomocą narzędzi automatyzacji. U podstaw monitorowania leżą dane śledzenia. W Istio monitorowanie jest realizowane za pomocą Prometheusa i posiada odpowiedni interfejs użytkownika. Prometheus wspiera automatyczne monitorowanie z użyciem powiadomień Alerts i Alert Managers.
Robimy znaki
Aby śledzenie stało się możliwe, aplikacja musi stworzyć kolekcję spanów. Następnie muszą być one eksportowane do Jaeger, aby ten z kolei stworzył wizualną reprezentację śledzenia. Między innymi te spany oznaczają nazwę operacji oraz znaczniki czasowe jej rozpoczęcia i zakończenia. Przekazywanie spanów odbywa się przez przekierowanie nagłówków HTTP zapytań przeznaczonych dla Jaeger z zapytań przychodzących do wychodzących. W zależności od używanego języka programowania może to wymagać niewielkiej modyfikacji kodu źródłowego aplikacji. Poniżej przedstawiony jest przykład kodu w Javie (przy użyciu frameworka Spring Boot), który dodaje nagłówki B3 (w stylu Zipkin) do twojego zapytania w klasie konfiguracyjnej Spring:

W tym celu wykorzystuje się następujące ustawienia nagłówków:

Jeśli używasz Javy, to kod można zostawić w spokoju i po prostu dodać kilka linii do pliku POM w Mavenie oraz ustawić zmienne środowiskowe. Oto jakie linie należy dodać do pliku POM.XML, aby wdrożyć Jaeger Tracer Resolver:

A odpowiednie zmienne środowiskowe ustawia się w Dockerfile:

Wszystko, teraz wszystko jest skonfigurowane, a nasze mikrousługi zaczną generować dane śledzenia.
Przyglądając się z grubsza
Istio zawiera prostą desktopy opartą na Grafanie. Kiedy wszystko jest skonfigurowane i działa na platformie Red Hat OpenShift PaaS (w naszym przykładzie Red Hat OpenShift i Kubernetes są uruchomione na minishift), ten pulpit uruchamia się następującą komendą:
open "$(minishift openshift service grafana -u)\/d\/1\/istio-dashboard?refresh=5⩝Id=1"
Panel Grafany pozwala szybko ocenić działanie systemu. Fragment tego panelu jest pokazany na poniższym rysunku:

Tut można zauważyć, że mikroserwis customer wywołuje mikroserwis preference v1, a ten z kolei wywołuje mikroserwisy recommendation v1 i v2. Na panelu Grafana znajduje się blok Dashboard Row dla metryk na wysokim poziomie, takich jak ogólna liczba zapytań (Global Request Volume), wskaźnik udanych zapytań (success rates) oraz błędy 4xx. Dodatkowo znajduje się tam widok Server Mesh z wykresami dla każdego serwisu oraz blok Services Row do przeglądania szczegółowych informacji o każdym kontenerze dla każdego serwisu.
Teraz zagłębimy się w temat
Przy odpowiednio skonfigurowanym śledzeniu Istio, co można powiedzieć, że działa wręcz od razu, umożliwia to głębszą analizę wydajności systemu. W interfejsie Jaeger można przeglądać śledzenia i zobaczyć, jak daleko i głęboko sięgają, a także wizualnie zlokalizować wąskie gardła w wydajności. Przy użyciu Red Hat OpenShift na platformie minishift uruchomienie interfejsu Jaeger UI wykonuje się poleceniem:
minishift openshift service jaeger-query --in-browser

Co można powiedzieć o śledzeniu na tym zrzucie ekranu:
- Dzieli się na 7 span'ów.
- Całkowity czas wykonania wynosi 6.99 ms.
- Na mikroserwisie recommendation, który jest ostatni w łańcuchu, spędza się 0.69 ms.
Tego rodzaju diagramy pozwalają szybko zrozumieć sytuację, gdy z powodu jednego niewłaściwie działającego serwisu t.j. cała wydajność systemu na tym cierpi.
A teraz utrudnimy zadanie i uruchomimy dwa egzemplarze mikroserwisu recommendation:v2 poleceniem oc scale --replicas=2 deployment/recommendation-v2. Oto jakie mamy po tym pod'y:

Jeśli teraz przełączymy się z powrotem do Jaeger i rozwinimy span dla serwisu recommendation, zobaczymy, na który pod są kierowane zapytania. Dzięki temu możemy łatwo zlokalizować problemy na poziomie konkretnych pod'ów. Należy przy tym zwrócić uwagę na pole node_id:

Dokąd i jak wszystko się porusza
Teraz przechodzimy do interfejsu Prometheus i zupełnie przewidywanie widzimy, że zapytania między drugą a pierwszą wersją serwisu recommendation dzielą się w proporcji 2:1, dokładnie zgodnie z liczbą działających pod'ów. Przy czym ten wykres będzie dynamicznie się zmieniał przy skalowaniu pod'ów w górę i w dół, co będzie szczególnie przydatne przy Canary Deployment (szczegółowiej przyjrzymy się tej metodzie wdrażania następnym razem).

To dopiero początek
Dzisiaj jedynie lekko dotknęliśmy skarbnicy przydatnych informacji o Jaegerze, Grafanie i Prometheusie. To było naszym celem – wskazać wam właściwy kierunek i otworzyć perspektywy Istio.
Pamiętajcie, że to wszystko jest już wbudowane w Istio. Używając określonych języków programowania (np. Java) i frameworków (np. Spring Boot), można to wszystko zrealizować, nie dotykając samego kodu aplikacji. Tak, kod trzeba nieco zmodyfikować, jeśli używasz innych języków, przede wszystkim Nodejs lub C#. Ponieważ jednak śledzenie (czytaj: „tracing”) jest jednym z wymogów przy tworzeniu solidnych systemów chmurowych, w każdym razie będziesz musiał edytować kod, niezależnie od tego, czy masz Istio, czy nie. Dlaczego więc nie wykorzystać tego wysiłku bardziej korzystnie?
Przynajmniej po to, aby zawsze odpowiadać na pytania „gdzie?” i „jak szybko?” z 100% pewnością.
Chaos engineering w Istio: tak to było zaplanowane
Umiejętność psucia rzeczy pomaga sprawić, by nie psuły się
Testowanie oprogramowania to nie tylko skomplikowana, ale i ważna sprawa. W tym samym czasie testowanie poprawności (np. czy funkcja zwraca prawidłowy wynik) to jedno, a testowanie w warunkach niestabilnej sieci to zupełnie inne zadanie (często zakłada się, że sieć działa bez awarii, co jest pierwszym z ośmiu mitów dotyczących obliczeń rozproszonych). Jednym z wyzwań w rozwiązaniu tego problemu jest to, jak symulować awarie w systemie lub wprowadzać je celowo, wykonując tzw. fault injection. Można to zrobić, modyfikując kod źródłowy samej aplikacji. Ale wtedy nie będziesz testował swojego pierwotnego kodu, lecz jego wersję, która specjalnie symuluje awarie. W rezultacie ryzykujesz wpadnięcie w śmiertelne objęcia fault injection i natrafienie na gejzenbugi – błędy, które znikają, gdy próbujesz je wykryć.
A teraz pokażemy, jak Istio pomaga poradzić sobie z tymi trudnościami błyskawicznie.
Jak to wszystko wygląda, gdy wszystko działa świetnie
Rozważmy następujący scenariusz: mamy dwa pod’y dla naszego mikroserwisu recommendation, które wzięliśmy z podręcznika po Istio. Jeden pod oznaczony jest jako v1, a drugi jako v2. Jak widać, na razie wszystko działa doskonale:

(Na marginesie, liczba po prawej to po prostu licznik wywołań dla każdego poda)
Ale to nie o to nam chodzi, prawda? Spróbujmy wszystko złamać, zupełnie nie dotykając kodu źródłowego.
Tworzymy przestoje w pracy mikroserwisu
Poniżej znajduje się plik yaml dla reguły routingu Istio, która w połowie przypadków będzie powodować błąd (błąd serwera 503):

Zauważ, że wyraźnie określamy, że w połowie przypadków powinien być zwracany błąd 503.
Tak wygląda zrzut ekranu polecenia curl uruchomionego w pętli po aktywacji tej reguły, aby symulować awarie. Jak widać, połowa żądań zwraca błąd 503, niezależnie od tego, do którego poda – v1 czy v2 – są wysyłane:

Aby przywrócić normalne działanie, wystarczy usunąć tę regułę, w naszym przypadku poleceniem istioctl delete routerule recommendation-503 -n tutorial. Tutaj Tutorial to nazwa projektu Red Hat OpenShift, w ramach którego działa nasz podręcznik dotyczący Istio.
Wprowadzamy sztuczne opóźnienia
Sztuczne błędy 503 pomagają przetestować system pod kątem odporności na awarie, ale umiejętność przewidywania i radzenia sobie z opóźnieniami powinna Cię jeszcze bardziej zaimponować. W końcu opóźnienia w rzeczywistości występują częściej niż awarie. Wolno działający mikroserwis to trucizna, która szkodzi całemu systemowi. Dzięki Istio możesz przetestować kod związany z obsługą opóźnień, nie zmieniając go przy tym. Na początek pokażemy, jak to zrobić w przypadku wprowadzenia sztucznych opóźnień w sieci.
Zauważ, że po takim teście możesz potrzebować (lub chcieć) dopracować swój kod. Dobrą wiadomością jest to, że w tym przypadku będziesz działać proaktywnie, a nie reaktywnie. Tak powinien być skonstruowany cykl programowania: kodowanie-testowanie-opinia zwrotna-kodowanie-testowanie…
Oto jak wygląda reguła, która… Chociaż wiesz co? Istio jest tak proste, a ten plik yaml tak zrozumiały, że wszystko w tym przykładzie mówi samo za siebie, po prostu spójrz:

W połowie przypadków napotkamy 7-sekundowe opóźnienie. I to nie to samo, co wstawienie polecenia sleep do kodu źródłowego, ponieważ Istio rzeczywiście opóźnia żądanie o 7 sekund. Ponieważ Istio wspiera śledzenie Jaeger, to opóźnienie jest wyraźnie widoczne w interfejsie użytkownika Jaeger’a, jak pokazano na zrzucie ekranu poniżej. Zauważ długie żądanie w prawym górnym rogu wykresu – jego czas trwania wynosi 7,02 sekundy:

Ten scenariusz pozwala przetestować kod w warunkach opóźnień sieciowych. I oczywiście, usuwając tę regułę, zlikwidujemy sztuczne opóźnienie. Powtarzamy, ale znowu zrobiliśmy to wszystko, nie dotykając kodu źródłowego.
Nie ustępować i nie poddawać się
Inną przydatną funkcją Istio w inżynierii chaosu jest wielokrotne próbowanie wezwania usługi określoną liczbę razy. Chodzi o to, aby nie przerywać prób, gdy pierwsze żądanie kończy się błędem 503 – a wtedy, może za N-ty razem, będziemy mieli szczęście. Może usługa po prostu chwilowo odpoczęła z jakiegoś powodu. Tak, ten powód trzeba by zbadać i wyeliminować. Ale to później, a na razie spróbujmy zrobić tak, aby system wciąż działał.
Zatem chcemy, aby usługa od czasu do czasu zgłaszała błąd 503, a Istio po tym ponownie próbowało się z nią połączyć. I tutaj wyraźnie potrzebny jest sposób generowania błędu 503, nie dotykając samego kodu…
Stop, czekajcie! Przecież dopiero to robiliśmy.
Ten plik sprawi, że usługa recommendation-v2 w połowie przypadków zwróci błąd 503:

Oczywiście, część żądań zakończy się niepowodzeniem:

A teraz wykorzystajmy funkcję Retry w Istio:

Ta zasada routingu wykonuje trzy próby z dwu-sekundowym interwałem i powinna skrócić (a idealnie całkowicie wyeliminować) błędy 503:

Podsumowując: sprawiliśmy, że Istio, po pierwsze, generuje błąd 503 dla połowy żądań. Po drugie, to samo Istio wykonuje trzy próby ponownego połączenia się z usługą w przypadku wystąpienia błędu 503. W rezultacie wszystko działa po prostu doskonale. W ten sposób, wykorzystując funkcję Retry, spełniliśmy nasze obietnice, aby nie ustępować i nie poddawać się.
I tak, znowu to zrobiliśmy, całkowicie nie dotykając kodu. Wszystko, czego potrzebowaliśmy, to dwie zasady routingu w Istio:

Jak nie zawieść użytkownika lub siedmiu osób nie czeka.
A teraz odwróćmy sytuację i rozważmy scenariusz, w którym nie poddawanie się i nie rezygnowanie ma sens tylko przez pewien określony czas. Następnie należy po prostu zakończyć próby przetworzenia zapytania, aby nie zmuszać wszystkich do czekania na jeden spowalniający serwis. Innymi słowy, nie będziemy bronić straconej pozycji, lecz ustąpimy do zapasowej linii, aby nie zawodzić użytkownika strony i nie zmuszać go do biernego czekania.
W Istio można ustawić limit czasu wykonywania zapytania. Jeśli serwis przekroczy ten limit, zwracany jest błąd 504 (Gateway Timeout) – i to wszystko jest konfigurowane przez Istio. Ale będziemy musieli dodać do kodu źródłowego serwisu polecenie sleep (a następnie, oczywiście, wykonać rebuild i redeploy), aby zasymulować wolne działanie serwisu. Niestety, inaczej się nie da.
Zatem wstawiliśmy trzysekundowy sleep w kodzie serwisu recommendation v2, przebudowaliśmy odpowiedni obraz i dokonaliśmy redeploy kontenera, a teraz dodamy limit czasu za pomocą następującej reguły routingu Istio:

Na powyższym zrzucie ekranu można zobaczyć, że rezygnujemy z prób kontaktu z serwisem recommendation, jeśli nie otrzymujemy odpowiedzi w ciągu jednej sekundy, to znaczy jeszcze przed wystąpieniem błędu 504. Po zastosowaniu tej reguły routingu (i dodaniu trzysekundowego sleep'a w kodzie serwisu recommendation:v2) uzyskamy następujący wynik:

Znów powtarzamy, ale limit czasu można ustawić, nie zmieniając kodu źródłowego. A dodatkowym plusem jest to, że teraz możesz modyfikować swój kod tak, aby reagował na limit czasu i łatwo testować te zmiany za pomocą Istio.
A teraz wszystko razem
Wprowadzenie odrobiny chaosu za pomocą Istio to doskonały sposób na przetestowanie swojego kodu i niezawodności całego systemu. Wzorce fallback, bulkhead i circuit breaker, mechanizmy wprowadzania sztucznych awarii i opóźnień, a także ponowne wywołania i limity czasowe będą bardzo przydatne przy tworzeniu odpornych na błędy systemów chmurowych. W połączeniu z Kubernetes i Red Hat OpenShift te narzędzia pomogą pewnie zmierzyć się z przyszłością.
Źródło: habr.com
