Kontenery, mikrousługi i siatki serwisowe

W Internecie mnóstwo artykułów większą w siatkach serwisowych (service mesh), i oto kolejna. Hurra! Ale po co? Po to, że chcę przedstawić swoją opinię, że lepiej by było, gdyby siatki serwisowe pojawiły się 10 lat temu, zanim powstały platformy kontenerowe, takie jak Docker i Kubernetes. Nie twierdzę, że moja opinia jest lepsza lub gorsza od innych, ale ponieważ siatki serwisowe to dość skomplikowane twory, wielość punktów widzenia pomoże lepiej je zrozumieć.

Opowiem o platformie dotCloud, która została zbudowana na ponad setce mikrousług i wspierała tysiące aplikacji w kontenerach. Wyjaśnię problemy, z jakimi się spotkaliśmy podczas jej rozwoju i uruchamiania, i jak siatki serwisowe mogłyby pomóc (lub nie mogły).

Historia dotCloud

Już pisałem o historii dotCloud i wyborze architektury dla tej platformy, ale mało mówiłem o poziomie sieciowym. Jeśli nie chcesz zagłębiać się w lekturę poprzedniego artykułu o dotCloud, to krótko mówiąc: jest to platforma jako usługa PaaS, która pozwala klientom uruchamiać szeroki wachlarz aplikacji (Java, PHP, Python...), z obsługą szerokiego zakresu usług danych (MongoDB, MySQL, Redis…) i mechanizmem roboczym jak w Heroku: przesyłasz swój kod na platformę, ona buduje obrazy kontenerów i wdraża je.

Opowiem, jak kierowano ruchem na platformie dotCloud. Nie dlatego, że było to szczególnie świetne (chociaż jak na swoje czasy system działał całkiem dobrze!), ale przede wszystkim dlatego, że przy użyciu nowoczesnych narzędzi taki projekt może zostać zrealizowany w krótkim czasie przez skromny zespół, jeśli potrzebują sposobu na routowanie ruchu między wieloma mikrousługami lub różnymi aplikacjami. Można więc porównać opcje: co się dzieje, gdy wszystko zaprojektujesz samodzielnie lub skorzystasz z istniejącej siatki serwisowej. Standardowy wybór: zrobić samemu lub kupić.

Routowanie ruchu dla aplikacji hostowanych

Aplikacje na dotCloud mogą udostępniać punkty końcowe HTTP i TCP.

Punkty końcowe HTTP są dynamicznie dodawane do konfiguracji klastra load balancerów Hipache. To przypomina to, co dziś robią zasoby Ingress w Kubernetes i load balancerów takich jak Traefik.

Klienci łączą się z punktami końcowymi HTTP przez odpowiednie domeny, pod warunkiem, że nazwa domeny wskazuje na load balancery dotCloud. Nic specjalnego.

Punkty końcowe TCP są związane z numerem portu, który następnie przechodzi do wszystkich kontenerów tego stosu za pośrednictwem zmiennych środowiskowych.

Klienci mogą łączyć się z punktami końcowymi TCP, używając odpowiedniej nazwy hosta (czegoś w stylu gateway-X.dotcloud.com) oraz numeru portu.

Ta nazwa hosta jest rozwiązywana na klaster serwerów „nats“ (nie ma to związku z NATS), które będą kierować przychodzące połączenia TCP do odpowiedniego kontenera (lub, w przypadku usług z równoważeniem obciążenia, do odpowiednich kontenerów).

Jeśli znasz Kubernetes, prawdopodobnie przypomni ci to usługi. NodePort.

Na platformie dotCloud nie było odpowiednika usług. ClusterIP: dla uproszczenia dostęp do usług odbywał się w ten sam sposób zarówno wewnątrz, jak i na zewnątrz platformy.

Wszystko było zorganizowane dość prosto: początkowe realizacje sieci routingu HTTP i TCP, prawdopodobnie liczące zaledwie kilka setek linijek Pythona. Proste (powiedziałbym, naiwne) algorytmy, które były rozwijane w miarę wzrostu platformy i pojawiania się dodatkowych wymagań.

Obszerne refaktoryzacje istniejącego kodu nie były wymagane. W szczególności, aplikacje zgodne z zasadą 12-faktorową mogą bezpośrednio korzystać z adresu uzyskanego za pośrednictwem zmiennych środowiskowych.

Czym to się różni od współczesnej siatki usług?

Ograniczona widoczność. W ogóle nie mieliśmy żadnych metryk dla siatki routingu TCP. Jeśli chodzi o routing HTTP, w późniejszych wersjach pojawiły się szczegółowe metryki HTTP z kodami błędów i czasem odpowiedzi, ale współczesne siatki usług idą jeszcze dalej, zapewniając integrację z systemami zbierania metryk, takimi jak Prometheus, na przykład.

Widoczność jest ważna nie tylko z operacyjnego punktu widzenia (aby pomóc w rozwiązywaniu problemów), ale także przy wprowadzaniu nowych funkcji. Mowa o bezpiecznym wdrożeniu w systemie blue-green i wdrożeniu canary.

Efektywność routingu była również ograniczona. W sieci routingu dotCloud cały ruch musiał przechodzić przez klaster dedykowanych węzłów routingu. Oznaczało to potencjalne przekraczanie kilku granic AZ (stref dostępności) oraz znaczne zwiększenie opóźnienia. Pamiętam, jak rozwiązywałem problemy z kodem, który wykonywał ponad sto zapytań SQL na stronę i dla każdego zapytania otwierał nowe połączenie z serwerem SQL. Przy lokalnym uruchomieniu strona ładowała się błyskawicznie, ale w dotCloud ładowanie trwało kilka sekund, ponieważ każde połączenie TCP (i następujące zapytania SQL) wymagało dziesiątek milisekund. W tym konkretnym przypadku problem rozwiązano za pomocą stałych połączeń.

Nowoczesne service mesh lepiej radzą sobie z takimi problemami. Przede wszystkim sprawdzają, czy połączenia są routowane w źródle. Strumień logiczny pozostaje ten sam: klient → mesh → serwis, ale teraz mesh działa lokalnie, a nie na zdalnych węzłach, więc połączenie klient → mesh jest lokalne i bardzo szybkie (mikrosekundy zamiast milisekund).

Nowoczesne service mesh wdrażają również bardziej inteligentne algorytmy równoważenia obciążenia. Monitorując dostępność backendów, mogą skierować więcej ruchu do szybszych backendów, co prowadzi do zwiększenia ogólnej wydajności.

Bezpieczeństwo jest również lepsze. Sieć routingu dotCloud działała całkowicie na EC2 Classic i nie szyfrowała ruchu (zakładając, że jeśli ktoś zdołał zainstalować sniffer na ruchu sieciowym EC2, masz już poważne problemy). Nowoczesne service mesh transparentnie chronią nasz cały ruch, na przykład poprzez wzajemną autoryzację TLS i następne szyfrowanie.

Routing ruchu dla usług platformy

Dobrze, omówiliśmy ruch między aplikacjami, ale co z samą platformą dotCloud?

Sama platforma składała się z około stu mikroserwisów odpowiedzialnych za różne funkcje. Niektóre przyjmowały zapytania od innych, a niektóre były pracownikami w tle, które łączyły się z innymi usługami, ale same nie przyjmowały połączeń. W każdym razie każda usługa musi znać końcowe adresy, do których należy się połączyć.

Wiele usług na wysokim poziomie może korzystać z opisanej powyżej sieci routingu. W rzeczywistości wiele z ponad stu mikrousług dotCloud zostało wdrożonych jako zwykłe aplikacje na samej platformie dotCloud. Jednak niewielka liczba niskopoziomowych usług (w szczególności tych, które realizują tę sieć routingu) potrzebowała czegoś prostszego, z mniejszymi zależnościami (ponieważ nie mogły być zależne od samych siebie – stara dobra problem kurczaka i jajka).

Te niskopoziomowe, ważne usługi zostały wdrożone przez uruchamianie kontenerów bezpośrednio na kilku kluczowych węzłach. Nie wykorzystywano standardowych usług platformy: kompozytora, harmonogramu i runnera. Jeśli chcesz porównać to z nowoczesnymi platformami kontenerowymi, przypomina to uruchomienie płaszczyzny zarządzania z docker run bezpośrednio na węzłach, zamiast delegowania zadania Kubernetes. To całkiem przypomina koncepcję statycznych modułów (podów), które wykorzystuje kubeadm lub bootkube do uruchamiania autonomicznego klastra.

Te usługi były eksponowane w prosty i surowy sposób: w pliku YAML wymienione były ich nazwy i adresy; a każdy klient musiał pobrać kopię tego pliku YAML do wdrożenia.

Z jednej strony jest to niezwykle niezawodne, ponieważ nie wymaga wsparcia dla zewnętrznego magazynu kluczy/wartości, takiego jak Zookeeper (pamiętaj, że w tamtym czasie jeszcze nie istniało etcd ani Consul). Z drugiej strony utrudniało to przenoszenie usług. Za każdym razem, gdy dochodziło do przeniesienia, wszyscy klienci musieli otrzymać zaktualizowany plik YAML (a potencjalnie zrestartować). Niezbyt wygodne!

W późniejszym czasie zaczęliśmy wdrażać nowy schemat, w którym każdy klient łączył się z lokalnym serwerem proxy. Zamiast adresu i portu wystarczyło mu znać tylko numer portu usługi i łączyć się przez localhost. Lokalny serwer proxy obsługuje to połączenie i przekazuje je do faktycznego serwera. Teraz przy przenoszeniu backendu na inną maszynę lub skalowaniu, zamiast aktualizować wszystkich klientów, trzeba zaktualizować tylko te lokalne serwery proxy; a ponowne uruchomienie nie jest już wymagane.

(Planned was also to encapsulate traffic in TLS connections and add another proxy server on the receiving side, as well as verify TLS certificates without involving the receiving service, which is configured to accept connections only on localhost).

This is very similar to SmartStack from Airbnb, but a significant difference is that SmartStack is implemented and deployed in production, whereas the internal routing system of dotCloud was shelved when dotCloud turned into Docker.

I personally consider SmartStack one of the precursors to systems like Istio, Linkerd, and Consul Connect, because they all follow a common pattern:

  • Launching a proxy on each node.
  • Clients connect to the proxy.
  • The control plane updates the proxy configuration when backend changes occur.
  • … Profit!

Modern implementation of a service mesh

If we need to implement such a mesh today, we can use similar principles. For example, set up an internal DNS zone, mapping service names to addresses in the space 127.0.0.0/8. Then launch HAProxy on each node of the cluster, accepting connections at each service address (in this subnet 127.0.0.0/8) and redirecting/balancing the load to the corresponding backends. HAProxy configuration can be managed confd, allowing backend information to be stored in etcd or Consul and automatically pushing updated configuration to HAProxy when needed.

This is roughly how Istio works! But with some differences:

  • It uses Envoy Proxy instead of HAProxy.
  • It retains backend configuration through the Kubernetes API instead of etcd or Consul.
  • Services are assigned addresses in the internal subnet (Kubernetes ClusterIP addresses) instead of 127.0.0.0/8.
  • It has an additional component (Citadel) for adding mutual TLS authentication between clients and servers.
  • Supports new features such as circuit breaking, distributed tracing, canary deployments, and more.

Let's briefly review some differences.

Envoy Proxy

The Envoy Proxy was developed by Lyft [a competitor to Uber in the taxi market - translator's note]. It is largely similar to other proxies (e.g., HAProxy, Nginx, Traefik…), but Lyft created their own because they needed features lacking in other proxies, and it seemed more reasonable to create a new one than to extend the existing.

Envoy może działać samodzielnie. Jeśli mam konkretna usługę, która powinna łączyć się z innymi usługami, mogę skonfigurować ją do łączenia z Envoy, a następnie dynamicznie konfigurować i przestawiać Envoy z lokalizacjami innych usług, zyskując wiele cennych funkcji, takich jak monitorowanie. Zamiast korzystać z niestandardowej biblioteki klienckiej lub integrować śledzenie wywołań w kodzie, kierujemy ruch do Envoy, który zbiera dla nas metryki.

Ale Envoy jest również w stanie działać jako płaszczyzna danych (data plane) dla service mesh. Oznacza to, że dla danego service mesh Envoy jest konfigurowany płaszczyzną zarządzania (control plane).

Płaszczyzna zarządzania

W płaszczyźnie zarządzania Istio polega na API Kubernetes. Nie różni się to znacząco od korzystania z confd, który opiera się na etcd lub Consul do przeglądania zestawu kluczy w pamięci danych. Istio, poprzez Kubernetes API, przegląda zestaw zasobów Kubernetes.

Przy okazji: osobiście uważam to za przydatne opisanie API Kubernetes, które mówi:

Serwer API Kubernetes to "głupi serwer", który oferuje przechowywanie, zarządzanie wersjami, weryfikację, aktualizację i semantykę zasobów API.

Istio zostało zaprojektowane do pracy z Kubernetes; a jeśli chcesz go używać poza Kubernetes, musisz uruchomić egzemplarz serwera API Kubernetes (i pomocniczej usługi etcd).

Adresy usług

Istio polega na adresach ClusterIP przydzielanych przez Kubernetes, dlatego usługi Istio otrzymują wewnętrzny adres (nie w zakresie 127.0.0.0/8).

Ruch do adresu ClusterIP dla konkretnej usługi w klastrze Kubernetes bez Istio jest przechwytywany przez kube-proxy i kierowany na backend tego proxy. Jeśli interesują cię szczegóły techniczne, kube-proxy ustawia reguły iptables (lub load balancery IPVS, w zależności od konfiguracji), aby przepisać adresy IP docelowe połączeń idących do adresu ClusterIP.

Po zainstalowaniu Istio w klastrze Kubernetes nic się nie zmienia, dopóki nie zostanie jawnie włączone dla danego konsumenta lub nawet całej przestrzeni nazw, przez wprowadzenie kontenera sidecar do niestandardowych podów. Ten kontener uruchomi egzemplarz Envoy i ustawi szereg reguł iptables do przechwytywania ruchu idącego do innych usług i przekierowywania go na Envoy.

Integracja z Kubernetes DNS oznacza, że nasz kod może łączyć się po nazwie usługi i wszystko "po prostu działa". Innymi słowy, nasz kod wysyła zapytania typu http://api/v1/users/4242, wtedy api wymyśla zapytanie na 10.97.105.48, zasady iptables przechwytują połączenia z 10.97.105.48 i przekierowują je do lokalnego proxy Envoy, a to lokalne proxy skieruje zapytanie do faktycznego backendu API. Uff!

Dodatkowe uzupełnienia

Istio zapewnia również szyfrowanie end-to-end i autoryzację za pomocą mTLS (mutual TLS). Odpowiada za to komponent o nazwie Citadel.

Jest też komponent Mixer, który Envoy może zażądać do każdej zapytania, aby podjąć specjalną decyzję o tym zapytaniu w zależności od różnych czynników, takich jak nagłówki, obciążenie backendu itp... (nie martw się: istnieje wiele narzędzi, aby zapewnić działanie Mixera, a nawet jeśli on zawiedzie, Envoy nadal będzie działać jako proxy).

I, oczywiście, wspomnieliśmy o śledzeniu: Envoy zbiera ogromną liczbę metryk, zapewniając jednocześnie rozproszoną trasowanie. W architekturze mikrousług, jeśli jedno zapytanie API musi przejść przez mikrousługi A, B, C i D, to przy wejściu do systemu rozproszone śledzenie doda do zapytania unikalny identyfikator i zachowa ten identyfikator przez wszystkie subzapytania do tych mikrousług, umożliwiając rejestrowanie wszystkich powiązanych wywołań, ich opóźnień itp.

Opracować lub kupić

Istio ma reputację złożonego systemu. W przeciwieństwie do tego, budowanie siatki routingu, którą opisałem na początku tego wpisu, jest stosunkowo proste z użyciem istniejących narzędzi. Więc, czy jest sens tworzyć własny service mesh zamiast tego?

Jeśli mamy skromne potrzeby (nie potrzebujemy śledzenia, przerywacza łańcucha i innych szczegółów), nasuwają się myśli o opracowaniu własnego narzędzia. Ale jeśli korzystamy z Kubernetes, może to nawet nie być konieczne, ponieważ Kubernetes już zapewnia podstawowe narzędzia do odkrywania usług i równoważenia obciążenia.

Ale jeśli mamy zaawansowane wymagania, zakup service mesh wydaje się znacznie lepszą opcją. (Nie zawsze chodzi dokładnie o "zakup", ponieważ Istio jest dostępne z otwartym kodem źródłowym, ale i tak musimy zainwestować czas inżynieryjny, aby zrozumieć jego działanie, wdrożyć i zarządzać nim).

Co wybrać: Istio, Linkerd czy Consul Connect?

Jak na razie mówiliśmy tylko o Istio, ale to nie jest jedyny serwis mesh. Popularną alternatywą jest Linkerd, a jest jeszcze Consul Connect.

Co wybrać?

Szczerze mówiąc, nie wiem. W tej chwili nie uważam się za wystarczająco kompetentnego, aby odpowiedzieć na to pytanie. Istnieje kilka interesujących artykułów porównań tych narzędzi, a nawet benchmarków..

Jednym z obiecujących podejść jest użycie narzędzia takie jak SuperGloo. Wprowadza ono warstwę abstrakcji w celu uproszczenia i ujednolicenia API oferowanych przez serwisy mesh. Zamiast uczyć się konkretnych (moim zdaniem stosunkowo skomplikowanych) API różnych serwisów mesh, możemy korzystać z prostszych konstrukcji SuperGloo — i łatwo przełączać się z jednego na drugi, jakbyśmy mieli pośredni format konfiguracji opisujący interfejsy HTTP i backendy, zdolny generować rzeczywistą konfigurację dla Nginx, HAProxy, Traefik, Apache…

Trochę eksperymentowałem z Istio i SuperGloo, a w następnym artykule chcę pokazać, jak dodać Istio lub Linkerd do istniejącego klastra przy użyciu SuperGloo, i jak dobrze ostatni poradzi sobie ze swoją pracą, to znaczy, czy pozwoli na przełączanie się z jednego serwisu mesh na drugi bez przepisania konfiguracji.

Ź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