Envoy. 1. Introduction

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 être corrigé. 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, PR 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 protobuf, 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.com

Configuration 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 java 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: 6565

Lors 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 ici.

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

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster