Netramesh – lekkie rozwiązanie service mesh

W trakcie przechodzenia od monolitycznej aplikacji do architektury mikroserwisowej napotykamy nowe problemy.

W monolitycznej aplikacji zazwyczaj wystarczy po prostu określić, w której części systemu wystąpił błąd. Prawdopodobnie problem leży w kodzie samego monolitu lub w bazie danych. Jednak kiedy zaczynamy szukać problemu w architekturze mikroserwisowej, sytuacja nie jest już tak oczywista. Musimy znaleźć całą ścieżkę, którą przeszedł żądanie od początku do końca, wyróżniając ją spośród setek mikroserwisów. Co więcej, wiele z nich ma również własne magazyny, w których mogą występować zarówno błędy logiczne, jak i problemy z wydajnością oraz odpornością na awarie.

Netramesh – lekkie rozwiązanie service mesh

Długo szukałem narzędzia, które pomogłoby poradzić sobie z takimi problemami (pisałem o tym na Habra: 1, 2), ale ostatecznie stworzyłem własne rozwiązanie open-source. W artykule opisuję zalety podejścia service mesh i dzielę się nowym narzędziem do jego wdrożenia.

Rozproszony tracing jest powszechnym rozwiązaniem problemu znajdowania błędów w rozproszonych systemach. Ale co, jeśli w systemie nie wdrożono jeszcze takiego podejścia do zbierania informacji o interakcjach sieciowych, lub co gorsza, w części systemu to działa, a w części nie, ponieważ nie dodano go do starych usług? Aby określić dokładną przyczynę problemu, należy mieć pełny obraz tego, co dzieje się w systemie. Szczególnie ważne jest, aby rozumieć, które mikroserwisy uczestniczą w kluczowych dla biznesu ścieżkach.

W tym przypadku z pomocą może przyjść podejście service mesh, które zajmie się całą machiną zbierania informacji sieciowych na poziomie niższym niż same usługi. To podejście pozwala nam przechwytywać cały ruch i analizować go na bieżąco. Co więcej, aplikacje nie muszą o tym nic wiedzieć.

Podejście service mesh

Główną ideą podejścia service mesh jest dodanie jeszcze jednej warstwy infrastrukturalnej nad siecią, która pozwoli nam na realizację różnych zadań związanych z interakcją między usługami. Większość wdrożeń działa w następujący sposób: do każdej mikrousługi dodawany jest dodatkowy kontener sidecar z przezroczystym proxy, przez które przechodzi cały przychodzący i wychodzący ruch usługi. I to właśnie tam możemy realizować równoważenie obciążenia, stosować polityki bezpieczeństwa, wprowadzać ograniczenia liczby zapytań oraz zbierać istotne informacje na temat interakcji usług w produkcji.

Netramesh – lekkie rozwiązanie service mesh

Rozwiązania

Już istnieje kilka wdrożeń tego podejścia: Istio i linkerd2. Oferują one wiele funkcji od razu po zainstalowaniu. Jednak równocześnie wiąże się to z dużym obciążeniem zasobów. Im większy klaster, w którym działa taki system, tym więcej zasobów jest potrzebnych do utrzymania nowej infrastruktury. W Avito korzystamy z klastrów Kubernetes, w których znajdują się tysiące instancji usług (a ich liczba wciąż szybko rośnie). W obecnej wersji Istio zużywa ~300Mb pamięci operacyjnej na każdą instancję usługi. Z powodu dużej liczby funkcji przezroczyste równoważenie obciążenia również wpływa na całkowity czas odpowiedzi usług (nawet do 10ms).

W efekcie przyjrzeliśmy się, jakie dokładnie możliwości są nam potrzebne tu i teraz, i zdecydowaliśmy, że głównym powodem, dla którego zaczęliśmy wdrażać takie rozwiązania, była możliwość zbierania informacji śledzących (tracing) z całego systemu w sposób przezroczysty. Chcieliśmy również mieć kontrolę nad interakcją usług i przeprowadzać różne operacje na nagłówkach, które są przesyłane między usługami.

Ostatecznie doszliśmy do naszego rozwiązania:  Netramesh.

Netramesh

Netramesh — to lekkie rozwiązanie service mesh z możliwością nieskończonego skalowania niezależnie od liczby usług w systemie.

Głównymi celami nowego rozwiązania były niski narzut na zasoby oraz wysoka wydajność. Z podstawowych możliwości chcieliśmy od razu mieć możliwość przezroczystego przesyłania spanów śledzących do naszego systemu Jaeger.

Obecnie większość rozwiązań chmurowych jest realizowana w Golang. Oczywiście, są ku temu powody. Pisanie aplikacji sieciowych w Golang, które działają asynchronicznie z obsługą wejścia-wyjścia i skalują się w miarę potrzeby, jest wygodne i dość proste. Co również bardzo ważne, wydajność jest wystarczająca do wykonania tego zadania. Dlatego również wybraliśmy Golang.

Wydajność

Skupiliśmy nasze wysiłki na osiągnięciu maksymalnej wydajności. Dla rozwiązania, które jest wdrażane obok każdego wystąpienia usługi, konieczne jest niewielkie zużycie pamięci operacyjnej i czasu procesora. Oczywiście, opóźnienie odpowiedzi również powinno być jak najmniejsze.

Spójrzmy, jakie wyniki uzyskaliśmy.

RAM

Netramesh zużywa ~10Mb bez ruchu i maksymalnie 50Mb przy obciążeniu do 10000 RPS na jedno wystąpienie.

Proxy Istio envoy zawsze zużywa ~300Mb w naszych klastrach z tysiącami wystąpień. Uniemożliwia to skalowanie go w całym klastrze.

Netramesh – lekkie rozwiązanie service mesh

Netramesh – lekkie rozwiązanie service mesh

Z Netramesh uzyskaliśmy zmniejszenie zużycia pamięci o ~10 razy.

CPU

Zużycie CPU jest stosunkowo równe pod obciążeniem. Zależy od liczby zapytań na jednostkę czasu do sidecar. Wartości przy 3000 zapytaniach na sekundę w szczycie:

Netramesh – lekkie rozwiązanie service mesh

Netramesh – lekkie rozwiązanie service mesh

Jest jeszcze jeden ważny aspekt: Netramesh to rozwiązanie bez control plane i bez obciążenia nie zużywa czasu procesora. W przypadku Istio sidecary zawsze aktualizują punkty końcowe usług. W rezultacie możemy zobaczyć taki obrazek bez obciążenia:

Netramesh – lekkie rozwiązanie service mesh

Używamy HTTP/1 do komunikacji między usługami. Zwiększenie czasu odpowiedzi w Istio przy proxy z envoy wynosiło do 5-10ms, co jest dość dużo dla usług, które są gotowe odpowiadać w milisekundzie. W przypadku Netramesh ten czas zmniejszył się do 0.5-2ms.

Skalowalność

Niewielka ilość zasobów, jaką zużywa każde proxy, pozwala umieścić je blisko każdej usługi. Netramesh został celowo stworzony bez komponentu control plane, aby zachować lekkość każdego sidecara. Często w rozwiązaniach service mesh control plane rozprzestrzenia informacje o odkrywaniu usług do każdego sidecara. Razem z nią przychodzi również informacja o timeoutach i ustawieniach równoważenia. Wszystko to pozwala na wiele przydatnych rzeczy, ale niestety zwiększa rozmiar sidecarów.

Odkrywanie usług

Netramesh – lekkie rozwiązanie service mesh

Netramesh nie dodaje żadnych dodatkowych mechanizmów do odkrywania usług. Cały ruch jest przezroczysto proxowany przez sidecar netra.

Netramesh obsługuje protokół aplikacyjny HTTP/1. Aby go zdefiniować, używa konfigurowalnej listy portów. Zwykle w systemie znajduje się kilka portów, przez które odbywa się interakcja w ramach HTTP. Na przykład do interakcji między usługami i zewnętrznymi żądaniami wykorzystujemy porty 80, 8890, 8080. W takim przypadku można je określić za pomocą zmiennej środowiskowej NETRA_HTTP_PORTS.

Jeśli używasz Kubernetes jako orkiestratora i jego mechanizmu Service do wewnętrznej interakcji między usługami w klastrze, mechanizm pozostaje dokładnie taki sam. Najpierw mikroserwis otrzymuje adres IP usługi za pomocą kube-dns i nawiązuje nowe połączenie. To połączenie jest nawiązywane najpierw z lokalnym netra-sidecar, a wszystkie pakiety TCP pierwotnie przychodzą do netra. Następnie netra-sidecar nawiązuje połączenie z pierwotnym punktem docelowym. NAT na pod IP na nodzie pozostaje dokładnie taki sam jak bez netra.

Rozproszony tracing i przekazywanie kontekstu

Netramesh zapewnia funkcjonalność niezbędną do wysyłania spanów trace'ów o interakcji HTTP. Netra-sidecar analizuje protokół HTTP, mierzy opóźnienia żądań, wydobywa niezbędne informacje z nagłówków HTTP. Ostatecznie otrzymujemy wszystkie trace'y w jednym systemie Jaeger. Do szczegółowej konfiguracji można również używać zmiennych środowiskowych, które zapewnia oficjalna biblioteka jaeger go library.

Netramesh – lekkie rozwiązanie service mesh

Netramesh – lekkie rozwiązanie service mesh

Ale jest problem. Dopóki usługi nie będą generować i przekazywać specjalnego nagłówka uber, nie zobaczymy połączonych spanów trace w systemie. A to jest to, czego potrzebujemy do szybkiego znajdowania przyczyn problemów. Tutaj Netramesh znowu ma rozwiązanie. Proxy odczytują nagłówki HTTP i, jeśli nie zawierają uber trace id, generują go. Netramesh przechowuje również informacje o przychodzących i wychodzących żądaniach w sidecarze i dopasowuje je poprzez wzbogacenie o niezbędne nagłówki wychodzących żądań. Wszystko, co należy zrobić w usługach, to przekazać jeden nagłówek X-Request-Id, który można skonfigurować za pomocą zmiennej środowiskowej NETRA_HTTP_REQUEST_ID_HEADER_NAME. Aby zarządzać rozmiarem kontekstu w Netramesh, można ustawić następujące zmienne środowiskowe: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS (czas, przez który będzie przechowywany kontekst) i NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (częstotliwość czyszczenia kontekstu).

Możliwe jest również łączenie kilku ścieżek w Twoim systemie przez oznaczanie ich specjalnym znacznikiem sesji. Netra umożliwia ustawienie HTTP_HEADER_TAG_MAP aby przekształcić nagłówki HTTP w odpowiadające im znaczniki śledzenia. Może to być szczególnie przydatne do testowania. Po zakończeniu testów funkcjonalnych możesz sprawdzić, która część systemu została dotknięta, filtrując według odpowiedniego klucza sesji.

Określenie źródła żądania

Aby określić, skąd pochodzi żądanie, można skorzystać z funkcji automatycznego dodawania nagłówka źródłowego. Przy użyciu zmiennej środowiskowej NETRA_HTTP_X_SOURCE_HEADER_NAME można określić nazwę nagłówka, który będzie automatycznie ustawiany. Przy użyciu NETRA_HTTP_X_SOURCE_VALUE można ustawić wartość, do której będzie przypisywany nagłówek X-Source we wszystkich wychodzących żądaniach.

Pozwala to na jednolite rozprzestrzenienie tego przydatnego nagłówka w całej sieci. Następnie można go już używać w usługach i dodawać do logów oraz metryk.

Routowanie ruchu i wnętrze Netramesh

Netramesh składa się z dwóch głównych komponentów. Pierwszy, netra-init, ustawia reguły sieciowe do przechwytywania ruchu. Używa reguł przekierowania iptables do przechwytywania całego lub części ruchu na sidecar, który jest drugim głównym komponentem Netramesh. Można skonfigurować, które porty mają być przechwytywane dla przychodzących i wychodzących sesji TCP: INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS.

Narzędzie to ma również ciekawą funkcję — router probabilistyczny. Jeśli korzystasz z Netramesh wyłącznie do zbierania znacznika śledzenia, to w środowisku produkcyjnym możesz zaoszczędzić zasoby i włączyć routowanie probabilistyczne przy użyciu zmiennych NETRA_INBOUND_PROBABILITY i NETRA_OUTBOUND_PROBABILITY (od 0 do 1). Wartość domyślna wynosi 1 (przechwytywany jest cały ruch).

Po pomyślnym przechwyceniu netra sidecar nawiązuje nowe połączenie i używa SO_ORIGINAL_DST opcji gniazda do uzyskania pierwotnego punktu docelowego. Następnie Netra otwiera nowe połączenie do pierwotnego adresu IP i nawiązuje dwustronną komunikację TCP między stronami, nasłuchując cały przechodzący ruch. Jeśli port jest określony jako HTTP, Netra stara się go przetworzyć i śledzić. Jeśli przetwarzanie HTTP nie powiedzie się, Netra przechodzi na TCP i przezroczyste proxyuje bajty.

Budowanie grafu zależności

Po uzyskaniu dużej ilości informacji o śledzeniu w Jaegerze, chcemy uzyskać pełny graf interakcji w systemie. Jednak jeśli twój system jest odpowiednio obciążony i gromadzi miliardy spanów śledzenia w ciągu dnia, ich agregacja staje się dość trudnym zadaniem. Istnieje oficjalny sposób na to: spark-dependencies. Niemniej jednak zajmie to godziny, aby zbudować pełny graf i zmusi do pobrania z Jaeger wszystkich danych z ostatnich 24 godzin.

Jeśli używasz Elasticsearch do przechowywania spanów śledzenia, możesz skorzystać z prostej narzędzia napisanego w Golang, które stworzy taki sam graf w ciągu minut, wykorzystując funkcje i możliwości Elasticsearch.

Netramesh – lekkie rozwiązanie service mesh

Jak używać Netramesh

Netra może być łatwo dodana do dowolnej usługi działającej pod kontrolą dowolnego orkiestratora. Można zobaczyć przykład tutaj.

Na chwilę obecną Netra nie ma możliwości automatycznego wdrażania sidecara do usług, ale są plany na jego realizację.

Przyszłość Netramesh

Głównym celem Netramesh jest osiągnięcie minimalnych kosztów zasobów i wysokiej wydajności, zapewniając podstawowe możliwości do observability i kontroli interakcji między usługami.

W przyszłości Netramesh otrzyma wsparcie dla innych protokołów aplikacyjnych poza HTTP. W najbliższej przyszłości pojawi się możliwość routingu L7.

Użyj Netramesh, jeśli spotykasz się z podobnymi problemami, i pisz do nas z pytaniami i sugestiami.

Ź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