Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes
Ten artykuł pomoże Ci zrozumieć, jak działa równoważenie obciążenia w Kubernetes, co się dzieje podczas skalowania długotrwałych połączeń i dlaczego powinieneś rozważyć równoważenie po stronie klienta, jeśli używasz HTTP/2, gRPC, RSockets, AMQP lub innych długotrwałych protokołów . 

Trochę o tym, jak redystrybucja ruchu jest w Kubernetesie 

Kubernetes udostępnia dwie wygodne abstrakcje wdrażania aplikacji: usługi i wdrożenia.

Wdrożenia opisują, jak i ile kopii aplikacji powinno być uruchomionych w danym momencie. Każda aplikacja jest wdrażana jako Pod i ma przypisany adres IP.

Usługi mają podobne funkcje do modułu równoważenia obciążenia. Są przeznaczone do dystrybucji ruchu na wiele podów.

Zobaczmy jak to wygląda.

  1. Na poniższym schemacie możesz zobaczyć trzy instancje tej samej aplikacji i modułu równoważenia obciążenia:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Moduł równoważenia obciążenia nazywa się usługą i ma przypisany adres IP. Każde przychodzące żądanie jest przekierowywane do jednego z podów:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Scenariusz wdrożenia określa liczbę wystąpień aplikacji. Prawie nigdy nie będziesz musiał rozwijać się bezpośrednio pod:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  4. Każdemu podowi przypisany jest własny adres IP:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Warto myśleć o usługach jako o zbiorze adresów IP. Za każdym razem, gdy uzyskujesz dostęp do usługi, jeden z adresów IP jest wybierany z listy i używany jako adres docelowy.

To wygląda tak.

  1. Do usługi otrzymano żądanie curl 10.96.45.152:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Usługa jako miejsce docelowe wybiera jeden z trzech adresów podów:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Ruch jest przekierowywany do konkretnego podu:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Jeśli Twoja aplikacja składa się z frontendu i backendu, dla każdego z nich będziesz mieć zarówno usługę, jak i wdrożenie.

Kiedy frontend wysyła żądanie do backendu, nie musi dokładnie wiedzieć, ile podów obsługuje backend: może ich być jeden, dziesięć lub sto.

Ponadto frontend nie wie nic o adresach podów obsługujących backend.

Kiedy frontend wysyła żądanie do backendu, używa adresu IP usługi backendu, który się nie zmienia.

Oto jak to wygląda.

  1. Under 1 żąda wewnętrznego komponentu backendu. Zamiast wybierać konkretny backend, wysyła żądanie do usługi:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Usługa wybiera jeden z podów backendu jako adres docelowy:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Ruch przechodzi z Pod 1 do Pod 5, wybranego przez usługę:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  4. Under 1 nie wie dokładnie, ile podów takich jak under 5 jest ukrytych za usługą:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Ale w jaki sposób usługa dokładnie dystrybuuje żądania? Wygląda na to, że stosowane jest równoważenie okrężne? Rozwiążmy to. 

Bilansowanie w usługach Kubernetes

Usługi Kubernetes nie istnieją. Nie ma procesu dla usługi, któremu przypisano adres IP i port.

Można to sprawdzić logując się do dowolnego węzła w klastrze i uruchamiając komendę netstat -ntlp.

Nie będziesz nawet w stanie znaleźć adresu IP przydzielonego do usługi.

Adres IP usługi znajduje się w warstwie kontrolnej, w kontrolerze i jest zapisywany w bazie danych - itp. Z tego samego adresu korzysta inny komponent – ​​kube-proxy.
Kube-proxy otrzymuje listę adresów IP wszystkich usług i generuje zestaw reguł iptables na każdym węźle w klastrze.

Reguły te mówią: „Jeśli zobaczymy adres IP usługi, musimy zmodyfikować adres docelowy żądania i wysłać je do jednego z podów”.

Adres IP usługi jest używany jedynie jako punkt wejścia i nie jest obsługiwany przez żaden proces nasłuchujący tego adresu IP i portu.

Spójrzmy na to

  1. Rozważmy klaster trzech węzłów. Każdy węzeł ma strąki:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Wiązane strąki pomalowane na beżowo są częścią usługi. Ponieważ usługa nie istnieje jako proces, jest wyświetlana w kolorze szarym:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Pierwszy pod żąda usługi i musi udać się do jednego z powiązanych podów:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  4. Ale usługa nie istnieje, proces nie istnieje. Jak to działa?

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  5. Zanim żądanie opuści węzeł, przechodzi przez reguły iptables:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  6. Reguły iptables wiedzą, że usługa nie istnieje i zastępują jej adres IP jednym z adresów IP podów powiązanych z tą usługą:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  7. Żądanie otrzymuje prawidłowy adres IP jako adres docelowy i jest przetwarzane normalnie:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  8. W zależności od topologii sieci żądanie ostatecznie dociera do zasobnika:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Czy iptables może równoważyć obciążenie?

Nie, iptables służą do filtrowania i nie zostały zaprojektowane do równoważenia.

Można jednak napisać zestaw reguł, które będą działać podobnie pseudobalanser.

I właśnie to jest zaimplementowane w Kubernetesie.

Jeśli masz trzy pody, kube-proxy napisze następujące reguły:

  1. Wybierz pierwszy sub z prawdopodobieństwem 33%, w przeciwnym razie przejdź do następnej reguły.
  2. Wybierz drugą z prawdopodobieństwem 50%, w przeciwnym razie przejdź do następnej reguły.
  3. Wybierz trzeci pod.

W tym systemie każdy strąk jest wybierany z prawdopodobieństwem 33%.

Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Nie ma też gwarancji, że kapsuła 2 zostanie wybrana jako następna po kapsułie 1.

Operacja: iptables używa modułu statystycznego z losowym rozkładem. Zatem algorytm równoważenia opiera się na losowym wyborze.

Teraz, gdy już wiesz, jak działają usługi, przyjrzyjmy się bardziej interesującym scenariuszom usług.

Długotrwałe połączenia w Kubernetesie domyślnie nie są skalowane

Każde żądanie HTTP z frontendu do backendu jest obsługiwane przez oddzielne połączenie TCP, które jest otwierane i zamykane.

Jeśli frontend wysyła do backendu 100 żądań na sekundę, wówczas otwieranych i zamykanych jest 100 różnych połączeń TCP.

Możesz skrócić czas przetwarzania i obciążenia żądań, otwierając jedno połączenie TCP i używając go do wszystkich kolejnych żądań HTTP.

Protokół HTTP ma funkcję zwaną utrzymywaniem aktywności HTTP lub ponownym wykorzystaniem połączenia. W tym przypadku pojedyncze połączenie TCP służy do wysyłania i odbierania wielu żądań i odpowiedzi HTTP:

Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Ta funkcja nie jest domyślnie włączona: zarówno serwer, jak i klient muszą być odpowiednio skonfigurowane.

Sama konfiguracja jest prosta i dostępna dla większości języków programowania i środowisk.

Oto kilka linków do przykładów w różnych językach:

Co się stanie, jeśli użyjemy funkcji Keep-Alive w usłudze Kubernetes?
Załóżmy, że wsparcie zarówno frontendu, jak i backendu utrzymuje się przy życiu.

Mamy jedną kopię frontendu i trzy kopie backendu. Frontend wysyła pierwsze żądanie i otwiera połączenie TCP z backendem. Żądanie dociera do usługi, jeden z podów backendu jest wybierany jako adres docelowy. Backend wysyła odpowiedź, a frontend ją odbiera.

W przeciwieństwie do zwykłej sytuacji, w której połączenie TCP jest zamykane po otrzymaniu odpowiedzi, jest ono teraz otwarte dla dalszych żądań HTTP.

Co się stanie, jeśli frontend wyśle ​​więcej żądań do backendu?

Aby przekazać te żądania, zostanie użyte otwarte połączenie TCP. Wszystkie żądania trafią do tego samego backendu, do którego trafiło pierwsze żądanie.

Czy iptables nie powinien redystrybuować ruchu?

Nie w tym przypadku.

Kiedy tworzone jest połączenie TCP, przechodzi ono przez reguły iptables, które wybierają konkretny backend, do którego będzie kierowany ruch.

Ponieważ wszystkie kolejne żądania kierowane są do już otwartego połączenia TCP, reguły iptables nie są już wywoływane.

Zobaczmy jak to wygląda.

  1. Pierwszy pod wysyła żądanie do usługi:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Już wiesz, co będzie dalej. Usługa nie istnieje, ale istnieją reguły iptables, które przetworzą żądanie:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Jako adres docelowy zostanie wybrany jeden z podów backendu:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  4. Żądanie dociera do zasobnika. W tym momencie zostanie ustanowione trwałe połączenie TCP pomiędzy dwoma zasobnikami:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  5. Każde kolejne żądanie z pierwszego poda będzie przechodzić przez już nawiązane połączenie:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Rezultatem jest krótszy czas reakcji i większa przepustowość, ale tracisz możliwość skalowania backendu.

Nawet jeśli w backendzie masz dwa pody, przy stałym połączeniu, ruch zawsze będzie kierowany do jednego z nich.

Czy można to naprawić?

Ponieważ Kubernetes nie wie, jak zrównoważyć trwałe połączenia, to zadanie spada na Ciebie.

Usługi to zbiór adresów IP i portów zwanych punktami końcowymi.

Twoja aplikacja może uzyskać listę punktów końcowych z usługi i zdecydować, w jaki sposób dystrybuować żądania między nimi. Możesz otworzyć trwałe połączenie z każdym podem i zrównoważyć żądania między tymi połączeniami za pomocą działania okrężnego.

Lub zastosuj więcej złożone algorytmy równoważenia.

Kod po stronie klienta odpowiedzialny za równoważenie powinien kierować się następującą logiką:

  1. Pobierz listę punktów końcowych z usługi.
  2. Otwórz trwałe połączenie dla każdego punktu końcowego.
  3. Jeśli konieczne jest złożenie wniosku, użyj jednego z otwartych połączeń.
  4. Regularnie aktualizuj listę punktów końcowych, twórz nowe lub zamykaj stare, trwałe połączenia, jeśli lista ulegnie zmianie.

Tak to będzie wyglądać.

  1. Zamiast wysyłać żądanie do usługi przez pierwszy pod, możesz zrównoważyć żądania po stronie klienta:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  2. Musisz napisać kod, który zapyta, które pody są częścią usługi:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  3. Gdy już będziesz mieć listę, zapisz ją po stronie klienta i użyj jej do połączenia się z podami:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

  4. Jesteś odpowiedzialny za algorytm równoważenia obciążenia:

    Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Teraz pojawia się pytanie: czy ten problem dotyczy tylko utrzymywania aktywności protokołu HTTP?

Równoważenie obciążenia po stronie klienta

HTTP nie jest jedynym protokołem, który może wykorzystywać trwałe połączenia TCP.

Jeśli Twoja aplikacja korzysta z bazy danych, to połączenie TCP nie jest otwierane za każdym razem, gdy chcesz wykonać żądanie lub pobrać dokument z bazy danych. 

Zamiast tego otwierane i używane jest trwałe połączenie TCP z bazą danych.

Jeśli Twoja baza danych jest wdrożona na Kubernetesie i dostęp jest udostępniany w formie usługi, wówczas napotkasz te same problemy, które opisano w poprzedniej sekcji.

Jedna replika bazy danych będzie bardziej obciążona niż pozostałe. Kube-proxy i Kubernetes nie pomogą zrównoważyć połączeń. Musisz zadbać o zrównoważenie zapytań do bazy danych.

W zależności od biblioteki, której używasz do łączenia się z bazą danych, możesz mieć różne opcje rozwiązania tego problemu.

Poniżej znajduje się przykład dostępu do klastra bazy danych MySQL z poziomu Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Istnieje wiele innych protokołów korzystających z trwałych połączeń TCP:

  • WebSockets i zabezpieczone WebSockets
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Większość z tych protokołów powinieneś już znać.

Ale skoro te protokoły są tak popularne, dlaczego nie istnieje ustandaryzowane rozwiązanie równoważące? Dlaczego logika klienta musi się zmienić? Czy istnieje natywne rozwiązanie Kubernetes?

Kube-proxy i iptables zaprojektowano tak, aby obsługiwały najczęstsze przypadki użycia podczas wdrażania w Kubernetes. To dla wygody.

Jeśli korzystasz z usługi internetowej udostępniającej API REST, masz szczęście - w tym przypadku nie są używane trwałe połączenia TCP, możesz skorzystać z dowolnej usługi Kubernetes.

Kiedy jednak zaczniesz używać trwałych połączeń TCP, będziesz musiał dowiedzieć się, jak równomiernie rozłożyć obciążenie na backendy. Kubernetes nie zawiera gotowych rozwiązań w tym przypadku.

Z pewnością jednak istnieją opcje, które mogą pomóc.

Równoważenie długotrwałych połączeń w Kubernetesie

W Kubernetesie istnieją cztery typy usług:

  1. IP klastra
  2. Port węzła
  3. Load Balancer
  4. Bezgłowy

Pierwsze trzy usługi działają w oparciu o wirtualny adres IP, który służy kube-proxy do budowania reguł iptables. Jednak podstawową podstawą wszystkich usług jest służba bez głowy.

Z usługą headless nie jest powiązany żaden adres IP, a jedynie zapewnia mechanizm pobierania listy adresów IP i portów powiązanych z nią podów (punktów końcowych).

Wszystkie usługi opierają się na usłudze bezgłowej.

Usługa ClusterIP jest usługą bezgłową z pewnymi dodatkami: 

  1. Warstwa zarządzająca przydziela mu adres IP.
  2. Kube-proxy generuje niezbędne reguły iptables.

W ten sposób możesz zignorować kube-proxy i bezpośrednio użyć listy punktów końcowych uzyskanych z usługi headless w celu zrównoważenia obciążenia aplikacji.

Ale jak dodać podobną logikę do wszystkich aplikacji wdrażanych w klastrze?

Jeśli Twoja aplikacja jest już wdrożona, to zadanie może wydawać się niemożliwe. Istnieje jednak alternatywna opcja.

Service Mesh Ci pomoże

Prawdopodobnie już zauważyłeś, że strategia równoważenia obciążenia po stronie klienta jest dość standardowa.

Po uruchomieniu aplikacji:

  1. Pobiera listę adresów IP z usługi.
  2. Otwiera i utrzymuje pulę połączeń.
  3. Okresowo aktualizuje pulę, dodając lub usuwając punkty końcowe.

Gdy aplikacja chce wysłać żądanie, to:

  1. Wybiera dostępne połączenie przy użyciu jakiejś logiki (np. okrężnego).
  2. Wykonuje żądanie.

Te kroki działają zarówno w przypadku połączeń WebSockets, gRPC, jak i AMQP.

Możesz wydzielić tę logikę do osobnej biblioteki i używać jej w swoich aplikacjach.

Zamiast tego możesz jednak użyć siatek usług, takich jak Istio lub Linkerd.

Service Mesh rozszerza Twoją aplikację o proces, który:

  1. Automatycznie wyszukuje adresy IP usług.
  2. Testuje połączenia, takie jak WebSockets i gRPC.
  3. Równoważy żądania przy użyciu prawidłowego protokołu.

Service Mesh pomaga zarządzać ruchem w klastrze, ale wymaga dużych zasobów. Inne opcje obejmują korzystanie z bibliotek innych firm, takich jak Netflix Ribbon lub programowalnych serwerów proxy, takich jak Envoy.

Co się stanie, jeśli zignorujesz problemy z balansem?

Możesz zrezygnować z równoważenia obciążenia i nadal nie zauważyć żadnych zmian. Przyjrzyjmy się kilku scenariuszom pracy.

Jeśli masz więcej klientów niż serwerów, nie jest to taki duży problem.

Załóżmy, że pięciu klientów łączy się z dwoma serwerami. Nawet jeśli nie będzie równoważenia, używane będą oba serwery:

Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Połączenia mogą nie być równomiernie rozłożone: być może czterech klientów jest podłączonych do tego samego serwera, ale istnieje duża szansa, że ​​oba serwery będą używane.

Bardziej problematyczny jest scenariusz odwrotny.

Jeśli masz mniej klientów i więcej serwerów, Twoje zasoby mogą być niedostatecznie wykorzystane i pojawi się potencjalne wąskie gardło.

Załóżmy, że jest dwóch klientów i pięć serwerów. W najlepszym przypadku będą dwa stałe połączenia z dwoma z pięciu serwerów.

Pozostałe serwery będą bezczynne:

Równoważenie obciążenia i skalowanie długotrwałych połączeń w Kubernetes

Jeśli te dwa serwery nie są w stanie obsłużyć żądań klientów, skalowanie poziome nie pomoże.

wniosek

Usługi Kubernetes są zaprojektowane do pracy w większości standardowych scenariuszy aplikacji internetowych.

Jednak po rozpoczęciu pracy z protokołami aplikacji korzystającymi z trwałych połączeń TCP, takimi jak bazy danych, gRPC lub WebSockets, usługi nie są już odpowiednie. Kubernetes nie zapewnia wewnętrznych mechanizmów równoważenia trwałych połączeń TCP.

Oznacza to, że musisz pisać aplikacje z myślą o równoważeniu po stronie klienta.

Tłumaczenie przygotowane przez zespół Kubernetes aaS z Mail.ru.

Co jeszcze przeczytać na ten temat:

  1. Trzy poziomy autoskalowania w Kubernetesie i jak je efektywnie wykorzystać
  2. Kubernetes w duchu piractwa z szablonem do wdrożenia.
  3. Nasz kanał na Telegramie poświęcony transformacji cyfrowej.

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

Dodaj komentarz