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 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). 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. Szenarien 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.comDynamische 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 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: 6565Beim 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 .
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
