Bonjour ! Voici un court article répondant aux questions : "qu'est-ce qu'Envoy ?", "à quoi ça sert ?" et "par où commencer ?".
Qu'est-ce que c'est
Envoy est un équilibreur de charge L4-L7 écrit en C++, conçu pour des performances et une disponibilité élevées. D'une part, c'est en quelque sorte l'équivalent de nginx et haproxy, comparable à eux en termes de performances. D'autre part, il est davantage orienté vers l'architecture de microservices et dispose de fonctionnalités équivalentes à celles des équilibreurs de charge en Java et Go, comme zuul ou traefik.
Tableau comparatif haproxy/nginx/envoy, qui ne prétend pas à la vérité absolue, mais donne une vue d'ensemble.
nginx
haproxy
envoy
), qui sera l'unique
étoiles sur github
11.2k/mirror
1.1k/mirror
12.4k
27.6k
écrit en
C
C
C++
go
API
non
socket only/push
dataplane/pull
pull
contrôle de santé actif
non
oui
oui
oui
Open tracing
plugin externe
non
oui
oui
JWT
plugin externe
non
oui
non
Extension
Lua/C
Lua/C
Lua/C++
non
Pourquoi
C'est un projet jeune, il manque beaucoup de choses, certains éléments sont en alpha précoce. Mais envoy, notamment grâce à sa jeunesse, il évolue rapidement et a déjà beaucoup de fonctionnalités intéressantes : configuration dynamique, de nombreux filtres prêts à l'emploi, une interface simple pour écrire ses propres filtres.
Cela découle des domaines d'application, mais pour commencer, voici deux anti-modèles :
- Distribution de contenu statique.
Le fait est qu'à l'heure actuelle, envoy il n'y a pas de support pour le caching. Les gars de Google essaient de . L'idée est d'implémenter une fois pour toutes dans envoy tous les détails (zoo des en-têtes) conformes aux RFC, et de créer une interface pour les implémentations spécifiques. Mais pour l'instant, ce n'est même pas de l'alpha, l'architecture est en discussion, ouvert (pendant que j'écrivais cet article, un PR a été fusionné, mais ce point est toujours d'actualité).
En attendant, utilisez nginx pour le contenu statique.
- Configuration statique.
Vous pouvez l'utiliser, mais envoy elle n'a pas été créée pour cela. Les fonctionnalités en configuration statique ne seront pas exploitées. Il y a beaucoup de points :
En éditant la configuration en yaml, vous ferez des erreurs, vous maudirez les développeurs pour leur prolixité et vous penserez que les configurations nginx/haproxy, bien que moins structurées, sont plus concises. C'est le cœur du problème. La configuration de Nginx et Haproxy a été créée pour une édition manuelle, tandis que envoy est conçue pour la génération à partir du code. Toute la configuration est décrite dans , en générant cela à partir de fichiers proto, il est beaucoup plus difficile de se tromper.
Les scénarios de déploiement canary, b/g et bien d'autres se réalisent correctement uniquement dans une configuration dynamique. Je ne dis pas que cela ne peut pas être fait dans une configuration statique, nous le faisons tous. Mais pour cela, il faut jongler avec des astuces, dans n'importe lequel des équilibreurs, y compris. envoy c'est vrai.
Les tâches dans lesquelles Envoy est indispensable :
- L'équilibrage de trafic dans des systèmes complexes et dynamiques. Cela inclut le service mesh, mais ce n'est pas seulement cela.
- La nécessité d'une fonctionnalité de traçage distribué, d'une autorisation complexe ou d'autres, qui sont disponibles dans envoy ou qui peuvent être facilement mises en œuvre, alors que dans nginx/haproxy, il faut se tourner vers lua et des plugins douteux.
Les deux doivent, si nécessaire, garantir des performances élevées.
Comment cela fonctionne
Envoy est distribué en tant qu'images docker uniquement. L'image contient déjà un exemple de configuration statique. Mais il nous intéresse seulement pour comprendre la structure.
envoy.yaml configuration statique
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
# Commentez la ligne suivante pour tester sur des réseaux 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.comConfiguration dynamique
Quel problème sommes-nous en train de résoudre ? On ne peut pas simplement recharger la configuration de l'équilibreur sous charge, cela entraînera des "petits" problèmes :
- Validation de la configuration.
Le fichier de configuration peut être grand, voire très grand, si nous le rechargeons entièrement, les chances d'une erreur quelque part augmentent.
- Connexions de longue durée.
Lors de l'initialisation d'un nouveau listener, il faut prendre en compte les connexions fonctionnant sur l'ancien. Si les changements se produisent fréquemment et que des connexions de longue durée existent, il faudra trouver un compromis. Bonjour, kubernetes ingress sur nginx.
- Checks de santé actifs.
S'il y a des checks de santé actifs, il vaudrait mieux tous les vérifier sur la nouvelle configuration avant d'envoyer du trafic. S'il y a beaucoup d'upstreams, cela prend du temps. Bonjour, haproxy.
Comment cela est résolu dans envoy, en chargeant la configuration dynamiquement, par le modèle de pool, il est possible de la diviser en parties distinctes et de ne pas réinitialiser la partie qui n'a pas changé. Par exemple, un listener, qui coûte cher à réinitialiser, change rarement.
Configuration envoy (du fichier ci-dessus) a les entités suivantes :
- listener — un listener accrochés à une certaine ip/port
- virtual host — un hôte virtuel par nom de domaine
- route — règle de répartition
- cluster — groupe d'upstreams avec des paramètres de répartition
- endpoint — adresse de l'instance d'upstream
Chacune de ces entités plus certaines autres peuvent être remplies dynamiquement, pour cela, l'adresse du service d'où la configuration sera obtenue est indiquée dans la configuration. Le service peut être REST ou gRPC, il est préférable d'utiliser gRPC.
Les services s'appellent respectivement : LDS, VHDS, RDS, CDS et EDS. Il est possible de combiner une configuration statique et dynamique, avec la restriction que la ressource dynamique ne peut pas être spécifiée dans la configuration statique.
Pour la plupart des tâches, il suffit de mettre en œuvre les trois derniers services, ils s'appellent ADS (Aggregated Discovery Service), pour et Go dispose d'une implémentation gRPC dataplane prête à l'emploi dans laquelle il suffit de remplir les objets à partir de votre source.
La configuration prend la forme suivante :
envoy.yaml configuration dynamique
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: 6565Lors de l'exécution, le message suivant s'affiche : envoy avec cette configuration, il se connectera au control-plane et tentera de demander la configuration RDS, CDS et EDS. Comment se déroule le processus d'interaction est décrit .
En bref, envoy il envoie une demande, en spécifiant le type de ressource demandée, la version et les paramètres du nœud. En réponse, il reçoit la ressource et la version, si la version sur le control-plane n'a pas changé, il ne répond pas.
Il existe 4 variantes d'interaction :
- Un flux gRPC pour tous les types de ressources, l'état complet de la ressource est envoyé.
- Flux séparés, état complet.
- Un flux, état incrémental.
- Flux séparés, état incrémental.
xDS incrémental permet de réduire le trafic entre le plan de contrôle et envoy, ce qui est pertinent pour les grandes configurations. Mais cela complique l'interaction, car la requête transmet une liste de ressources pour se désabonner et s'abonner.
Dans notre exemple, nous utilisons ADS — un flux pour RDS, CDS, EDS et en mode non incrémental. Pour activer le mode incrémental, il faut indiquer api_type: DELTA_GRPC
Comme la requête comporte des paramètres de nœud, nous pouvons envoyer des ressources différentes au plan de contrôle pour différentes instances envoy, ce qui est pratique pour construire un maillage de services.
Réchauffement
Sur envoy lorsque vous démarrez ou recevez une nouvelle configuration du plan de contrôle, le processus de réchauffement des ressources est lancé. Il est divisé en réchauffement de l'écouteur et réchauffement du cluster. Le premier est lancé lors de modifications dans RDS/LDS, le second lors de CDS/EDS. Cela signifie que si seuls les upstreams changent, l'écouteur n'est pas recréé.
Pendant le processus de réchauffement, des ressources dépendantes du plan de contrôle sont attendues pendant un certain délai. Si le délai est dépassé, l'initialisation échouera et le nouvel écouteur ne commencera pas à écouter le port.
Ordre d'initialisation : EDS, CDS, vérification de santé active, RDS, LDS. Lorsque la vérification de santé active est activée, le trafic sera dirigé vers l'upstream uniquement après une vérification de santé réussie.
Si l'écouteur a été recréé, l'ancien passe en état DRAIN et sera supprimé après la fermeture de toutes les connexions ou l'expiration du délai --drain-time-s, par défaut 10 minutes.
La suite au prochain épisode.
Source : habr.com
