Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud
Witam, jestem Sergey Elantsev, rozwijam się moduł równoważenia obciążenia sieci w Yandex.Cloud. Wcześniej kierowałem rozwojem balansera L7 dla portalu Yandex - koledzy żartują, że niezależnie od tego, co zrobię, okazuje się, że to balanser. Opowiem czytelnikom Habr, jak zarządzać obciążeniem platformy chmurowej, co uważamy za idealne narzędzie do osiągnięcia tego celu i w jaki sposób zmierzamy w kierunku budowy tego narzędzia.

Najpierw wprowadźmy kilka terminów:

  • VIP (Virtual IP) - adres IP balansera
  • Serwer, backend, instancja - maszyna wirtualna, na której działa aplikacja
  • RIP (Real IP) - adres IP serwera
  • Healthcheck - sprawdzanie gotowości serwera
  • Strefa dostępności, Arizona – izolowana infrastruktura w centrum danych
  • Region - związek różnych AZ

Load Balancery rozwiązują trzy główne zadania: wykonują samo równoważenie, poprawiają odporność usługi na awarie i upraszczają jej skalowanie. Tolerancję na awarie zapewnia automatyczne zarządzanie ruchem: moduł równoważący monitoruje stan aplikacji i wyklucza z równoważenia instancje, które nie przejdą kontroli żywotności. Skalowanie zapewnione jest poprzez równomierne rozłożenie obciążenia pomiędzy instancje, a także aktualizację listy instancji na bieżąco. Jeśli równoważenie nie będzie wystarczająco równomierne, niektóre instancje otrzymają obciążenie przekraczające ich limit wydajności, a usługa stanie się mniej niezawodna.

Moduł równoważenia obciążenia jest często klasyfikowany według warstwy protokołu z modelu OSI, na którym działa. Cloud Balancer działa na poziomie TCP, który odpowiada czwartej warstwie, L4.

Przejdźmy do przeglądu architektury balansera chmury. Stopniowo będziemy zwiększać poziom szczegółowości. Elementy wyważarki dzielimy na trzy klasy. Klasa płaszczyzny konfiguracji jest odpowiedzialna za interakcję z użytkownikiem i przechowuje docelowy stan systemu. Płaszczyzna kontrolna przechowuje aktualny stan systemu i zarządza systemami z klasy płaszczyzny danych, które bezpośrednio odpowiadają za dostarczanie ruchu od klientów do Twoich instancji.

Płaszczyzna danych

Ruch trafia do drogich urządzeń zwanych routerami granicznymi. Aby zwiększyć odporność na awarie, w jednym centrum danych działa jednocześnie kilka takich urządzeń. Następnie ruch trafia do modułów równoważących, które ogłaszają adresy IP anycast wszystkim AZ za pośrednictwem protokołu BGP dla klientów. 

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Ruch przesyłany jest poprzez ECMP – jest to strategia routingu, zgodnie z którą do celu może prowadzić kilka równie dobrych tras (w naszym przypadku celem będzie docelowy adres IP) i dowolną z nich można przesyłać pakiety. Wspieramy także pracę w kilku strefach dostępności według następującego schematu: w każdej strefie reklamujemy adres, ruch idzie do najbliższej i nie przekracza jej granic. W dalszej części wpisu przyjrzymy się bardziej szczegółowo temu, co dzieje się z ruchem ulicznym.

Skonfiguruj płaszczyznę

 
Kluczowym komponentem płaszczyzny konfiguracyjnej jest API, za pomocą którego wykonywane są podstawowe operacje na balanserach: tworzenie, usuwanie, zmiana składu instancji, uzyskiwanie wyników kontroli stanu itp. Z jednej strony jest to API REST, z drugiej inne, my w Chmurze bardzo często korzystamy z frameworka gRPC, dlatego „tłumaczymy” REST na gRPC i wtedy używamy już tylko gRPC. Każde żądanie prowadzi do utworzenia serii asynchronicznych idempotentnych zadań, które są wykonywane na wspólnej puli pracowników Yandex.Cloud. Zadania są napisane w taki sposób, że można je w każdej chwili zawiesić, a następnie wznowić. Zapewnia to skalowalność, powtarzalność i rejestrowanie operacji.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

W rezultacie zadanie z API wyśle ​​żądanie do kontrolera usługi Balancer, które jest zapisane w Go. Może dodawać i usuwać balansery, zmieniać skład backendów i ustawień. 

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Usługa przechowuje swój stan w Yandex Database, rozproszonej zarządzanej bazie danych, z której wkrótce będziesz mógł korzystać. W Yandex.Cloud, jak już powiedziałobowiązuje koncepcja karmy dla psów: jeśli sami skorzystamy z naszych usług, to nasi klienci również będą chętnie z nich korzystać. Yandex Database jest przykładem realizacji takiej koncepcji. Wszystkie nasze dane przechowujemy w YDB i nie musimy myśleć o utrzymaniu i skalowaniu bazy danych: te problemy są rozwiązane za nas, korzystamy z bazy danych jako usługi.

Wróćmy do sterownika balansera. Jego zadaniem jest zapisanie informacji o balanserze i wysłanie zadania sprawdzenia gotowości maszyny wirtualnej do kontrolera Healthcheck.

Kontroler kontroli stanu

Otrzymuje żądania zmiany reguł sprawdzania, zapisuje je w YDB, rozdziela zadania pomiędzy węzły sprawdzania stanu zdrowia i agreguje wyniki, które następnie są zapisywane w bazie danych i wysyłane do kontrolera modułu równoważenia obciążenia. Ten z kolei wysyła żądanie zmiany składu klastra w płaszczyźnie danych do węzła Loadbalancer, co omówię poniżej.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Porozmawiajmy więcej o kontrolach stanu zdrowia. Można je podzielić na kilka klas. Audyty mają różne kryteria sukcesu. Kontrole TCP muszą pomyślnie nawiązać połączenie w określonym czasie. Kontrole HTTP wymagają zarówno pomyślnego połączenia, jak i odpowiedzi z kodem stanu 200.

Kontrole różnią się także klasą działania - są aktywne i pasywne. Kontrole pasywne po prostu monitorują, co dzieje się z ruchem ulicznym, bez podejmowania żadnych specjalnych działań. Nie działa to zbyt dobrze na L4, ponieważ zależy to od logiki protokołów wyższego poziomu: na L4 nie ma informacji o tym, jak długo trwała operacja ani czy zakończenie połączenia było dobre, czy złe. Aktywne kontrole wymagają, aby moduł równoważący wysyłał żądania do każdej instancji serwera.

Większość modułów równoważenia obciążenia samodzielnie sprawdza żywotność. W Cloud postanowiliśmy oddzielić te części systemu, aby zwiększyć skalowalność. Takie podejście pozwoli nam zwiększyć liczbę balanserów przy jednoczesnym utrzymaniu liczby żądań kontroli stanu wysyłanych do usługi. Kontrole są przeprowadzane przez oddzielne węzły sprawdzania stanu, w których elementy docelowe kontroli są dzielone i replikowane. Nie można przeprowadzać kontroli z jednego hosta, ponieważ może to zakończyć się niepowodzeniem. Nie dostaniemy wtedy stanu sprawdzanych przez niego instancji. Wykonujemy kontrole dowolnej instancji z co najmniej trzech węzłów kontroli stanu. Dzielimy się celami kontroli między węzłami przy użyciu spójnych algorytmów mieszania.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Oddzielenie równoważenia i sprawdzania stanu może prowadzić do problemów. Jeśli węzeł kontroli stanu wysyła żądania do instancji, omijając moduł równoważący (który obecnie nie obsługuje ruchu), powstaje dziwna sytuacja: zasób wydaje się być żywy, ale ruch do niego nie dociera. Rozwiązujemy ten problem w ten sposób: mamy gwarancję zainicjowania ruchu sprawdzającego stan poprzez moduły równoważące. Inaczej mówiąc, schemat przenoszenia pakietów z ruchem od klientów i z kontroli stanu różni się minimalnie: w obu przypadkach pakiety dotrą do balanserów, które dostarczą je do zasobów docelowych.

Różnica polega na tym, że klienci wysyłają żądania do VIP-ów, podczas gdy kontrole stanu wysyłają żądania do każdego indywidualnego protokołu RIP. Tu pojawia się ciekawy problem: dajemy naszym użytkownikom możliwość tworzenia zasobów w szarych sieciach IP. Wyobraźmy sobie, że jest dwóch różnych właścicieli chmur, którzy ukryli swoje usługi za równoważnikami. Każdy z nich posiada zasoby w podsieci 10.0.0.1/24, o tych samych adresach. Musisz je jakoś rozróżnić i tutaj musisz zagłębić się w strukturę sieci wirtualnej Yandex.Cloud. Więcej szczegółów lepiej poznać w wideo z wydarzenia about:cloud, ważne jest dla nas teraz, aby sieć była wielowarstwowa i posiadała tunele, które można rozróżnić po identyfikatorze podsieci.

Węzły sprawdzające stan zdrowia kontaktują się z modułami równoważącymi przy użyciu tak zwanych adresów quasi-IPv6. Quasi-adres to adres IPv6 z osadzonym w nim adresem IPv4 i identyfikatorem podsieci użytkownika. Ruch dociera do balansera, który wyodrębnia z niego adres zasobu IPv4, zastępuje IPv6 IPv4 i wysyła pakiet do sieci użytkownika.

Ruch zwrotny przebiega w ten sam sposób: moduł równoważący widzi, że miejscem docelowym jest szara sieć, i konwertuje protokół IPv4 na IPv6.

VPP – serce płaszczyzny danych

Balanser jest zaimplementowany przy użyciu technologii Vector Packet Processing (VPP), platformy firmy Cisco do przetwarzania wsadowego ruchu sieciowego. W naszym przypadku framework działa na bazie biblioteki zarządzania urządzeniami sieciowymi w przestrzeni użytkownika — Data Plane Development Kit (DPDK). Zapewnia to wysoką wydajność przetwarzania pakietów: w jądrze występuje znacznie mniej przerwań i nie ma przełączania kontekstu pomiędzy przestrzenią jądra a przestrzenią użytkownika. 

VPP idzie jeszcze dalej i wyciska jeszcze więcej wydajności z systemu, łącząc pakiety w partie. Wzrost wydajności wynika z agresywnego wykorzystania pamięci podręcznych w nowoczesnych procesorach. Stosowane są zarówno pamięci podręczne danych (pakiety są przetwarzane w „wektorach”, dane są blisko siebie), jak i pamięci podręczne instrukcji: w VPP przetwarzanie pakietów odbywa się według wykresu, którego węzły zawierają funkcje realizujące to samo zadanie.

Przykładowo przetwarzanie pakietów IP w VPP odbywa się w następującej kolejności: najpierw nagłówki pakietów są analizowane w węźle analizującym, a następnie przesyłane do węzła, który przekazuje pakiety dalej zgodnie z tablicami routingu.

Trochę hardkoru. Twórcy VPP nie tolerują kompromisów w wykorzystaniu pamięci podręcznych procesora, dlatego typowy kod do przetwarzania wektora pakietów zawiera ręczną wektoryzację: istnieje pętla przetwarzająca, w której przetwarzana jest sytuacja typu „mamy cztery pakiety w kolejce”, potem to samo dla dwóch, a potem - dla jednego. Instrukcje pobierania wstępnego są często używane do ładowania danych do pamięci podręcznej w celu przyspieszenia dostępu do nich w kolejnych iteracjach.

n_left_from = frame->n_vectors;
while (n_left_from > 0)
{
    vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
    // ...
    while (n_left_from >= 4 && n_left_to_next >= 2)
    {
        // processing multiple packets at once
        u32 next0 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        u32 next1 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        // ...
        /* Prefetch next iteration. */
        {
            vlib_buffer_t *p2, *p3;

            p2 = vlib_get_buffer (vm, from[2]);
            p3 = vlib_get_buffer (vm, from[3]);

            vlib_prefetch_buffer_header (p2, LOAD);
            vlib_prefetch_buffer_header (p3, LOAD);

            CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
            CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
        }
        // actually process data
        /* verify speculative enqueues, maybe switch current next frame */
        vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
                to_next, n_left_to_next,
                bi0, bi1, next0, next1);
    }

    while (n_left_from > 0 && n_left_to_next > 0)
    {
        // processing packets by one
    }

    // processed batch
    vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}

Zatem Healthchecks rozmawia przez IPv6 z VPP, co zamienia je w IPv4. Odbywa się to poprzez węzeł na grafie, który nazywamy algorytmicznym NAT. Dla ruchu zwrotnego (i konwersji z IPv6 na IPv4) istnieje ten sam algorytmiczny węzeł NAT.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Bezpośredni ruch od klientów modułu równoważącego przechodzi przez węzły graficzne, które same dokonują równoważenia. 

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Pierwszy węzeł to sesje lepkie. Przechowuje skrót 5-krotka dla ustalonych sesji. 5-krotka zawiera adres i port klienta, z którego przesyłane są informacje, adres i porty zasobów dostępnych do odbierania ruchu, a także protokół sieciowy. 

5-krotkowy skrót pomaga nam wykonywać mniej obliczeń w kolejnym spójnym węźle mieszającym, a także lepiej obsługiwać zmiany na liście zasobów za modułem równoważącym. Kiedy do modułu równoważącego dociera pakiet, dla którego nie ma sesji, jest on wysyłany do spójnego węzła mieszającego. Tutaj następuje równoważenie przy użyciu spójnego hashowania: wybieramy zasób z listy dostępnych zasobów „na żywo”. Następnie pakiety przesyłane są do węzła NAT, który faktycznie zastępuje adres docelowy i ponownie oblicza sumy kontrolne. Jak widać, postępujemy zgodnie z zasadami VPP – lubić lubić, grupując podobne obliczenia w celu zwiększenia wydajności pamięci podręcznej procesora.

Konsekwentne hashowanie

Dlaczego go wybraliśmy i czym w ogóle jest? Najpierw rozważmy poprzednie zadanie - wybór zasobu z listy. 

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

W przypadku niespójnego mieszania obliczany jest skrót przychodzącego pakietu, a zasób jest wybierany z listy na podstawie reszty z dzielenia tego skrótu przez liczbę zasobów. Dopóki lista pozostaje niezmieniona, ten schemat działa dobrze: zawsze wysyłamy pakiety z tą samą 5-krotką do tej samej instancji. Jeśli na przykład jakiś zasób przestanie odpowiadać na kontrole stanu, wówczas dla znacznej części skrótów wybór ulegnie zmianie. Połączenia TCP klienta zostaną zerwane: pakiet, który wcześniej dotarł do instancji A, może zacząć docierać do instancji B, która nie jest zaznajomiona z sesją dla tego pakietu.

Konsekwentne mieszanie rozwiązuje opisany problem. Najłatwiej wyjaśnić tę koncepcję w następujący sposób: wyobraź sobie, że masz pierścień, do którego dystrybuujesz zasoby według skrótu (na przykład według adresu IP:port). Wybór zasobu polega na obróceniu koła o kąt określony przez hash pakietu.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Minimalizuje to redystrybucję ruchu w przypadku zmiany składu zasobów. Usunięcie zasobu będzie miało wpływ tylko na część spójnego pierścienia mieszającego, w którym znajdował się zasób. Dodanie zasobu również zmienia dystrybucję, ale mamy węzeł sesji trwałych, który pozwala nam nie przełączać już ustanowionych sesji na nowe zasoby.

Przyjrzeliśmy się, co dzieje się z ruchem bezpośrednim pomiędzy modułem równoważącym a zasobami. Przyjrzyjmy się teraz ruchowi powrotnemu. Działa według tego samego wzorca, co ruch sprawdzający — poprzez algorytmiczny NAT, czyli odwrotny NAT 44 dla ruchu klienckiego i poprzez NAT 46 dla ruchu sprawdzającego stan. Trzymamy się własnego schematu: ujednolicamy ruch sprawdzający stan zdrowia i rzeczywisty ruch użytkowników.

Węzeł Loadbalancer i zmontowane komponenty

Skład balanserów i zasobów w VPP raportowany jest przez lokalną usługę - węzeł Loadbalancer. Subskrybuje strumień zdarzeń z kontrolera równoważenia obciążenia i jest w stanie wykreślić różnicę pomiędzy bieżącym stanem VPP a stanem docelowym otrzymanym z kontrolera. Otrzymujemy system zamknięty: zdarzenia z API trafiają do kontrolera balansera, który przydziela kontrolerowi Healthcheck zadania polegające na sprawdzeniu „żywotności” zasobów. To z kolei przydziela zadania do węzła kontroli stanu zdrowia i agreguje wyniki, po czym wysyła je z powrotem do kontrolera równoważącego. Węzeł Loadbalancer subskrybuje zdarzenia ze sterownika i zmienia stan VPP. W takim systemie każda usługa wie tylko to, co jest konieczne o usługach sąsiadujących. Liczba połączeń jest ograniczona, a my mamy możliwość niezależnego operowania i skalowania różnych segmentów.

Architektura modułu równoważenia obciążenia sieciowego w Yandex.Cloud

Jakich problemów udało się uniknąć?

Wszystkie nasze usługi na płaszczyźnie kontrolnej są napisane w Go i charakteryzują się dobrą skalowalnością i niezawodnością. Go ma wiele bibliotek open source do tworzenia systemów rozproszonych. Aktywnie korzystamy z GRPC, wszystkie komponenty zawierają implementację odkrywania usług typu open source - nasze usługi monitorują się nawzajem, mogą dynamicznie zmieniać swój skład, a my połączyliśmy to z równoważeniem GRPC. W przypadku metryk korzystamy również z rozwiązania typu open source. W płaszczyźnie danych otrzymaliśmy przyzwoitą wydajność i dużą rezerwę zasobów: bardzo trudno było złożyć stojak, na którym moglibyśmy polegać na wydajności VPP, a nie na żelaznej karcie sieciowej.

Problemy i rozwiązania

Co nie zadziałało tak dobrze? Go ma automatyczne zarządzanie pamięcią, ale wycieki pamięci nadal się zdarzają. Najłatwiej sobie z nimi poradzić, uruchamiając goroutines i pamiętając o ich zakończeniu. Na wynos: obserwuj zużycie pamięci przez programy Go. Często dobrym wskaźnikiem jest liczba goroutines. Jest w tej historii plus: w Go łatwo jest uzyskać dane dotyczące czasu wykonania - zużycie pamięci, liczbę uruchomionych goroutines i wiele innych parametrów.

Ponadto Go może nie być najlepszym wyborem do testów funkcjonalnych. Są dość szczegółowe i standardowe podejście polegające na „uruchamianiu wszystkiego w CI wsadowo” nie jest dla nich zbyt odpowiednie. Faktem jest, że testy funkcjonalne wymagają więcej zasobów i powodują rzeczywiste przekroczenia limitu czasu. Z tego powodu testy mogą zakończyć się niepowodzeniem, ponieważ procesor jest zajęty testami jednostkowymi. Wniosek: Jeśli to możliwe, wykonuj „ciężkie” testy oddzielnie od testów jednostkowych. 

Architektura zdarzeń mikroserwisów jest bardziej złożona niż monolit: zbieranie logów na dziesiątkach różnych maszyn nie jest zbyt wygodne. Wniosek: jeśli tworzysz mikroserwisy, od razu pomyśl o śledzeniu.

Nasze plany

Uruchomimy wewnętrzny moduł równoważący, moduł równoważenia protokołu IPv6, dodamy obsługę skryptów Kubernetes, będziemy kontynuować dzielenie naszych usług (obecnie dzielone są tylko węzeł sprawdzania kondycji i ctrl kontroli stanu), dodamy nowe kontrole stanu, a także wdrożymy inteligentną agregację kontroli. Rozważamy możliwość jeszcze bardziej uniezależnić nasze usługi – tak, aby komunikowały się ze sobą nie bezpośrednio, a za pomocą kolejki komunikatów. Niedawno w chmurze pojawiła się usługa kompatybilna z SQS Kolejka wiadomości Yandex.

Niedawno miało miejsce publiczne wydanie Yandex Load Balancer. Badać dokumentacja do serwisu, zarządzaj balanserami w dogodny dla Ciebie sposób i zwiększ odporność swoich projektów na awarie!

Źródło: www.habr.com

Dodaj komentarz