Envoy. 1. Einführung

Willkommen! Dies ist ein kleiner Artikel, der auf die Fragen antwortet: "Was ist Envoy?", "Wozu wird es benötigt?" und "Wie fängt man an?".

Was ist das

Envoy ist ein L4-L7 Lastverteiler, der in C++ geschrieben wurde und auf hohe Leistung und Verfügbarkeit ausgelegt ist. Einerseits ist er in gewisser Weise ein Pendant zu nginx und haproxy und vergleichbar mit ihnen in Bezug auf die Leistung. Andererseits ist er stärker auf mikroserviceorientierte Architekturen ausgerichtet und bietet Funktionen, die mit denen von Lastverteilern in Java und Go wie zuul oder traefik vergleichbar sind.

Eine Vergleichstabelle von haproxy/nginx/envoy, die keinen Anspruch auf absolute Wahrheit erhebt, aber einen Überblick bietet.

nginx
haproxy
envoy
traefik

Sternen auf GitHub
11.2k/mirror
1.1k/mirror
12.4k
27.6k

geschrieben in
C
C
C++
go

API
nein
socket only/push
dataplane/pull
pull

aktiver Healthcheck
nein
ja
ja
ja

Open tracing
externes Plugin
nein
ja
ja

JWT
externes Plugin
nein
ja
nein

Erweiterung
Lua/C
Lua/C
Lua/C++
nein

Warum

Dies ist ein junges Projekt, in dem noch viele Funktionen fehlen, und einiges befindet sich in einer frühen Alpha-Phase. Aber envoy, auch dank seiner Jugend, entwickelt es sich schnell weiter und bietet bereits viele interessante Möglichkeiten: dynamische Konfiguration, viele fertige Filter und eine einfache Schnittstelle zum Schreiben eigener Filter.
Das ergibt Anwendungsbereiche, aber zunächst zwei Antipattern:

  • Bereitstellung von Statischer.

Das Problem ist, dass es derzeit in envoy keine Unterstützung für Caching gibt. Das Team von Google versucht, dies zu beheben.Die Idee ist, die gesamten Details (den Zoo von Headern) einmal zu implementieren, um sie dann in für spezifische Implementierungen verwendbare Schnittstellen zu bringen. Aber bisher ist das noch nicht einmal Alpha, die Architektur wird diskutiert. envoy Es ist offen (während ich diesen Artikel geschrieben habe, wurde der PR gemergt, aber dieser Punkt ist immer noch aktuell). PR Verwenden Sie vorerst nginx für statische Inhalte.

Statische Konfiguration.

  • Sie kann verwendet werden, aber

wurde nicht dafür erstellt. Die Möglichkeiten in der statischen Konfiguration werden nicht ausgeschöpft. Es gibt viele Punkte: envoy Wenn Sie die Konfiguration im YAML bearbeiten, werden Sie Fehler machen, die Entwickler für ihre Wortfülle verfluchen und denken, dass die Konfigurationen von nginx/haproxy, obwohl sie weniger strukturiert sind, prägnanter sind. Das ist der Punkt. Die Konfiguration von Nginx und Haproxy wurde für die Bearbeitung von Hand erstellt, während

für die Generierung aus Code gedacht ist. Die gesamte Konfiguration ist in envoy , indem man sie nach Proto-Dateien generiert, ist es viel schwieriger, einen Fehler zu machen. protobufSzenarien für Canary- und B/G-Deployments und vieles mehr werden nur in der dynamischen Konfiguration vernünftig umgesetzt. Ich sage nicht, dass dies nicht in der statischen Konfiguration möglich ist, wir tun das alles. Aber dafür müssen viele Umwege in jedem der Lastverteiler, auch in

mitgenommen werden. envoy Aufgaben, bei denen Envoy unverzichtbar ist:

Aufgaben, in denen Envoy unersetzlich ist:

  • Lastenverteilung im komplexen und dynamischen Systemen. Hierzu gehört der Service Mesh, aber es ist nicht unbedingt nur dieser.
  • Die Notwendigkeit von Funktionen für verteiltes Tracing, komplexe Autorisierung oder ähnliches, das in envoy Out-of-the-Box verfügbar ist oder bequem implementiert werden kann, während in nginx/haproxy lua und fragwürdige Plugins nötig sind.

Beides sollte, falls notwendig, eine hohe Leistung gewährleisten.

Die DANE-Spezifikation ist in

Envoy wird in Binaries nur als Docker-Image verbreitet. Im Image befindet sich bereits ein Beispiel für eine statische Konfiguration. Doch wir interessieren uns nur für dessen Struktur.

envoy.yaml statische Konfiguration

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
    # Kommentiere die folgende Zeile aus, um in Netzwerken der Version 6 zu testen
    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

Dynamische Konfiguration

Welches Problem versuchen wir zu lösen? Man kann nicht einfach die Konfiguration des Load Balancers unter Last neu laden; es treten "geringe" Probleme auf:

  • Validierung der Konfiguration.

Die Konfiguration kann groß sein, sogar sehr groß. Wenn wir sie alle auf einmal neu laden, steigen die Chancen, dass irgendwo ein Fehler auftritt.

  • Langfristige Verbindungen.

Bei der Initialisierung eines neuen Listeners muss auf die Verbindungen geachtet werden, die auf dem alten laufen. Wenn Änderungen häufig auftreten und es langfristige Verbindungen gibt, muss ein Kompromiss gefunden werden. Hallo, Kubernetes Ingress auf Nginx.

  • Aktive Health Checks.

Wenn wir aktive Health Checks haben, sollten wir alle im neuen Konfig überprüfen, bevor wir Traffic senden. Wenn es viele Upstreams gibt, erfordert das Zeit. Hallo, HAProxy.

Wie wird das in envoy, indem die Konfiguration dynamisch geladen wird, kann man das Model in separate Teile aufteilen und den Teil, der nicht geändert wurde, nicht neu initialisieren. Zum Beispiel ist der Listener, der teuer neu zu initialisieren ist, seltenen Änderungen unterworfen.

Konfiguration envoy (aus der obigen Datei) hat folgende Entitäten:

  • Listener — Listener, der an einer bestimmten IP/Port hängt
  • virtueller Host — virtueller Host nach Domainnamen
  • Route — Regel für das Lastenbalancing
  • Cluster — Gruppe von Upstreams mit Lastenbalancing-Parametern
  • endpoint — Adresse der Instanz des Upstreams

Jede dieser Entitäten sowie einige andere können dynamisch ausgefüllt werden, dafür wird in der Konfiguration die Adresse des Dienstes angegeben, von dem die Konfiguration bezogen wird. Der Dienst kann REST oder gRPC sein, wobei gRPC bevorzugt verwendet werden sollte.

Die Dienste werden entsprechend genannt: LDS, VHDS, RDS, CDS und EDS. Man kann statische und dynamische Konfiguration kombinieren, wobei zu beachten ist, dass dynamische Ressourcen nicht in der statischen Konfiguration angegeben werden dürfen.

Für die meisten Aufgaben reicht es aus, die letzten drei Dienste zu implementieren, die ADS (Aggregated Discovery Service) genannt werden, für java und es gibt bereits eine fertige Implementierung von gRPC Dataplane in Go, bei der es ausreicht, nur die Objekte aus der eigenen Quelle auszufüllen.

Die Konfiguration sieht folgendermaßen aus:

envoy.yaml dynamische Konfiguration

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

Beim Start wird Folgendes angezeigt: envoy Mit dieser Konfiguration wird eine Verbindung zur Control-Plane hergestellt und versucht, die RDS-, CDS- und EDS-Konfiguration anzufordern. Wie der Interaktionsprozess abläuft, wird beschrieben hier.

Kurz gesagt, envoy sendet eine Anfrage mit Angabe des Typs der angeforderten Ressource, der Version und der Parameter des Knotens. Als Antwort erhält er die Ressource und die Version. Wenn sich die Version auf der Control-Plane nicht geändert hat, antwortet sie nicht.
Es gibt 4 Varianten der Interaktion:

  • Ein gRPC-Stream für alle Ressourcentypen, der den vollständigen Zustand der Ressource übermittelt.
  • Getrennte Streams, vollständiger Zustand.
  • Ein Stream, inkrementeller Zustand.
  • Getrennte Streams, inkrementeller Zustand.

Inkrementales xDS reduziert den Datenverkehr zwischen Control-Plane und envoy, was für große Konfigurationen relevant ist. Es kompliziert jedoch die Interaktion, da in der Anfrage eine Liste von Ressourcen zum Abonnieren und Abbestellen übergeben wird.

In unserem Beispiel verwenden wir ADS – einen Stream für RDS, CDS, EDS und nicht inkrementellen Modus. Um den inkrementellen Modus zu aktivieren, muss angegeben werden api_type: DELTA_GRPC

Da in der Anfrage Knotendaten vorhanden sind, können wir über die Control-Plane unterschiedliche Ressourcen für verschiedene Instanzen senden envoy, was für den Aufbau eines Service Mesh hilfreich ist.

Warmup

Auf envoy beim Start oder beim Erhalt einer neuen Konfiguration von der Control-Plane wird der Ressourcen-Warmup-Prozess gestartet. Dieser ist unterteilt in Listener-Warmup und Cluster-Warmup. Der erste wird bei Änderungen in RDS/LDS gestartet, der zweite bei CDS/EDS. Das bedeutet, dass, wenn nur Upstreams geändert werden, der Listener nicht neu erstellt wird.

Während des Aufwärmvorgangs werden abhängige Ressourcen von der Control-Plane für die Dauer des Timeouts erwartet. Wenn das Timeout überschritten wird, wird die Initialisierung nicht erfolgreich sein, und der neue Listener beginnt nicht, den Port zu überwachen.
Reihenfolge der Initialisierung: EDS, CDS, aktiver Gesundheitscheck, RDS, LDS. Bei aktivierten aktiven Gesundheitschecks fließt der Verkehr zu den Upstreams erst nach einem erfolgreichen Gesundheitscheck.

Wenn der Listener neu erstellt wurde, wechselt der alte in den Zustand DRAIN und wird entfernt, nachdem alle Verbindungen geschlossen oder das Timeout abgelaufen ist. --drain-time-s, standardmäßig 10 Minuten.

Fortsetzung folgt.

Quelle: habr.com

60GB SSD 8Gb DDR4