Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową

Witaj, Habro! Jestem Artem Karamyshev, szef zespołu administracyjnego systemu Rozwiązania chmurowe Mail.Ru (MCS). W zeszłym roku wprowadziliśmy na rynek wiele nowych produktów. Chcieliśmy mieć pewność, że usługi API będą łatwo skalowalne, odporne na awarie i gotowe na szybki wzrost obciążenia użytkowników. Nasza platforma jest zaimplementowana na OpenStack i chcę powiedzieć, jakie problemy z odpornością na awarie komponentów musieliśmy rozwiązać, aby uzyskać system odporny na awarie. Myślę, że będzie to interesujące dla tych, którzy również tworzą produkty na OpenStack.

Ogólna odporność platformy na uszkodzenia składa się z odporności jej komponentów. Będziemy więc stopniowo przechodzić przez wszystkie poziomy, na których zidentyfikowaliśmy ryzyka i je zamknęliśmy.

Wersja wideo tej historii, której pierwotnym źródłem była relacja z konferencji Uptime day 4, zorganizowanej przez ITSumma, możesz zobaczyć na kanale YouTube społeczności Uptime.

Odporność architektury fizycznej

Część publiczna chmury MCS zlokalizowana jest obecnie w dwóch centrach danych Tier III, pomiędzy nimi znajduje się własne ciemne włókno, zarezerwowane na poziomie fizycznym różnymi trasami, o przepustowości 200 Gbit/s. Poziom III zapewnia niezbędny poziom odporności na awarie infrastruktury fizycznej.

Ciemne włókno jest zarezerwowane zarówno na poziomie fizycznym, jak i logicznym. Proces rezerwacji kanałów był iteracyjny, pojawiały się problemy, a my stale usprawniamy komunikację pomiędzy centrami danych.

Na przykład niedawno, podczas pracy w studni w pobliżu jednego z centrów danych, koparka uszkodziła rurę, w której znajdował się zarówno główny, jak i zapasowy kabel optyczny. Nasz odporny na awarie kanał komunikacji z centrum danych okazał się w pewnym momencie podatny na ataki, w studni. W związku z tym straciliśmy część infrastruktury. Wyciągnęliśmy wnioski i podjęliśmy szereg działań, m.in. zamontowaliśmy dodatkową optykę w sąsiedniej studni.

W centrach danych znajdują się punkty obecności dostawców usług komunikacyjnych, do których rozsyłamy nasze prefiksy za pośrednictwem protokołu BGP. Dla każdego kierunku sieci wybierana jest najlepsza metryka, która pozwala zapewnić różnym klientom najlepszą jakość połączenia. Jeśli komunikacja przez jednego dostawcę ulegnie awarii, przebudowujemy nasz routing poprzez dostępnych dostawców.

Jeśli dostawca zawiedzie, automatycznie przełączamy się na innego. W przypadku awarii jednego z data center, w drugim data center posiadamy lustrzaną kopię naszych usług, które przejmują całe obciążenie.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Odporność infrastruktury fizycznej

Czego używamy do odporności na błędy na poziomie aplikacji

Nasza usługa opiera się na wielu komponentach typu open source.

ExaBGP to usługa realizująca szereg funkcji z wykorzystaniem protokołu routingu dynamicznego opartego na protokole BGP. Aktywnie wykorzystujemy je do reklamowania naszych adresów IP znajdujących się na białej liście, za pośrednictwem których użytkownicy uzyskują dostęp do interfejsu API.

HAPROXY to moduł równoważenia dużego obciążenia, który umożliwia konfigurowanie bardzo elastycznych reguł równoważenia ruchu na różnych poziomach modelu OSI. Używamy go do balansowania przed wszystkimi usługami: bazami danych, brokerami wiadomości, usługami API, usługami sieciowymi, naszymi wewnętrznymi projektami - wszystko stoi za HAProxy.

Aplikacja API — aplikacja internetowa napisana w języku Python, za pomocą której użytkownik zarządza swoją infrastrukturą i usługami.

Aplikacja pracownika (zwany dalej po prostu workerem) - w usługach OpenStack jest to demon infrastruktury pozwalający na rozgłaszanie poleceń API do infrastruktury. Na przykład utworzenie dysku odbywa się w procesie roboczym, a żądanie utworzenia następuje w interfejsie API aplikacji.

Standardowa architektura aplikacji OpenStack

Większość usług opracowanych dla OpenStack stara się kierować jednym paradygmatem. Usługa składa się zwykle z 2 części: API i procesów roboczych (executorów backendu). Z reguły API to aplikacja WSGI w Pythonie, która jest uruchamiana albo jako niezależny proces (demon), albo przy użyciu gotowego serwera WWW Nginx lub Apache. Interfejs API przetwarza żądanie użytkownika i przekazuje dalsze instrukcje do aplikacji roboczej w celu wykonania. Transfer odbywa się za pomocą brokera komunikatów, zwykle RabbitMQ, pozostałe są słabo obsługiwane. Gdy wiadomości dotrą do brokera, są przetwarzane przez procesy robocze i, jeśli to konieczne, zwracają odpowiedź.

Ten paradygmat obejmuje izolowane wspólne punkty awarii: RabbitMQ i bazę danych. Ale RabbitMQ jest izolowany w ramach jednej usługi i teoretycznie może być indywidualny dla każdej usługi. Dlatego w MCS maksymalnie rozdzielamy te usługi, dla każdego indywidualnego projektu tworzymy osobną bazę danych, oddzielny RabbitMQ. Takie podejście jest dobre, ponieważ w razie wypadku w niektórych wrażliwych punktach psuje się nie cała usługa, ale tylko jej część.

Liczba aplikacji roboczych jest nieograniczona, więc interfejs API można łatwo skalować poziomo za modułami równoważącymi, aby zwiększyć wydajność i odporność na awarie.

Niektóre usługi wymagają koordynacji w ramach usługi, gdy między interfejsami API a procesami roboczymi występują złożone operacje sekwencyjne. W tym przypadku wykorzystywane jest jedno centrum koordynacyjne, system klastrowy taki jak Redis, Memcache itp., który pozwala jednemu pracownikowi powiedzieć drugiemu, że przydzielono mu to zadanie („proszę nie brać tego”). Używamy itp. Z reguły pracownicy aktywnie komunikują się z bazą danych, zapisują i czytają z niej informacje. Używamy mariadb jako bazy danych, która znajduje się w klastrze multimaster.

Ta klasyczna pojedyncza usługa jest zorganizowana w sposób ogólnie przyjęty w OpenStack. Można go uznać za system zamknięty, dla którego metody skalowania i odporności na uszkodzenia są dość oczywiste. Na przykład, aby zapewnić odporność na błędy API, wystarczy umieścić przed nimi balanser. Skalowanie pracowników osiąga się poprzez zwiększanie ich liczby.

Słabym punktem całego schematu jest RabbitMQ i MariaDB. Ich architektura zasługuje na osobny artykuł, w tym artykule chcę się skupić na odporności API na błędy.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Architektura aplikacji Openstack. Równoważenie i odporność na awarie platformy chmurowej

Uodpornienie modułu równoważącego HAProxy na błędy przy użyciu ExaBGP

Aby nasze interfejsy API były skalowalne, szybkie i odporne na błędy, umieściliśmy przed nimi moduł równoważenia obciążenia. Wybraliśmy HAProxy. Moim zdaniem posiada wszystkie niezbędne cechy do naszego zadania: równoważenie na kilku poziomach OSI, interfejs zarządzania, elastyczność i skalowalność, dużą liczbę metod równoważenia, obsługę tabel sesyjnych.

Pierwszym problemem, który należało rozwiązać, była odporność samej wyważarki na uszkodzenia. Samo zainstalowanie wyważarki również powoduje awarię: wyważarka psuje się, a usługa ulega awarii. Aby temu zapobiec, użyliśmy HAProxy w połączeniu z ExaBGP.

ExaBGP pozwala na wdrożenie mechanizmu sprawdzania stanu usługi. Wykorzystaliśmy ten mechanizm, aby sprawdzić funkcjonalność HAProxy i w przypadku problemów wyłączyć usługę HAProxy z poziomu BGP.

Schemat ExaBGP+HAProxy

  1. Instalujemy niezbędne oprogramowanie ExaBGP i HAProxy na trzech serwerach.
  2. Na każdym serwerze tworzymy interfejs pętli zwrotnej.
  3. Na wszystkich trzech serwerach przypisujemy do tego interfejsu ten sam biały adres IP.
  4. Biały adres IP jest reklamowany w Internecie za pośrednictwem ExaBGP.

Odporność na awarie osiąga się poprzez ogłaszanie tego samego adresu IP na wszystkich trzech serwerach. Z punktu widzenia sieci ten sam adres jest dostępny z trzech różnych kolejnych przeskoków. Router widzi trzy identyczne trasy, wybiera z nich najwyższy priorytet na podstawie własnej metryki (zwykle jest to ta sama opcja), a ruch kierowany jest tylko do jednego z serwerów.

W przypadku problemów z działaniem HAProxy lub awarii serwera, ExaBGP przestaje ogłaszać trasę, a ruch płynnie przełącza się na inny serwer.

W ten sposób uzyskaliśmy odporność na uszkodzenia wyważarki.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Tolerancja błędów balanserów HAProxy

Schemat okazał się niedoskonały: nauczyliśmy się rezerwować HAProxy, ale nie nauczyliśmy się rozkładać obciążenia w ramach usług. Dlatego trochę rozszerzyliśmy ten schemat: przeszliśmy do balansowania pomiędzy kilkoma białymi adresami IP.

Równoważenie w oparciu o DNS plus BGP

Kwestia równoważenia obciążenia dla naszego HAProxy pozostaje nierozwiązana. Można to jednak rozwiązać w prosty sposób, tak jak zrobiliśmy to tutaj.

Aby zrównoważyć trzy serwery, będziesz potrzebować 3 białych adresów IP i starego, dobrego DNS. Każdy z tych adresów jest określany w interfejsie pętli zwrotnej każdego HAProxy i ogłaszany w Internecie.

W OpenStack do zarządzania zasobami wykorzystywany jest katalog usług, który określa API punktu końcowego konkretnej usługi. W tym katalogu rejestrujemy nazwę domeny - public.infra.mail.ru, która jest rozpoznawana przez DNS za pomocą trzech różnych adresów IP. W rezultacie otrzymujemy rozkład obciążenia pomiędzy trzema adresami poprzez DNS.

Ponieważ jednak ogłaszając białe adresy IP, nie kontrolujemy priorytetów wyboru serwerów, nie jest to jeszcze zbilansowane. Zwykle tylko jeden serwer zostanie wybrany na podstawie starszeństwa adresu IP, a pozostałe dwa będą bezczynne, ponieważ w protokole BGP nie określono żadnych metryk.

Zaczęliśmy wysyłać trasy przez ExaBGP z różnymi metrykami. Każdy moduł równoważący reklamuje wszystkie trzy białe adresy IP, ale jeden z nich, główny dla tego modułu równoważącego, jest reklamowany z minimalną metryką. Tak więc, gdy wszystkie trzy moduły równoważące działają, wywołania z pierwszego adresu IP kierowane są do pierwszego modułu równoważącego, wywołania z drugiego do drugiego, a wywołania z trzeciego do trzeciego.

Co się stanie, gdy jeden z balanserów upadnie? Jeśli którykolwiek moduł równoważący zawiedzie, jego główny adres jest nadal ogłaszany z pozostałych dwóch, a ruch jest redystrybuowany pomiędzy nimi. W ten sposób dajemy użytkownikowi kilka adresów IP jednocześnie za pośrednictwem DNS. Równoważąc za pomocą DNS i różnych metryk, uzyskujemy równomierny rozkład obciążenia na wszystkie trzy moduły równoważące. Jednocześnie nie tracimy tolerancji na błędy.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Równoważenie HAProxy w oparciu o DNS + BGP

Interakcja pomiędzy ExaBGP i HAProxy

Wdrożyliśmy więc tolerancję na awarie w przypadku opuszczenia serwera, polegającą na zatrzymywaniu ogłaszania tras. Jednak HAProxy może zostać zamknięty z innych powodów niż awaria serwera: błędy administracyjne, awarie usługi. W tym przypadku również chcemy usunąć zepsuty balanser spod ładunku i potrzebujemy innego mechanizmu.

Dlatego rozszerzając poprzedni schemat, wdrożyliśmy heartbeat pomiędzy ExaBGP i HAProxy. Jest to programowa implementacja interakcji pomiędzy ExaBGP i HAProxy, podczas której ExaBGP używa niestandardowych skryptów do sprawdzania stanu aplikacji.

Aby to zrobić, musisz skonfigurować moduł sprawdzający stan w konfiguracji ExaBGP, który może sprawdzić stan HAProxy. W naszym przypadku backend kondycji skonfigurowaliśmy w HAProxy, a od strony ExaBGP sprawdzamy prostym żądaniem GET. Jeśli ogłoszenie przestanie się pojawiać, oznacza to, że HAProxy najprawdopodobniej nie działa i nie ma potrzeby go reklamować.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Kontrola stanu HAProxy

HAProxy Peers: synchronizacja sesji

Następną rzeczą do zrobienia była synchronizacja sesji. Podczas pracy za pośrednictwem rozproszonych balanserów trudno jest zorganizować przechowywanie informacji o sesjach klientów. Jednak HAProxy jest jednym z niewielu systemów równoważących, które mogą to zrobić dzięki funkcjonalności Peers - możliwości przesyłania tabel sesji pomiędzy różnymi procesami HAProxy.

Istnieją różne metody równoważenia: proste, takie jak okrężne, i przedłużane, gdy sesja klienta zostaje zapamiętana i za każdym razem trafia on na ten sam serwer co poprzednio. Chcieliśmy wdrożyć drugą opcję.

HAProxy używa tablic samoprzylepnych do zapisywania sesji klientów tego mechanizmu. Zapisują oryginalny adres IP klienta, wybrany adres docelowy (backend) i niektóre informacje o usłudze. Zazwyczaj tablice stick służą do przechowywania pary źródłowy adres IP + docelowy adres IP, co jest szczególnie przydatne w aplikacjach, które nie mogą przenieść kontekstu sesji użytkownika podczas przełączania na inny moduł równoważący, na przykład w trybie równoważenia RoundRobin.

Jeśli tabela sztyftów zostanie nauczona poruszania się pomiędzy różnymi procesami HAProxy (pomiędzy którymi następuje równoważenie), nasze moduły równoważące będą mogły pracować z jedną pulą tabel sztyftów. Umożliwi to płynne przełączenie sieci klienta w przypadku awarii jednego z balanserów; praca z sesjami klienta będzie kontynuowana na tych samych backendach, które zostały wybrane wcześniej.

Do poprawnego działania musi zostać rozwiązany problem źródłowego adresu IP balansera, z którego została nawiązana sesja. W naszym przypadku jest to adres dynamiczny w interfejsie pętli zwrotnej.

Prawidłową pracę rówieśników osiąga się tylko pod pewnymi warunkami. Oznacza to, że limity czasu protokołu TCP muszą być wystarczająco duże lub przełączanie musi być wystarczająco szybkie, aby sesja TCP nie miała czasu na zakończenie. Pozwala jednak na płynne przełączanie.

W IaaS mamy usługę zbudowaną w tej samej technologii. Ten Load Balancer jako usługa dla OpenStack, która nazywa się Oktawia. Opiera się na dwóch procesach HAProxy i początkowo obejmuje obsługę peerów. Okazali się znakomici w tej usłudze.

Rysunek schematycznie przedstawia ruch tabel równorzędnych pomiędzy trzema instancjami HAProxy. Proponowana jest konfiguracja pokazująca, jak można to skonfigurować:

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
HAProxy Peers (synchronizacja sesji)

Jeśli wdrażasz ten sam schemat, należy dokładnie przetestować jego działanie. Nie jest prawdą, że będzie działać w ten sam sposób w 100% przypadków. Ale przynajmniej nie stracisz tabelek, gdy będziesz musiał zapamiętać źródłowy adres IP klienta.

Ograniczenie liczby jednoczesnych żądań od tego samego klienta

Wszelkie usługi, które są publicznie dostępne, w tym nasze interfejsy API, mogą być przedmiotem lawin żądań. Przyczyny mogą być zupełnie inne, od błędów użytkownika po ataki ukierunkowane. Okresowo jesteśmy poddawani atakom DDoS według adresów IP. Klienci często popełniają błędy w swoich skryptach i wysyłają nam mini-DDoS.

Tak czy inaczej należy zapewnić dodatkową ochronę. Oczywistym rozwiązaniem jest ograniczenie liczby żądań API i nie marnowanie czasu procesora na przetwarzanie złośliwych żądań.

Aby wdrożyć takie ograniczenia, stosujemy limity stawek zorganizowane w oparciu o HAProxy, korzystając z tych samych tabel sztyftów. Konfigurowanie limitów jest dość proste i pozwala na ograniczenie użytkownika liczbą żądań do API. Algorytm zapamiętuje źródłowy adres IP, z którego wysyłane są żądania i ogranicza liczbę jednoczesnych żądań od jednego użytkownika. Oczywiście obliczyliśmy średni profil obciążenia API dla każdej usługi i ustaliliśmy limit ≈ 10-krotności tej wartości. W dalszym ciągu uważnie monitorujemy sytuację i trzymamy rękę na pulsie.

Jak to wygląda w praktyce? Mamy klientów, którzy cały czas korzystają z naszych interfejsów API automatycznego skalowania. Rano tworzą około dwustu do trzystu maszyn wirtualnych i usuwają je wieczorem. W przypadku OpenStack utworzenie maszyny wirtualnej, także z usługami PaaS, wymaga co najmniej 1000 żądań API, ponieważ interakcja pomiędzy usługami odbywa się również poprzez API.

Takie przeniesienie zadań powoduje dość duże obciążenie. Oceniliśmy to obciążenie, zebraliśmy dzienne szczyty, zwiększyliśmy je dziesięciokrotnie i to stało się naszym limitem stawki. Trzymamy rękę na pulsie. Często widzimy boty i skanery, które próbują na nas spojrzeć, aby sprawdzić, czy mamy jakieś skrypty CGA, które można uruchomić, aktywnie je wycinamy.

Jak zaktualizować bazę kodu tak, aby użytkownicy tego nie zauważyli

Wdrażamy również odporność na błędy na poziomie procesów wdrażania kodu. Podczas wdrażania mogą wystąpić usterki, ale ich wpływ na dostępność usług można zminimalizować.

Stale aktualizujemy nasze usługi i musimy zapewnić aktualizację bazy kodu bez wpływu na użytkowników. Udało nam się rozwiązać ten problem wykorzystując możliwości zarządzania HAProxy i wdrożenie Graceful Shutdown w naszych usługach.

Aby rozwiązać ten problem, konieczne było zapewnienie kontroli balansera i „prawidłowego” wyłączenia usług:

  • W przypadku HAProxy kontrola odbywa się poprzez plik statystyk, który w zasadzie jest gniazdem i jest zdefiniowany w konfiguracji HAProxy. Możesz wysyłać do niego polecenia przez stdio. Ale naszym głównym narzędziem do kontroli konfiguracji jest ansible, więc ma wbudowany moduł do zarządzania HAProxy. Z którego aktywnie korzystamy.
  • Większość naszych usług API i silnika obsługuje technologie bezpiecznego zamykania: podczas zamykania czekają na zakończenie bieżącego zadania, niezależnie od tego, czy jest to żądanie HTTP, czy jakieś zadanie serwisowe. To samo dzieje się z pracownikiem. Zna wszystkie zadania, które wykonuje i kończy, gdy wszystko pomyślnie wykona.

Dzięki tym dwóm punktom bezpieczny algorytm naszego wdrożenia wygląda następująco.

  1. Deweloper składa nowy pakiet kodu (dla nas jest to RPM), testuje go w środowisku deweloperskim, testuje na etapie i pozostawia w repozytorium etapu.
  2. Deweloper wyznacza zadanie wdrożenia, podając najbardziej szczegółowy opis „artefaktów”: wersję nowego pakietu, opis nowej funkcjonalności i, jeśli to konieczne, inne szczegóły dotyczące wdrożenia.
  3. Administrator systemu rozpoczyna aktualizację. Uruchamia podręcznik Ansible, który z kolei wykonuje następujące czynności:
    • Pobiera pakiet z repozytorium stage i używa go do aktualizacji wersji pakietu w repozytorium produktów.
    • Kompiluje listę backendów zaktualizowanej usługi.
    • Zamyka pierwszą usługę do aktualizacji w HAProxy i czeka na zakończenie działania jej procesów. Dzięki płynnemu zamknięciu jesteśmy pewni, że wszystkie bieżące żądania klientów zostaną pomyślnie zrealizowane.
    • Po całkowitym zatrzymaniu interfejsu API i procesów roboczych oraz wyłączeniu HAProxy kod jest aktualizowany.
    • Ansible obsługuje usługi.
    • Dla każdej usługi pobierane są pewne „dojścia”, które przeprowadzają testy jednostkowe na szeregu wcześniej zdefiniowanych kluczowych testów. Następuje podstawowa kontrola nowego kodu.
    • Jeśli w poprzednim kroku nie znaleziono żadnych błędów, backend zostanie aktywowany.
    • Przejdźmy do następnego backendu.
  4. Po zaktualizowaniu wszystkich backendów uruchamiane są testy funkcjonalne. Jeśli ich brakuje, programista sprawdza każdą nową funkcjonalność, którą stworzył.

To kończy wdrażanie.

Jak w platformie Mail.ru Cloud Solutions zaimplementowano odporną na awarie architekturę internetową
Cykl aktualizacji usługi

Ten schemat nie działałby, gdybyśmy nie mieli jednej reguły. Wspieramy w walce zarówno starą, jak i nową wersję. Z góry, na etapie tworzenia oprogramowania, zakłada się, że nawet jeśli nastąpią zmiany w bazie danych usługi, nie złamią one poprzedniego kodu. W rezultacie baza kodu jest stopniowo aktualizowana.

wniosek

Dzieląc się własnymi przemyśleniami na temat odpornej na błędy architektury WEB, chciałbym jeszcze raz zwrócić uwagę na jej kluczowe punkty:

  • odporność na uszkodzenia fizyczne;
  • odporność na awarie sieci (balancery, BGP);
  • odporność na błędy wykorzystywanego i opracowanego oprogramowania.

Stabilny czas pracy dla wszystkich!

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

Dodaj komentarz