Envoy. 1. Einleitung

Willkommen! Dies ist ein kurzer Artikel, der auf die Fragen beantwortet: "Was ist Envoy?", "Wozu ist es gut?" und "Wie fange ich an?".

Was ist das

Envoy ist ein L4-L7 Load Balancer, der in C++ entwickelt wurde und auf hohe Leistung und Verfügbarkeit ausgerichtet ist. Einerseits ist es gewissermaßen eine Alternative zu Nginx und HAProxy und vergleichbar mit diesen in Bezug auf die Leistung. Andererseits ist es stärker auf eine Microservices-Architektur ausgerichtet und bietet Funktionen, die mit Load Balancern in Java und Go, wie Zuul oder Traefik, vergleichbar sind.

Eine Vergleichstabelle von HAProxy/Nginx/Envoy, die keinen Anspruch auf absolute Richtigkeit erhebt, aber einen allgemeinen Überblick gibt.

nginx
HAProxy
Envoy
traefik

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

geschrieben in
C
C
C++
go

API
nein
nur Socket/push
Dataplane/pull
pull

aktives Healthcheck
nein
ja
ja
ja

Open Tracing
externes Plugin
nein
ja
ja

(JSON Web Token) ist ein Webstandard, der definiert, wie Benutzerdaten in verschlüsselter Form im JSON-Format übermittelt werden.
externes Plugin
nein
ja
nein

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

Warum

Dies ist ein junges Projekt, es fehlt viel, und vieles befindet sich in einer frühen Alpha-Version. Aber Envoy, unter anderem aufgrund seiner Jugend, entwickelt es sich schnell weiter und bietet bereits jetzt viele interessante Möglichkeiten: dynamische Konfiguration, viele fertige Filter, eine einfache Schnittstelle zum Schreiben eigener Filter.
Das ergibt Anwendungsbereiche, aber zunächst zwei Antipattern:

  • Aushändigen von Statischer.

Fakt ist, dass derzeit in Envoy Caching wird nicht unterstützt. Die Google-Jungs versuchen das. behoben werden kann,Die Idee ist, dies einmalig in Gang zu setzen. Envoy Die ganzen Feinheiten (Header-Dschungel) der RFC-Konformität und für spezifische Implementierungen eine Schnittstelle zu schaffen. Aber bis jetzt ist das noch nicht einmal alpha, die Architektur ist in Diskussion. PR Es ist offen (gerade als ich den Artikel schrieb, wurde der PR gemerged, aber dieser Punkt bleibt aktuell).

Bis dahin sollten Sie nginx für statische Inhalte verwenden.

  • Statische Konfiguration.

Sie kann verwendet werden, aber Envoy ist dafür nicht gedacht. Die Möglichkeiten in der statischen Konfiguration werden nicht ausgeschöpft. Es gibt viele Aspekte:

Wenn Sie die Konfiguration in YAML bearbeiten, werden Sie Fehler machen, die Entwickler wegen der Wortfülle verfluchen und denken, dass die Konfigurationen von nginx/haproxy, obwohl weniger strukturiert, aber prägnanter sind. Genau das ist der Punkt. Die Konfiguration von Nginx und Haproxy wurde für manuelle Anpassungen entwickelt, während die Envoy für die Codegenerierung gedacht ist. Die gesamte Konfiguration ist beschrieben in protobuf, indem Sie sie aus Protobuf-Dateien generieren, ist es viel schwieriger, einen Fehler zu machen.

Canary-Szenarien, b/g Deployments und vieles mehr lassen sich nur in dynamischen Konfigurationen sinnvoll umsetzen. Ich sage nicht, dass man das nicht in der statischen Konfiguration machen kann, wir tun das alles. Aber dazu müssen wir uns mit Stützen umgeben, in jedem der Balancer, Envoy einschließlich.

Einsatzmöglichkeiten, in denen Envoy unverzichtbar ist:

  • Lastverteilung in komplexen und dynamischen Systemen. Dazu gehört das Service Mesh, aber es ist nicht ausschließlich darauf beschränkt.
  • Notwendigkeit von Funktionen wie verteiltem Tracing, komplexer Autorisierung oder anderen, die in Envoy aus der Box verfügbar sind oder einfach umzusetzen sind, während in nginx/haproxy Lua und fragwürdige Plugins erforderlich sind.

Beides muss bei Bedarf eine hohe Leistung bieten.

Wie es funktioniert

Envoy wird ausschließlich als Docker-Image in Binärform verteilt. Das Image enthält bereits ein Beispiel für eine statische Konfiguration. Es ist jedoch nur interessant, um die Struktur zu verstehen.

envoy.yaml statische Konfiguration

statische_ressourcen:
  hörer:
  - name: hörer_0
    adresse:
      socket_adresse:
        protokoll: TCP
        adresse: 0.0.0.0
        port_wert: 10000
    filter_ketten:
    - filter:
      - 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: lokale_route
            virtuelle_hosts:
            - name: lokaler_service
              domänen: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite: www.google.com
                  cluster: service_google
          http_filter:
          - name: envoy.router
  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # Kommentieren Sie die folgende Zeile aus, um im v6-Netzwerk zu testen
    dns_lookup_familie: V4_ONLY
    lb_policy: RUNDEN_ROBIN
    load_assignment:
      cluster_name: service_google
      endpoints:
      - lb_endpoints:
        - endpoint:
            adresse:
              socket_adresse:
                adresse: www.google.com
                port_wert: 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 die Konfiguration des Lastenausgleichs nicht einfach neu laden, unter Last treten "geringe" Probleme auf:

  • Konfigurationsvalidierung.

Die Konfiguration kann groß sein, sie kann sehr groß sein, wenn wir sie beim ersten Mal überladen, steigen die Chancen, dass irgendwo ein Fehler auftritt.

  • Langfristige Verbindungen.

Beim Initialisieren eines neuen Listeners müssen die Verbindungen, die auf der alten Konfiguration basieren, berücksichtigt werden. Wenn die Änderungen häufig vorkommen und es langfristige Verbindungen gibt, muss ein Kompromiss gefunden werden. Hallo, Kubernetes Ingress auf Nginx.

  • Aktive Gesundheitsprüfungen.

Wenn wir aktive Gesundheitsprüfungen haben, sollten wir diese alle auf der neuen Konfiguration überprüfen, bevor wir den Traffic senden. Bei vielen Upstreams benötigt dies Zeit. Hallo, HAProxy.

Wie wird das gelöst in Envoy, indem die Konfiguration dynamisch nach dem Pool-Modell geladen wird, kann sie in einzelne Teile aufgeteilt werden, und der Teil, der sich nicht geändert hat, muss nicht neu initialisiert werden. Zum Beispiel ein Listener, dessen Neuinitalisierung teuer ist und der sich selten ändert.

Konfiguration Envoy (aus der obigen Datei) hat die folgenden Entitäten:

  • listener — ein Listener, der auf einer bestimmten IP/Port hängt
  • virtual host — virtueller Host nach Domainnamen
  • route — Load-Balancing-Regel
  • cluster — Gruppe von Upstreams mit Load-Balancing-Parametern
  • endpoint — Adresse des Instanz-Upstreams

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

Die Dienste heißen entsprechend: LDS, VHDS, RDS, CDS und EDS. Es ist möglich, statische und dynamische Konfiguration zu kombinieren, mit der Einschränkung, dass eine dynamische Ressource nicht in der statischen angegeben werden kann.

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

Die Konfiguration erhält folgendes Format:

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 zum Control-Plane hergestellt und versucht, die RDS-, CDS- und EDS-Konfiguration abzurufen. Der Prozess der Interaktion wird beschrieben hier.

Kurz gesagt, Envoy sendet eine Anfrage mit dem Typ des angeforderten Ressourcen, der Version und den Node-Parametern. Als Antwort erhält es die Ressource und die Version. Wenn sich die Version im Control-Plane nicht geändert hat, antwortet er nicht.
Es gibt 4 Varianten der Interaktion:

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

Inkrementales xDS ermöglicht es, den Datenverkehr zwischen Control-Plane und Envoy, was für große Konfigurationen relevant ist. Es erschwert jedoch die Interaktion, da die Anfrage eine Liste von Ressourcen zum Abbestellen und Abonnieren überträgt.

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

Da in der Anfrage Knotenparameter enthalten sind, können wir an die Control-Plane unterschiedliche Ressourcen für verschiedene Instanzen senden. Envoy, was den Aufbau von Service Mesh erleichtert.

Warmup

Auf Envoy Beim Start oder beim Erhalten einer neuen Konfiguration von der Control-Plane wird der Warmup-Prozess für Ressourcen gestartet. Er ist unterteilt in Listener-Warmup und Cluster-Warmup. Ersterer wird bei Änderungen in RDS/LDS gestartet, letzterer bei CDS/EDS. Das bedeutet, dass bei Änderungen nur der Upstreams der Listener nicht neu erstellt wird.

Während des Warmup-Prozesses werden abhängige Ressourcen von der Control-Plane innerhalb eines Timeouts erwartet. Wenn das Timeout abläuft und die Initialisierung nicht erfolgreich ist, beginnt der neue Listener nicht, den Port zu hören.
Initialisierungsreihenfolge: EDS, CDS, aktiver Gesundheitscheck, RDS, LDS. Bei aktivierten Gesundheitschecks wird der Traffic erst nach einem erfolgreichen Gesundheitscheck zum Upstream geleitet.

Wenn der Listener neu erstellt wurde, wechselt der alte in den Status DRAIN und wird nach dem Schließen aller Verbindungen oder nach Ablauf des Timeouts entfernt. --drain-time-s, standardmäßig 10 Minuten.

Fortsetzung folgt.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster