Envoy. 1. Wprowadzenie

Witam! To krótki artykuł, odpowiadający na pytania: "co to jest envoy?", "po co jest potrzebny?" i "od czego zacząć?".

Co to jest?

Envoy to rozdzielacz L4-L7 napisany w C++, skoncentrowany na wysokiej wydajności i dostępności. Z jednej strony jest to w pewnym sensie odpowiednik nginx i haproxy, porównywalny z nimi pod względem wydajności. Z drugiej strony, jest bardziej dostosowany do architektury mikroserwisów i ma możliwości porównywalne z rozdzielaczami w javie i go, takimi jak zuul czy traefik.

Tabela porównawcza haproxy/nginx/envoy, która nie rości sobie prawa do absolutnej prawdy, ale daje ogólny obraz.

nginx
haproxy
envoy
traefik

gwiazdek na githubie
11.2k/mirror
1.1k/mirror
12.4k
27.6k

napisany w
C
C
C++
go

API
nie
socket only/push
dataplane/pull
pull

aktywny healthcheck
nie
tak
tak
tak

Open tracing
zewnętrzny plugin
nie
tak
tak

JWT
zewnętrzny plugin
nie
tak
nie

Rozszerzenie
Lua/C
Lua/C
Lua/C++
nie

Dlaczego

To młody projekt, brakuje w nim wielu rzeczy, a niektóre są na wczesnym etapie alfa. Ale envoy, w tym także dzięki swojej młodości, szybko się rozwija i już teraz ma wiele interesujących możliwości: dynamiczną konfigurację, wiele gotowych filtrów, prosty interfejs do pisania własnych filtrów.
Z tego wynikają obszary zastosowania, ale na początek 2 antywzorce:

  • Serwowanie statycznych treści.

Faktem jest, że w tej chwili w envoy nie ma wsparcia dla caching. Ludzie z google próbują to naprawić.. Idea polega na jednokrotnym wdrożeniu w na envoy wszystkich niuansów (zoo nagłówków) zgodności z RFC, a dla konkretnych wdrożeń stworzenie interfejsu. Ale na razie to nawet nie jest alfa, architektura jest w dyskusji, PR otwarty (w momencie pisania artykułu PR został połączony, ale ten punkt wciąż jest aktualny).

A póki co użyj nginx do statycznych treści.

  • Statyczna konfiguracja.

Można jej używać, ale envoy został stworzony nie w tym celu. Możliwości w statycznej konfiguracji nie zostaną ujawnione. Jest wiele rzeczy:

Edytując konfigurację w yaml, będziesz popełniać błędy, przeklinać programistów za zawiłość i myśleć, że konfiguracje nginx/haproxy, mniej zorganizowane, ale bardziej zwięzłe. W tym jest sedno. Konfiguracja Nginx i Haproxy została stworzona do ręcznej edycji, a w przypadku envoy do generacji z kodu. Cała konfiguracja jest opisana w protobuf, generując ją z plików proto znacznie trudniej pomylić.

Scenariusze canary, podejmowanie decyzji b/g i wiele innych, są dobrze realizowane tylko w dynamicznej konfiguracji. Nie mówię, że nie można tego zrobić w statyce, wszyscy to robimy. Ale do tego trzeba się obłożyć ograniczeniami, w każdym z rozdzielaczy, w envoy tym także.

Zadania, w których Envoy jest niezastąpiony:

  • Równoważenie ruchu w złożonych i dynamicznych systemach. Wchodzi w to mesh usług, ale to nie tylko to.
  • Konieczność funkcjonalności rozproszonego śledzenia, złożonej autoryzacji lub innych, które są dostępne w envoy standardzie lub łatwo realizowane, a w nginx/haproxy trzeba korzystać z lua i wątpliwych wtyczek.

Obydwa te elementy należy zapewnić, aby osiągnąć wysoką wydajność.

Jak to działa

Envoy jest dystrybuowany w binarkach tylko jako obraz docker. Obraz zawiera już przykład statycznej konfiguracji. Ale interesuje nas tylko dla zrozumienia struktury.

envoy.yaml statyczna konfiguracja

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "\/"
                route:
                  host_rewrite: www.google.com
                  cluster: service_google
          http_filters:
          - name: envoy.router
  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # Skomentuj następną linijkę, aby przetestować na sieciach v6
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_google
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.google.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: www.google.com

Dynamiczna konfiguracja

Jakie problemy rozwiązujemy? Nie można po prostu zrestartować konfiguracji równoważnika obciążenia pod obciążeniem, pojawią się "niewielkie" problemy:

  • Weryfikacja konfiguracji.

Konfiguracja może być duża, może być bardzo duża, jeśli przeładowujemy ją całą na raz, szanse, że gdzieś wystąpi błąd, wzrastają.

  • Długoterminowe połączenia.

Przy inicjowaniu nowego listenera, trzeba zadbać o połączenia działające na starym, jeśli zmiany zachodzą często i są długoterminowe połączenia, trzeba będzie szukać kompromisu. Witaj, ingress kubernetes na nginx.

  • Aktywne health checki.

Jeśli mamy aktywne health checki, warto je wszystkie sprawdzić w nowej konfiguracji przed wysłaniem ruchu. Jeśli jest dużo upstreamów, zajmie to czas. Witaj, haproxy.

Jak to się rozwiązuje w envoy, ładując konfigurację dynamicznie, według modelu pul, można ją podzielić na oddzielne części i nie ponownie inicjalizować tej części, która się nie zmienia. Na przykład listener, którego ponowna inicjalizacja jest kosztowna, zmienia się rzadko.

Konfiguracja envoy (z powyższego pliku) ma następujące encje:

  • listener — listener przypisany do określonego IP/portu
  • virtual host — wirtualny host po nazwie domeny
  • trasa — zasada równoważenia
  • cluster — grupa upstreamów z parametrami równoważenia
  • endpoint — adres instancji upstreamu

Każdą z tych encji oraz niektóre inne można wypełnić dynamicznie, w tym celu w konfiguracji podaje się adres usługi, z której będzie uzyskana konfiguracja. Usługa może być REST lub gRPC, preferowane jest użycie gRPC.

Usługi nazywają się odpowiednio: LDS, VHDS, RDS, CDS i EDS. Można łączyć statyczną i dynamiczną konfigurację, z ograniczeniem, że zasób dynamiczny nie może być wskazany w statycznym.

Dla większości zadań wystarczy wdrożyć ostatnie trzy usługi, nazywają się ADS (Aggregated Discovery Service), dla java i go jest gotowa implementacja gRPC dataplane, w której wystarczy tylko wypełnić obiekty ze swojego źródła.

Konfiguracja nabiera następującego kształtu:

envoy.yaml dynamiczna konfiguracja

dynamic_resources:
  ads_config:
    api_type: GRPC
    grpc_services:
      envoy_grpc:
        cluster_name: xds_clr
  cds_config:
    ads: {}
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          rds:
            route_config_name: local_route
            config_source:
              ads: {}
          http_filters:
          - name: envoy.router
  clusters:
  - name: xds_clr
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: xds_clr
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: xds
                port_value: 6565

Po uruchomieniu envoy z tą konfiguracją, połączy się z control-plane i spróbuje zażądać konfiguracji RDS, CDS i EDS. Jak odbywa się proces interakcji opisano tutaj.

W skrócie, envoy wysyła żądanie, podając typ żądanego zasobu, wersję i parametry węzła. W odpowiedzi otrzymuje zasób i wersję, jeśli na control-plane wersja się nie zmieniła, nie odpowiada.
Istnieją 4 opcje interakcji:

  • Jeden strumień gRPC dla wszystkich typów zasobów, przesyłany jest pełny stan zasobu.
  • Oddzielne strumienie, pełny stan.
  • Jeden strumień, inkrementalny stan.
  • Oddzielne strumienie, inkrementalny stan.

Inkrementalny xDS pozwala na zmniejszenie ruchu pomiędzy control-plane a envoy, co jest istotne dla dużych konfiguracji. Jednakże komplikuje to interakcję, w żądaniu przesyłana jest lista zasobów do odsubskrybowania i subskrybowania.

W naszym przykładzie używamy ADS — jeden strumień dla RDS, CDS, EDS i tryb nieinkrementalny. Aby włączyć tryb inkrementalny, należy określić api_type: DELTA_GRPC

Ponieważ w żądaniu są parametry węzła, możemy na control-plane przesyłać różne zasoby dla różnych instancji envoy, co jest wygodne dla budowy service mesh.

Podgrzewanie

Na envoy przy starcie lub po otrzymaniu nowej konfiguracji z control-plane, uruchamiany jest proces podgrzewania zasobów. Jest on podzielony na podgrzewanie słuchacza i podgrzewanie klastra. Pierwsze uruchamiane jest przy zmianach w RDS/LDS, drugie przy CDS/EDS. Oznacza to, że jeśli zmieniają się tylko upstreamy, słuchacz nie jest ponownie tworzony.

W procesie podgrzewania, oczekiwane są zależne zasoby od control-plane przez czas oczekiwania. Jeśli czas oczekiwania upłynie, inicjalizacja nie będzie udana, nowy słuchacz nie zacznie nasłuchiwać portu.
Kolejność inicjalizacji: EDS, CDS, aktywne kontrole zdrowia, RDS, LDS. Przy włączonych aktywnych kontrolach zdrowia, ruch trafi do upstreamu tylko po jednym udanym teście zdrowia.

Jeśli słuchacz został ponownie utworzony, stary przechodzi w stan DRAIN i zostanie usunięty po zamknięciu wszystkich połączeń lub upływie czasu oczekiwania --drain-time-s, domyślnie 10 minut.

Ciąg dalszy nastąpi.

Ź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