APISIX : Migration des opérations etcd de HTTP vers gRPC

Zexuan Luo

Zexuan Luo

February 10, 2023

Products

Limitations des opérations HTTP d'Apache APISIX basées sur etcd

Lorsque etcd était en version 2.x, l'interface API qu'il exposait était HTTP 1 (nous l'appellerons simplement HTTP à partir de maintenant). Après la mise à niveau d'etcd vers la version 3.x, il a basculé le protocole de HTTP à gRPC. Pour les utilisateurs qui ne prennent pas en charge gRPC, etcd fournit gRPC-Gateway pour proxifier les requêtes HTTP en gRPC afin d'accéder aux nouvelles API gRPC.

Lorsque APISIX a commencé à utiliser etcd, il utilisait l'API v2 d'etcd. Dans APISIX 2.0 (2020), nous avons mis à jour la version requise d'etcd de 2.x à 3.x. La compatibilité d'etcd avec HTTP nous a permis de gagner du temps pour la mise à jour de version. Nous avons simplement dû modifier le code concernant les méthodes d'appel et le traitement des réponses. Cependant, au fil des années, nous avons également rencontré des problèmes liés à l'API HTTP d'etcd. Il existe encore quelques différences subtiles. Nous avons réalisé que la présence d'un gRPC-gateway ne signifie pas qu'il peut parfaitement supporter l'accès HTTP.

Voici une liste des problèmes liés que nous avons rencontrés avec etcd au cours des dernières années :

  1. gRPC-gateway désactivé par défaut. En raison de la négligence du mainteneur, la configuration par défaut d'etcd n'active pas gRPC-gateway dans certains projets. Nous avons donc dû ajouter des instructions dans la documentation pour vérifier si gRPC-gateway est activé dans la version actuelle d'etcd. Voir https://github.com/apache/apisix/pull/2940.
  2. Par défaut, gRPC limite les réponses à 4 Mo. etcd supprime cette restriction dans le SDK qu'il fournit, mais pas dans gRPC-gateway. Il s'avère que le etcdctl officiel (basé sur le SDK fourni) fonctionne bien, mais pas APISIX. Voir https://github.com/etcd-io/etcd/issues/12576.
  3. Même problème - cette fois avec le nombre maximum de requêtes pour la même connexion. L'implémentation HTTP2 de Go a une configuration MaxConcurrentStreams qui contrôle le nombre de requêtes qu'un seul client peut envoyer simultanément, par défaut à 250. Quel client enverrait normalement plus de 250 requêtes en même temps ? Ainsi, etcd a toujours utilisé cette configuration. Cependant, gRPC-gateway, le "client" qui proxifie toutes les requêtes HTTP vers l'interface gRPC locale, peut dépasser cette limite. Voir https://github.com/etcd-io/etcd/issues/14185.
  4. Après qu'etcd ait activé mTLS, etcd utilise le même certificat à la fois comme certificat serveur et comme certificat client : le certificat serveur pour gRPC-gateway et le certificat client lorsque gRPC-gateway accède à l'interface gRPC. Si l'extension d'authentification serveur est activée sur le certificat, mais que l'extension d'authentification client ne l'est pas, une erreur se produira lors de la vérification du certificat. Une fois de plus, l'accès direct avec etcdctl fonctionne bien (car le certificat ne sera pas utilisé comme certificat client dans ce cas), mais pas APISIX. Voir https://github.com/etcd-io/etcd/issues/9785.
  5. Après avoir activé mTLS, etcd permet la configuration des politiques de sécurité des informations utilisateur des certificats. Comme mentionné ci-dessus, gRPC-gateway utilise un certificat client fixe lors de l'accès à l'interface gRPC plutôt que les informations du certificat utilisé pour accéder à l'interface HTTP au départ. Ainsi, cette fonctionnalité ne fonctionnera pas naturellement puisque le certificat client est fixe et ne sera pas modifié. Voir https://github.com/apache/apisix/issues/5608.

Nous pouvons résumer les problèmes en deux points :

  1. gRPC-gateway (et peut-être d'autres tentatives de conversion de HTTP en gRPC) n'est pas une solution miracle qui résout tous les problèmes.
  2. Les développeurs d'etcd ne mettent pas suffisamment l'accent sur la méthode HTTP vers gRPC. Et leur plus grand utilisateur, Kubernetes, n'utilise pas cette fonctionnalité.

Pour résoudre ce problème, nous devons utiliser etcd directement via gRPC, afin de ne pas passer par le chemin HTTP de gRPC-Gateway réservé pour la compatibilité.

Surmonter les défis de la migration vers gRPC

Bug dans lua-protobuf

Notre premier problème lors du processus de migration a été un bug inattendu dans une bibliothèque tierce. Comme la plupart des applications OpenResty, nous utilisons lua-protobuf pour décoder/encoder protobuf.

Après avoir intégré le fichier proto d'etcd, nous avons constaté qu'il y avait des plantages occasionnels dans le code Lua, signalant une erreur de "table overflow". Comme ce plantage ne peut pas être reproduit de manière fiable, notre premier réflexe a été de chercher un exemple minimal reproductible. Curieusement, si vous utilisez uniquement le fichier proto d'etcd, vous ne pouvez pas reproduire le problème du tout. Ce plantage semble se produire uniquement lorsque APISIX est en cours d'exécution.

Après quelques débogages, j'ai localisé le problème dans lua-protobuf lors de l'analyse du champ oneof du fichier proto. lua-protobuf essaie de pré-allouer la taille de la table lors de l'analyse, et la taille allouée est calculée selon une valeur particulière. Il y avait une certaine probabilité que cette valeur soit un nombre négatif. Ensuite, LuaJIT convertirait ce nombre en un grand nombre positif lors de l'allocation, entraînant une erreur de "table overflow". J'ai signalé le problème à l'auteur et nous avons maintenu un fork avec une solution de contournement en interne.

L'auteur de lua-protobuf a été très réactif, fournissant un correctif le lendemain et publiant une nouvelle version quelques jours plus tard. Il s'est avéré que lorsque lua-protobuf nettoyait les fichiers proto qui n'étaient plus utilisés, il manquait le nettoyage de certains champs, ce qui entraînait un nombre négatif déraisonnable lorsque oneof était ensuite traité. Le problème ne se produisait qu'occasionnellement, et la raison pour laquelle il ne pouvait pas être reproduit en utilisant uniquement le fichier proto d'etcd était qu'il manquait les étapes de nettoyage de ces champs.

Alignement avec le comportement HTTP

Lors du processus de migration, j'ai constaté que l'API existante ne retourne pas exactement le résultat de l'exécution, mais une réponse HTTP avec un statut de réponse et un corps. Ensuite, l'appelant doit traiter la réponse HTTP par lui-même.

Si les réponses étaient en gRPC, elles devaient être encapsulées dans une enveloppe de réponse HTTP pour s'aligner sur la logique de traitement. Sinon, l'appelant doit modifier le code à plusieurs endroits pour s'adapter au nouveau format de réponse (gRPC). Surtout en considérant que les anciennes opérations basées sur HTTP d'etcd doivent également être supportées simultanément.

Bien que l'ajout d'une couche supplémentaire pour être compatible avec la réponse HTTP ne soit pas souhaité, nous devons contourner ce problème. En plus de cela, nous devons également effectuer un traitement sur la réponse gRPC. Par exemple, lorsqu'il n'y a pas de données correspondantes, HTTP ne retourne aucune donnée, mais gRPC retourne une table vide. Cela doit également être ajusté pour s'aligner sur les comportements HTTP.

De la connexion courte à la connexion longue

Dans les opérations basées sur HTTP d'etcd, APISIX utilise des connexions courtes, donc il n'est pas nécessaire de gérer les connexions. Tout ce que nous devons faire est d'initier une nouvelle connexion chaque fois que nous en avons besoin et de la fermer lorsque nous avons terminé.

Mais gRPC ne peut pas faire cela. L'un des principaux objectifs de la migration vers gRPC est d'atteindre le multiplexage, ce qui ne peut pas être réalisé si une nouvelle connexion gRPC est créée pour chaque opération. Ici, nous devons remercier gRPC-go, pour sa capacité intégrée de gestion des connexions, qui peut se reconnecter automatiquement une fois la connexion interrompue. Nous pouvons donc utiliser gRPC-go pour réutiliser la connexion. Et seuls les besoins métier doivent être pris en compte au niveau d'APISIX.

Les opérations d'etcd d'APISIX peuvent être divisées en deux catégories, l'une est les opérations CRUD (ajout, suppression, modification, requête) sur les données d'etcd ; l'autre est la synchronisation de la configuration depuis le plan de contrôle. Bien que théoriquement, ces deux opérations d'etcd pourraient partager la même connexion gRPC, nous avons décidé de les séparer en deux connexions pour la séparation des responsabilités. Pour la connexion des opérations CRUD, comme APISIX doit être traité séparément au démarrage et après le démarrage, une instruction if est ajoutée lors de l'obtention d'une nouvelle connexion. S'il y a un décalage (c'est-à-dire que la connexion actuelle est créée au démarrage alors que nous avons besoin d'une connexion après le démarrage), nous fermons la connexion actuelle et en créons une nouvelle. J'ai développé une nouvelle méthode de synchronisation pour la synchronisation de la configuration, de sorte que chaque ressource utilise un stream sous la connexion existante pour surveiller etcd.

Avantages de la migration vers gRPC

Un avantage évident après la migration vers gRPC est que le nombre de connexions nécessaires pour opérer etcd est considérablement réduit. Lors de l'opération d'etcd via HTTP, APISIX ne pouvait utiliser que des connexions courtes. Et lors de la synchronisation de la configuration, chaque ressource avait une connexion séparée.

Après être passé à gRPC, nous pouvons utiliser la fonction de multiplexage de gRPC, et chaque ressource n'utilise qu'un seul stream au lieu d'une connexion complète. Ainsi, le nombre de connexions n'augmente plus avec le nombre de ressources. En considérant que le développement ultérieur d'APISIX introduira plus de types de ressources, par exemple, la dernière version 3.1 a ajouté secrets, la réduction du nombre de connexions en utilisant gRPC sera plus significative.

Lors de l'utilisation de gRPC pour la synchronisation, chaque processus n'a qu'une seule (deux, si le sous-système de stream est activé) connexion pour la synchronisation de la configuration. Dans la figure ci-dessous, nous pouvons voir que les deux processus ont quatre connexions, dont deux sont pour la synchronisation de la configuration, l'API Admin utilise une connexion, et la connexion restante est pour l'agent privilégié pour rapporter les informations du serveur.

gRPC utilise beaucoup moins de connexions

Pour comparaison, la figure ci-dessous montre 22 connexions nécessaires pour utiliser la méthode de synchronisation de configuration d'origine tout en gardant les autres paramètres inchangés. De plus, ces connexions sont des connexions courtes.

trop de connexions

La seule différence entre ces deux configurations est si gRPC est activé pour les opérations d'etcd :

  etcd:
    use_grpc: true
    host:
      - "http://127.0.0.1:2379"
    prefix: "/apisix"
    ...

En plus de réduire le nombre de connexions, l'utilisation de gRPC pour accéder directement à etcd au lieu de gRPC-gateway peut résoudre une série de problèmes limités architecturalement tels que l'authentification mTLS mentionnée au début de l'article. Il y aura également moins de problèmes après l'utilisation de gRPC, car Kubernetes utilise gRPC pour opérer etcd. S'il y a un problème, il sera découvert par la communauté Kubernetes.

Bien sûr, comme la méthode gRPC est encore relativement nouvelle, APISIX aura inévitablement quelques nouveaux problèmes lors de l'opération d'etcd via gRPC. Actuellement, la méthode par défaut utilise toujours la méthode HTTP d'origine pour opérer etcd par défaut. Les utilisateurs ont la possibilité de configurer use_grpc sous etcd à true dans config.yaml par eux-mêmes. Vous pouvez essayer si la méthode gRPC est meilleure. Nous recueillerons également continuellement les retours de diverses sources pour améliorer l'opération d'etcd basée sur gRPC. Lorsque nous constaterons que l'approche gRPC est suffisamment mature, nous en ferons l'approche par défaut.

Pour maximiser APISIX, vous avez besoin d'API7

Vous aimez les performances d'Apache APISIX, mais pas les frais généraux de sa gestion. Vous pouvez vous concentrer sur votre cœur de métier sans vous soucier de la configuration, de la maintenance et des mises à jour.

Notre équipe comprend les créateurs et contributeurs d'Apache APISIX, les mainteneurs principaux d'OpenResty et NGINX, les membres de Kubernetes et des experts de l'industrie sur l'infrastructure cloud. Vous obtenez les meilleures personnes derrière la scène.

Voulez-vous accélérer votre développement en toute confiance ? Pour maximiser le support d'APISIX, vous avez besoin d'API7. Nous fournissons un support approfondi pour APISIX et des solutions de gestion d'API basées sur vos besoins !

Contactez-nous dès maintenant : https://api7.ai/contact.

Tags: