Praktycznie każda udana aplikacja biznesowa prędzej czy później dochodzi do etapu, w którym konieczne jest poziome skalowanie. W wielu przypadkach wystarczy uruchomić nowy egzemplarz i zmniejszyć średnie obciążenie. Jednak zdarzają się i mniej trywialne przypadki, w których musimy zapewnić, aby różne węzły znały się nawzajem i starannie rozdzielały obciążenie robocze.

Tak się szczęśliwie złożyło, że erlang, który wybraliśmy za przyjemną składnię i hype wokół niego, ma pierwszorzędne . Teoretycznie to brzmi całkowicie trywialnie:
Przesyłanie wiadomości między procesami na różnych węzłach, a także między referencjami i monitorami jest przezroczyste […]
W praktyce wszystko jest trochę bardziej skomplikowane. Rozproszony erlang został zaprojektowany, gdy „kontener” oznaczał tak dużą metalową skrzynkę do przewozu, a „docker” był po prostu synonimem portowego robotnika. W IP4 było wiele nieużywanych adresów, w przerwach sieciowych zazwyczaj winne były szczury przegryzające kable, a średni czas pracy systemu produkcyjnego mierzył się w dziesięcioleciach.
Teraz wszyscy jesteśmy niewyobrażalnie samowystarczalni, spakowani i uruchamiamy rozproszony erlang w środowisku, gdzie dynamiczne adresy IP są przydzielane według zasady wielkiej losowości, a węzły mogą pojawiać się i znikać na życzenie lewą piętą planistyczną. Aby uniknąć lawiny szablonowego kodu w każdym projekcie uruchamiającym rozproszony erlang, aby poradzić sobie z wrogim środowiskiem, potrzebna jest pomoc.
Uwaga: wiem, że jest . Jest naprawdę świetny, ma ponad tysiąc gwiazdek, autor znany jest w społeczności i tak dalej. Jeśli wystarczą Ci oferowane przez ten pakiet metody tworzenia i utrzymywania klastra – cieszę się za Ciebie. Niestety, ja potrzebuję znacznie więcej. Chcę zarządzać konfiguracją w szczegółach i nie być obojętnym widzem w teatrze przebudowy klastra.
Wymagania
Czego osobiście potrzebowałem, to biblioteka, która przejmie zarządzanie klastrem i będzie miała następujące właściwości:
- przezroczysta praca zarówno z twardo zakodowaną listą węzłów, jak i z dynamicznym wykrywaniem poprzez usługi erlang;
- wielofunkcyjny callback przy każdej zmianie topologii (węzeł tam, węzeł tu, niestabilność sieci, podziały);
- Przezroczysty interfejs do uruchamiania klastra z długimi i krótkimi nazwami, jak również z
:nonode@nohost; - Wsparcie dla Dockera z pudełka, bez potrzeby pisania kodu infrastrukturalnego.
Oznacza to, że po przetestowaniu aplikacji lokalnie w :nonode@nohost, lub w sztucznie rozproszonej atmosferze przy pomocy , chcę po prostu uruchomić docker-compose up --scale my_app=3 i zobaczyć, jak wykonuje trzy instancje w Dockerze bez jakichkolwiek zmian w kodzie. Chcę również, aby aplikacje zależne, takie jak mnesia — gdy topologia się zmienia, dynamika klastra przebudowywała się na żywo bez jakiegokolwiek dodatkowego bodźca ze strony aplikacji.
Cloister nie została pomyślana jako biblioteka, która potrafi wszystko: od wsparcia klastra po parzenie kawy. To nie jest srebrna kula, która stara się objąć wszystkie możliwe przypadki, ani akademickie, kompleksowe rozwiązanie w sensie, w którym teoretycy od CS współczują temu terminowi. Ta biblioteka została zaprojektowana w bardzo konkretnym celu, ale ma wykonać swoje ograniczone zadanie idealnie. Celem tym jest zapewnienie pełnej przejrzystości między lokalnym środowiskiem deweloperskim a rozproszonym, elastycznym otoczeniem pełnym wrogich kontenerów.
Wybrane podejście
Cloister zakłada uruchamianie jako aplikacji, chociaż doświadczeni użytkownicy mogą pracować z budową i konserwacją klastra ręcznie, bezpośrednio uruchamiając Cloister.Manager w drzewie nadzorców docelowej aplikacji.
Podczas uruchamiania jako aplikacji, biblioteka polega na config, skąd odczytuje następujące podstawowe wartości:
config :cloister,
otp_app: :my_app,
sentry: :"cloister.local", # lub ~w|n1@foo n2@bar|a
consensus: 3, # liczba węzłów do uwzględnienia
# klaster jest uruchomiony
listener: MyApp.Listener # listener, który będzie wywoływany, gdy
# pierścień został zmienionyPowyższe parametry oznaczają dosłownie to, że Cloister jest używane do aplikacji OTP :my_app, korzysta z odkrywania usług erlang do połączenia węzłów, co najmniej trzech, oraz MyApp.Listener moduł (implementujący ) jest zaprojektowany, aby otrzymywać powiadomienia o zmianach w topologii. Szczegółowy opis pełnej konfiguracji można znaleźć w .
Przy takiej konfiguracji, aplikacja Cloister będzie , odkładając proces uruchamiania głównej aplikacji do osiągnięcia konsensusu (trzy węzły podłączone i połączone, jak w powyższym przykładzie). To daje głównej aplikacji możliwość założenia, że gdy się uruchomi, klaster już istnieje. Przy każdej zmianie topologii (będzie ich wiele, ponieważ węzły uruchamiają się nie całkowicie synchronicznie), uruchamiany będzie handler . W większości przypadków wykonujemy akcję, gdy otrzymujemy wiadomość ze stanem %Cloister.Monitor{status: :up}, co oznacza: „cześć, klaster zbudowany”.
W większości przypadków ustalenie consensus: 3 jest optymalne, ponieważ nawet jeśli oczekujemy, że podłączy się więcej węzłów, callback będzie przechodził przez status: :rehashing → status: :up przy każdym nowo dodanym lub usuniętym węźle.
Uruchamiając w trybie deweloperskim, wystarczy ustawić consensus: 1 i Cloister radosnym skokiem przejdzie przez oczekiwanie na zbudowanie klastra, widząc :nonode@nohost, lub :node@host, lub :node@host.domain — w zależności od tego, jak skonfigurowano węzeł (:none | :shortnames | :longnames).
Zarządzanie rozproszonymi aplikacjami
Rozproszone aplikacje nie działają w próżni, zazwyczaj obejmują również rozproszone zależności, takie jak mnesia. Łatwo jest nimi zarządzać w ramach tej samej reakcji na zdarzenie on_state_change/2. Oto, na przykład, szczegółowy opis, jak na żywo skonfigurować mnesia w .
Główną zaletą używania Cloister jest to, że wykonuje wszystkie wymagane operacje przebudowy klastra po zmianie topologii pod zamknięciem. Aplikacja uruchamia się po prostu w już przygotowanym rozproszonym środowisku, ze wszystkimi podłączonymi węzłami, niezależnie od tego, czy znamy adresy IP i w związku z tym nazwy węzłów z wyprzedzeniem, czy były one dynamicznie przypisane/zmienione. Nie wymaga to żadnych specjalnych ustawień konfiguracyjnych Dockera, a z perspektywy dewelopera aplikacji nie ma żadnej różnicy między uruchamianiem w rozproszonym środowisku a lokalnym na :nonode@nohost. Więcej informacji na ten temat można znaleźć w .
Pomimo że złożona obsługa zmian topologii jest możliwa poprzez własną implementację MyApp.Listener, zawsze mogą wystąpić przypadki graniczne, kiedy te ograniczenia biblioteki i przyjęte podejście do konfiguracji okażą się kamieniem węgielnym na drodze do wdrożenia. To w porządku, po prostu weź pod uwagę to, co zostało wcześniej wspomniane libcluster, który jest bardziej uniwersalny, lub nawet samodzielnie obsłużyć klaster niskiego poziomu. Celem tej biblioteki kodu nie jest pokrycie wszystkich możliwych scenariuszy, ale wykorzystanie najczęściej występującego scenariusza bez zbędnych problemów i kłopotliwego kopiowania i wklejania.
Uwaga: w tym miejscu w oryginale znajdowała się fraza „Happy clustering!”, a Yandex, który mi tłumaczy (nie będę przecież przeszukiwać słowników), zaproponował mi wariant „Szczęśliwego klastrowania!”. Lepszego tłumaczenia, szczególnie w świetle obecnej sytuacji geopolitycznej – i trudno wymyślić.
Źródło: habr.com
