Est ce que j'ai vraiment besoin d'une API gateway ?
Aujourd’hui, je me chauffe pour faire une architecture microservice. Mon idée est de combiner Cloudrun avec l’API gateway pour profiter de l’hyperscalabilité de Cloudrun et mettre en place du rate limiting sur mes services. J’utiliserai un identity provider pour gérer l’authentification et les permissions de mes utilisateurs.
Et bien sûr, je vais bricoler des services un peu éclatés pour le bien de mon POC hihihi.
Mon idée est de me dire que Cloudrun est bien car il s’autoscale automatiquement, mais lorsque la charge augmente considérablement, cela peut coûter cher. Il est donc essentiel de trouver des solutions pour atténuer les floods de requêtes et calmer les robots qui attaquent un peu trop vigoureusement, sans pénaliser excessivement les requêtes légitimes. On peut toujours bloquer les IPs un peu trop agressives avec Cloud Armor.
En revanche, je pense que le rate limiting est une protection passive plutôt simple à mettre en œuvre dès le début du projet, et c’est une bonne solution passive pour atténuer les attaques.
De plus, passer par l’API gateway permet de garder l’option de mélanger différentes technologies pour répondre au mieux aux différents besoins. Cela offre la possibilité d’utiliser des cloud functions, du Cloudrun, du Kubernetes, voire même du compute en fonction des besoins, de manière transparente pour l’utilisateur final.
Un point que j’évoque rarement, c’est l’idée de réversibilité. Si l’on change complètement d’avis et que, pour des raisons commerciales, nous décidons de quitter GCP, comment pouvons-nous réinternaliser des cloud functions, du Cloudrun, etc. ? Si nous avons fait une erreur en utilisant Kubernetes et que nous souhaitons revenir à un monolithe, comment puis-je faire cohabiter plusieurs technologies sur le même domaine ?
Plus d’info sur Identity provider
Un fournisseur d’identité (IdP) est un service qui stocke et vérifie l’identité des utilisateurs. Les IdP sont généralement des services hébergés dans le cloud qui travaillent souvent avec des fournisseurs d’authentification unique (SSO) pour authentifier les utilisateurs. source : https://www.cloudflare.com/fr-fr/learning/access-management/what-is-an-identity-provider/
En général on utilise keycloak en version opensource gratuite ou bien une solution payante en SAAS type okta
Plus d’info sur le rate limiting
Le rate limiting est utilisé pour contrôler le débit du trafic envoyé ou reçu sur une interface réseau.
Le trafic inférieur ou égal à la limite spécifiée est envoyé, tandis que le trafic excédant cette limite est rejeté ou retardé. Un appareil qui remplit cette fonction est un limiteur de débit. Le rate limiting est effectué par traffic policing (rejet des paquets en excès), queuing (retard des paquets en transit) ou par contrôle de congestion (manipulation des mécanismes de contrôle de congestion du protocole utilisé). Policing et queuing peuvent être appliqués à tous les réseaux informatiques. Le contrôle de congestion peut seulement être appliqué aux protocoles réseaux utilisant des mécanismes de contrôle de congestion, tels que TCP.
source : https://fr.wikipedia.org/wiki/Rate_limiting
On utilise le rate limiting pour empêcher les scripts un peu trop agressifs de surcharger de requêtes les serveurs et causer des indisponibilités de service.
Je vous vois consommer des services comme des festivaliers du hellfest à la tireuse des bars, bande de saoulars de la bande passante !
Mettre en place une API gateway
À partir de là, j’ai commencé à bricoler toutes sortes de trucs, et j’ai fait quelques découvertes choquantes.
J’ai comme une grande vibe à la IoT Core (qui a été déprécié au profit de Clearblade). Je pense que Google ne va pas faire plus d’efforts que ça pour mettre en avant ce produit et peut-être même le déprécier complètement au profit d’Apigee.
Néanmoins, je ne vais pas jouer le jeu d’Apigee pour autant, puisque la stratégie ressemble énormément à celle de Microsoft : “Allez viens, on t’offre des jours gratuits et ensuite tu payes :)”
Comme je n’ai aucune idée de combien de temps encore je vais faire mon POC, je préfère le pricing de la gateway qui dit que tant que j’ai un trafic insignifiant je ne paie rien !
J’avais dans l’idée de mettre en place un FastAPI avec le CRUD Router, pour avoir des routes autogénérées et faire une petite API tranquille avec très peu de code.
J’aurais transporté ma spécification OpenAPI auto-générée de FastAPI dans ma conf API gateway, et j’aurais sûrement bricolé pour mettre en place l’authentification par clé d’API et du rate limiting.
En me renseignant un peu, je m’aperçois que l’API gateway GCP stagne techniquement avec un niveau très restreint de fonctionnalités et met un temps colossal pour mettre à jour la plateforme en version OpenAPI 3.
https://issuetracker.google.com/issues/204211041?pli=1
Ça veut dire que ce n’est même pas la peine d’essayer de transposer gracieusement mon Swagger FastAPI directement dans l’API gateway. Ça semble rien sur le papier, mais ça donne l’impression que ce service n’est pas du tout prioritaire dans la roadmap de GCP.
Donc ensuite je me suis dit : je vais faire le minimum et voir où ça nous mène, en restant dans les limitations techniques de l’outil.
On repart sur un scope plus simple
On va commencer par lire la doc et voir les promesses de l’outil.
Avec API Gateway, vous pouvez créer, sécuriser et surveiller les API pour les backends sans serveur de Google Cloud, y compris Cloud Functions, Cloud Run et App Engine. Conçu sur Envoy, API Gateway offre hautes performances et évolutivité, et vous permet de vous consacrer à la création d'applications de qualité. Ses niveaux de tarification basés sur la consommation vous aident à mieux gérer les coûts.
J’en comprends que le service rendu par API gateway est surtout prévu pour les services managés serverless. Du coup, on va partir dans l’idée de faire une cloud function servie par l’API gateway, à laquelle on ajoutera une authentification par API token et un rate limiting.
Si on crée une cloud function v2, en fait on s’aperçoit que c’est un Cloud Run qui tourne derrière. On peut tout à fait récupérer l’image et l’étudier. Personnellement, j’ai choisi d’en refaire un Cloud Run, car la ressource Terraform est infiniment plus simple à gérer que la cloud function.
Au passage, si on fouille l’image de la cloud function, on n’en apprend pas beaucoup plus que ce que la doc nous dit déjà. Un petit coup de “docker inspect” nous dit que l’entrypoint du conteneur est “/cnb/process/web”, donc on peut suspecter l’utilisation de cloud native buildpacks. Et notre fonction embarque comme dépendance le function framework de Google : https://cloud.google.com/functions/docs/functions-framework
All this fuss over fetching a tarball and executing the contents. (Kelsey Hightower)
Du coup, au niveau Terraform, ça me permet de faire ce setup qui contiendra l’objet API gateway, la gateway de l’objet gateway, et la conf que j’ai templatisée.
Je fais un hash 256 de mon fichier de template, ce qui me permet d’avoir un nom aléatoire à chaque fois et de garder un état idempotent de mon déploiement sans changement. Je ne peux pas dater le nom de mon fichier de conf pour cette raison, et le service n’a pas prévu de faire tourner le même fichier de conf sous plusieurs versions. Donc conflit de nommage si on utilise un nom fixe.
J’utilise le data source de mon Cloud Run pour récupérer son URL et la templater dans ma conf.
Le reste de la conf est construit à la main et c’est pénible de rajouter et d’enlever de nouveaux URI sur la configuration. Disons que pour une dizaine de fonctions ça reste acceptable, mais au-delà, je pense que les maux de tête vont arriver assez vite.
Voici la conf minimale que j’ai mise en place.
|
|
|
|
Maintenant en ce qui concerne le rate limiting et l’authentification pas api key, ça rend un service acceptable.
|
|
le message d’erreur est pas super mais au moins le code de retour est correct “429 Too Many Requests” et une fois la barrière du premier setup c’est assez simple de mettre de nouveaux entrypoints. du moins sur des fonctions ultra basiques.
On essaye des trucs plus complexes ?
Maintenant, en ce qui concerne le rate limiting et l’authentification par API key, ça rend un service acceptable.
J’ai adapté l’exemple du FastAPI CRUD Router pour avoir un peu plus qu’un simple “hello world” et plusieurs routes à exposer dans le même conteneur.
|
|
Je vous avoue que je n’ai pas fait d’effort particulier pour faire du beau code. Je voulais juste un service minimum qui fait lecture, écriture et suppression dans BigQuery. J’ai essayé d’utiliser le CRUD Router pour cela, mais clairement, ce n’est pas du tout fait pour ça !
J’ai pris un rabbit hole assez vite, même ChatGPT m’a dit de laisser tomber l’idée.
Au final, on peut voir les routes de cette façon :
|
|
Un CRUD de base, un GET all et la documentation OpenAPI. Juste ce qu’il faut pour un peu plus qu’un simple “hello world”.
Je pensais que je pourrais facilement adapter mon OpenAPI autogénéré, mais c’est même pas la peine d’y penser. Ça a été une grosse galère pour exposer les URLs sans erreur, et encore, le travail de mapping n’est même pas encore complet.
Je me trouve dans un état un peu pourri où je dois injecter mon token dans toutes mes URLs, et bien sûr, FastAPI devrait être adapté pour ça. Autant dire que la charge cognitive que ça représente pour le service rendu est juste parfaitement débile.
Je vous mets le fichier de template que j’ai bricolé, parce que franchement, ça serait dommage de jeter ça. Bonne indigestion :)
|
|
On passe à l’addition (ou la soustraction de features)
Donc, dans la liste des trucs énervants :
- Les doubles mappings de route (dans le Cloud Run et dans l’API) alors qu’on a déjà un Swagger propre et à jour généré automatiquement.
- La non-compatibilité avec OpenAPI 3.
- Je n’ai pas trouvé de webpoint qui pourrait exposer l’OpenAPI de l’API gateway, et franchement, ça m’a fatigué d’essayer de le faire, alors j’ai abandonné l’idée.
- Le changement du fichier de la conf est looooooooooooong.
- Les features sont basiques au possible.
- Le rate limiting est super chiant à mettre en place.
- Le token API est un token long qui ne change jamais et doit être passé en requêtes GET.
- On peut faire une quantité très limitée d’API tokens (10 max), mais ce sont des tokens longs, ce n’est pas forcément une mauvaise chose.
Je n’ai pas vraiment compris comment fonctionne le calcul du rate limiting, mais je n’ai pas creusé plus loin, tellement ça m’a fatigué. Je n’ai pas tenté d’utiliser un identity provider tiers, étant donné le challenge débile que représentait cette configuration pour un “truc facile”. Je ne suis pas du tout convaincu par l’ergonomie et le service rendu sur ce service managé. Je reste persuadé qu’il va dégager au profit d’Apigee ou d’une autre solution plus mature et plus simple à utiliser.
En ce qui me concerne, j’utiliserais plutôt un load balancer/Cloud Armor avec redirection vers Cloud Run pour bannir manuellement les méchants si j’étais dans une phase early de mon projet. Pourquoi pas tester Apigee au moment où un besoin de plus de sécurité se ferait sentir ?
Finalement, on a mieux à faire de gérer les routes vers des multi workloads directement depuis le load balancer, sans s’embêter à faire un mapping infernal et perdre un temps colossal pour un service peu rendu.
Autant gérer autrement l’authentification avec un JWT et mettre en place le rate limiting dans le code avec Redis, pourquoi pas.
Je dirais que par rapport à la qualité des services de base de Google Cloud, on est clairement sur un produit pas fini. Je n’ai aucune envie de m’embêter avec un truc pareil pour protéger mon backend tellement c’est casse-pieds !
Ou alors, c’est moi qui n’ai rien compris et je fais totalement de la merde, c’est aussi possible !