Gesandte. 1. Einleitung

Grüße! Dies ist ein kurzer Artikel, der die Fragen beantwortet: „Was ist ein Gesandter?“, „Warum wird er benötigt?“ und „Wo soll ich anfangen?“.

Was ist das

Envoy ist ein in C++ geschriebener L4-L7-Balancer, der auf hohe Leistung und Verfügbarkeit ausgerichtet ist. Einerseits ist dies in gewisser Weise ein Analogon zu Nginx und Haproxy, dessen Leistung mit ihnen vergleichbar ist. Andererseits ist es eher auf die Microservice-Architektur ausgerichtet und verfügt über eine nicht schlechtere Funktionalität als Java- und Go-Balancer wie Zuul oder Traefik.

Vergleichstabelle von haproxy/nginx/envoy, sie erhebt keinen Anspruch auf absolute Wahrheit, sondern vermittelt ein allgemeines Bild.


haproxy
Gesandte
Verkehr

Sterne auf Github
11.2k/Spiegel
1.1k/Spiegel
12.4K
27.6K

geschrieben in
C
C
C + +
go

API
Nein
Nur Steckdose/Push
Datenebene/Pull
ziehen

aktiver Gesundheitscheck
Nein
ja
ja
ja

Offene Nachverfolgung
externes Plugin
Nein
ja
ja

JWT
externes Plugin
Nein
ja
Nein

Expansion
Lua/C
Lua/C
Lua/C++
Nein

Warum

Dies ist ein junges Projekt, es fehlen viele Dinge, einige befinden sich in der frühen Alpha. Aber Gesandteentwickelt sich, auch aufgrund seiner Jugend, rasant und verfügt bereits über viele interessante Features: dynamische Konfiguration, viele vorgefertigte Filter, eine einfache Schnittstelle zum Schreiben eigener Filter.
Daraus ergeben sich Anwendungsbereiche, allerdings gibt es zunächst 2 Antimuster:

  • Statischer Rückstoß.

Tatsache ist, dass im Moment in Gesandte keine Caching-Unterstützung. Die Google-Leute versuchen das искреть. Die Idee wird einmal umgesetzt Gesandte Alle Feinheiten (Zoo-Header) der RFC-Konformität berücksichtigen und für bestimmte Implementierungen eine Schnittstelle erstellen. Aber im Moment ist es noch nicht einmal Alpha, die Architektur wird diskutiert, PR offen (während ich den PR-Artikel schrieb, fror die PR ein, aber dieser Punkt ist immer noch relevant).

Verwenden Sie vorerst Nginx für die Statik.

  • Statische Konfiguration.

Sie können es verwenden, aber Gesandte Dafür wurde es nicht geschaffen. Features in einer statischen Konfiguration werden nicht verfügbar gemacht. Es gibt viele Momente:

Wenn Sie die Konfiguration in Yaml bearbeiten, werden Sie sich irren, die Entwickler für ihre Ausführlichkeit schelten und denken, dass die Nginx/Haproxy-Konfigurationen zwar weniger strukturiert, aber prägnanter sind. Das ist der Punkt. Die Konfiguration von Nginx und Haproxy wurde zur manuellen Bearbeitung erstellt und Gesandte zur Generierung aus Code. Die gesamte Konfiguration ist in beschrieben protobuf, es aus Protodateien zu generieren, ist viel schwieriger, einen Fehler zu machen.

Canary-, B/G-Bereitstellungsszenarien und vieles mehr werden normalerweise nur in einer dynamischen Konfiguration implementiert. Ich sage nicht, dass dies nicht statisch möglich ist, wir alle tun es. Dafür müssen Sie jedoch Krücken in einem der Balancer anziehen Gesandte einschließlich.

Aufgaben, für die Envoy unverzichtbar ist:

  • Verkehrsausgleich in komplexen und dynamischen Systemen. Dazu gehört auch das Service Mesh, es ist aber nicht unbedingt das einzige.
  • Der Bedarf an verteilter Ablaufverfolgungsfunktion, komplexer Autorisierung oder anderen verfügbaren Funktionen Gesandte sofort einsatzbereit oder bequem implementiert, aber in Nginx/Haproxy muss man von Lua und dubiosen Plugins umgeben sein.

Beide liefern bei Bedarf eine hohe Leistung.

Wie funktioniert das

Envoy wird in Binärdateien nur als Docker-Image verteilt. Das Bild enthält bereits ein Beispiel einer statischen Konfiguration. Aber wir sind nur daran interessiert, die Struktur zu verstehen.

Statische Konfiguration von envoy.yaml

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
    # Comment out the following line to test on v6 networks
    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

Für welches Problem suchen wir eine Lösung? Unter Last kann man die Load-Balancer-Konfiguration nicht einfach neu laden, es entstehen „kleine“ Probleme:

  • Konfigurationsvalidierung.

Die Konfiguration kann groß sein, sie kann sehr groß sein. Wenn wir sie auf einmal überlasten, steigt die Wahrscheinlichkeit, dass irgendwo ein Fehler auftritt.

  • Langlebige Verbindungen.

Wenn Sie einen neuen Listener initialisieren, müssen Sie sich um die Verbindungen kümmern, die auf dem alten laufen. Bei häufigen Änderungen und langlebigen Verbindungen müssen Sie nach einem Kompromiss suchen. Hallo, Kubernetes-Ingress auf Nginx.

  • Aktive Gesundheitschecks.

Wenn wir aktive Gesundheitsprüfungen haben, müssen wir sie alle in der neuen Konfiguration noch einmal überprüfen, bevor wir Datenverkehr senden. Wenn es viele Upstreams gibt, braucht das Zeit. Hallo haproxy.

Wie wird das gelöst? GesandteIndem Sie die Konfiguration entsprechend dem Poolmodell dynamisch laden, können Sie sie in separate Teile aufteilen und müssen den Teil, der sich nicht geändert hat, nicht erneut initialisieren. Zum Beispiel ein Listener, dessen Neuinitialisierung teuer ist und der sich selten ändert.

Konfiguration Gesandte (aus der Datei oben) hat die folgenden Entitäten:

  • Hörer – Listener hängt an einer bestimmten IP/einem bestimmten Port
  • virtueller Host - Virtueller Host nach Domänenname
  • Route - Ausgleichsregel
  • Gruppe — eine Gruppe von Upstreams mit Ausgleichsparametern
  • Endpunkt — Upstream-Instanzadresse

Jede dieser Entitäten und einige andere können dynamisch ausgefüllt werden; dazu gibt die Konfiguration die Adresse des Dienstes an, von dem die Konfiguration empfangen wird. Der Dienst kann REST oder gRPC sein, gRPC ist vorzuziehen.

Die Dienste heißen jeweils: LDS, VHDS, RDS, CDS und EDS. Sie können statische und dynamische Konfiguration kombinieren, mit der Einschränkung, dass eine dynamische Ressource nicht in einer statischen angegeben werden kann.

Für die meisten Aufgaben reicht es aus, die letzten drei Dienste zu implementieren, sie werden z. B. ADS (Aggregated Discovery Service) genannt Java Und schon gibt es eine fertige Implementierung der gRPC-Datenebene, in die Sie nur noch die Objekte aus Ihrer Quelle eintragen müssen.

Die Konfiguration hat folgende Form:

Dynamische Konfiguration von envoy.yaml

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 Ausführen Gesandte Mit dieser Konfiguration wird eine Verbindung zur Steuerungsebene hergestellt und versucht, die RDS-, CDS- und EDS-Konfiguration anzufordern. Es wird beschrieben, wie der Interaktionsprozess abläuft hier.

Zusamenfassend, Gesandte sendet eine Anfrage mit Angabe des angeforderten Ressourcentyps, der Version und der Parameter des Knotens. Als Antwort erhält es eine Ressource und eine Version; wenn sich die Version auf der Steuerungsebene nicht geändert hat, antwortet es nicht.
Es gibt 4 Interaktionsmöglichkeiten:

  • Ein gRPC-Stream für alle Arten von Ressourcen, der vollständige Status der Ressource wird gesendet.
  • Separate Streams, vollständiger Zustand.
  • Ein Stream, inkrementeller Zustand.
  • Separate Streams, inkrementeller Status.

Mit inkrementellem xDS können Sie den Datenverkehr zwischen der Steuerungsebene und reduzieren GesandteDies ist für große Konfigurationen relevant. Dies erschwert jedoch die Interaktion: Die Anfrage enthält eine Liste von Ressourcen zum Abmelden und Abonnieren.

Unser Beispiel verwendet ADS – einen Stream für RDS, CDS, EDS und den nicht-inkrementellen Modus. Um den inkrementellen Modus zu aktivieren, müssen Sie Folgendes angeben api_type: DELTA_GRPC

Da die Anfrage Knotenparameter enthält, können wir für verschiedene Instanzen unterschiedliche Ressourcen an die Steuerungsebene senden GesandteDies ist praktisch für den Aufbau eines Service-Mesh.

Sich warm laufen

Auf Gesandte Beim Start oder beim Empfang einer neuen Konfiguration von der Steuerungsebene wird der Ressourcenaufwärmprozess gestartet. Es ist in Listener-Aufwärmphase und Cluster-Aufwärmphase unterteilt. Der erste wird bei Änderungen in RDS/LDS gestartet, der zweite bei CDS/EDS. Das bedeutet, dass der Listener nicht neu erstellt wird, wenn sich nur Upstreams ändern.

Während des Aufwärmvorgangs werden während des Timeouts abhängige Ressourcen von der Steuerungsebene erwartet. Wenn die Zeitüberschreitung auftritt, ist die Initialisierung nicht erfolgreich und der neue Listener beginnt nicht mit der Überwachung des Ports.
Initialisierungsreihenfolge: EDS, CDS, aktive Gesundheitsprüfung, RDS, LDS. Wenn aktive Gesundheitsprüfungen aktiviert sind, wird der Datenverkehr erst nach einer erfolgreichen Gesundheitsprüfung flussaufwärts geleitet.

Wenn der Listener neu erstellt wurde, wechselt der alte in den DRAIN-Status und wird gelöscht, nachdem alle Verbindungen geschlossen wurden oder das Timeout abgelaufen ist --drain-time-s, Standard 10 Minuten.

To be continued.

Source: habr.com

Kommentar hinzufügen