Cloud Amazon et Lambda : retour d'expérience sur l’utilisation des lambdas avec DynamoDB, RDS et VPC
Vous connaissez, ou avez très certainement déjà entendu parler de la plateforme Cloud proposée par Amazon : Amazon Web Services.
Amazon Web Services, c'est plus d'un tiers des parts de marché sur le secteur des plateformes Cloud. C'est l'outil privilégié de grands comptes comme Netflix, Atlassian, Ryanair et même la NASA. C'est aussi des dizaines de services adaptés à différents besoins (calcul, stockage, base de données, réseaux, ...). Comme vous l’aurez compris, AWS c'est la tendance incontournable du moment, et il y a fort à parier que vous allez un jour travailler avec. Chez ISC (l’Innovative Service Center de SQLI), nous avons eu l’occasion d’utiliser certains outils proposés par la plateforme AWS et notamment les fonctions Lambda. Voici un retour d’expérience qui vous servira peut-être d’aide-mémoire, pour vous accompagner et démarrer sereinement votre projet avec AWS Lambda.
LE CONTEXTE
En 2017, nous avons démarré un ambitieux projet avec AWS. Nous avons monté une équipe de développeurs confirmés, accompagnés d'experts et d'architectes, dans le but de réaliser un portail en utilisant exclusivement les services Cloud d'Amazon. Nous nous sommes donc lancés dans l'aventure. Rapidement, nous nous sommes heurtés à différents problèmes, desquels nous avons tiré des leçons. Voici 3 conseils que nous pouvons vous apporter à la suite de cette expérience.
CONSEIL NUMÉRO 1 : L’IMPORTANCE DE LA CONCEPTION AVEC LAMBDA
Une lambda chez Amazon est une unité de code exécutable, autonome (stateless), et atomique (la plus petite qui soit), pour laquelle le développeur n’a pas à s’occuper de l’environnement d’exécution (serverless). Concrètement, il s’agit d’un fichier de code qui contient une seule fonction appelée par Amazon lors d’une sollicitation externe.Une lambda peut être déclenchée par un appel HTTP (via API Gateway) pour créer un point d’API par exemple. Mais elle peut aussi être appelée par d’autres fonction Lambda, ou encore par d’autres services d’Amazon. Par exemple par le déclenchement d’un événement provenant d’une file de messages (SNS / SQS).
Ce principe de fonction Lambda, qui peut être instanciée à la volée sans s’occuper de l’infrastructure, peut paraître très attractif,notamment en termes de coût. En revanche, il faut faire attention à bien concevoir l'architecture de l’application et réfléchir au découpage de votre code. Comment allez-vous gérer la sécurité ? Quels systèmes d’authentification allez-vous mettre en place ? Comment vos lambdas vont-elles communiquer entre elles ? Comment partagerez-vous du code entre vos lambdas ? Et dans quelle mesure ? Ces questions ne doivent pas être prises à la légère. Essayez de :
- Bien cadrer votre besoin ;
- Définir pourquoi vous utilisez Lambda (plutôt qu’une application monolithique plus classique) ;
- Savoir comment votre application va se comporter si vous avez des pics de sollicitations, ou au contraire si vous n'avez que peu de trafic.
N’hésitez pas à schématiser votre application ainsi que les transferts d’informations entre vos différentes briques applicatives.
Note importante sur le réchauffage
Une lambda possède potentiellement 2 états : chaude ou froide. Une lambda dite « chaude » est une lambda dont le code source est déjà chargé en mémoire et qui est prête à être exécutée. Lorsqu’un appel arrive, le code se déclenche quasi instantanément et traite la requête. La lambda dite « froide » n’est quant à elle pas encore chargée en mémoire et a donc besoin d’une étape de réchauffage (warm up). Durant cette étape, AWS charge le code source de la lambda en mémoire (sur un de ses serveurs) et prépare le contexte d’exécution. Pendant environ 20 minutes, la lambda est « chaude », prête à être exécutée. Le réchauffage représente un coût en temps d’exécution, et ce temps est variable. Selon la disponibilité des serveurs AWS, la taille de votre code source et la quantité de code exécuté hors lambda (contexte), cela peut prendre jusqu’à 3 secondes pour réchauffer votre lambda (dans les pires cas). Potentiellement, votre code pourra prendre plusieurs dixièmes de seconde, voire plusieurs secondes avant de commencer à s’exécuter. Dans le cas d’une requête HTTP, plusieurs secondes peuvent paraître une éternité pour l’utilisateur qui attend le retour de la requête. Attention donc à optimiser votre code en conséquence et à être conscient de cette contrainte. Il existe des solutions pour la contourner, même si de mon point de vue, la meilleure solution est de s’en accommoder et de l’anticiper (en adaptant notamment l’ergonomie de votre application).
Pour parler un peu plus de notre expérience
Afin de vous apporter des pistes de réflexion dans votre modélisation, je partage quelques pratiques que nous avons mises en place (après différentes expérimentations).Il ne s’agit pas d’une architecture-type qui marche dans tous les cas, simplement de l’architecture que nous avons mise en place avec les contraintes techniques et métier que nous avons. Nous utilisions la lambda comme un point d’API REST : chaque point d’API et chaque verbe HTTP déclenche une lambda différente. Nous avons opté pour le langage Typescript, transpilé en Javascript, avant l’envoi du code source sur la plateforme Amazon. Ensuite, nous avons créé un ensemble de librairies Javascript, dont nous gérons les versions et les dépendances grâce à Lerna et Yarn. Le code source de nos lambdas est ainsi regroupé par ensemble métier (import des modèles de données, et services communs par exemple), puis nous importons certaines librairies communes à toute l’application. En termes de sécurité et d’authentification, nous nous sommes basés sur les règles standard d’API Gateway et avons opté pour un modèle avec une lambda d’identification.Celle-ci est appelée par les autres lambdas d’API de l’application (elles-mêmes invoquées par appels HTTP) pour connaître l’utilisateur actuellement connecté. Ainsi, en fonction des en-têtes HTTP de la requête, notre lambda d’identification retourne à la lambda d’API les informations à propos de l’utilisateur (récupérées depuis une base de données).
CONSEIL NUMÉRO 2 : LE CHOIX DU STOCKAGE DE DONNÉES
La grande majorité des applications que vous allez développer ont besoin de stocker et de manipuler des données. Selon le type de données que vous souhaitez manipuler vous avez plusieurs solutions techniques possibles. Mon conseil à ce sujet est de bien analyser votre besoin, de conceptualiser votre modèle de données et de choisir en conséquence votre système de stockage.
Apprendre à ses dépends
Lors des premières phases de développement de notre application, nous avons été confrontés à ce problème de choix de système de stockage. Suivant les avis et les bonnes pratiques concernant les lambdas, nous nous étions orientés vers la solution DynamoDB pour vraiment mesurer les impacts. DynamoDB semblait être l’outil idéal : il fonctionne en architecture serverless, tout comme les lambdas, les coûts d’infrastructure sont donc dérisoires. Il apporte des garanties de disponibilité ainsi que des temps de réponse et une vitesse de transfert plus qu’intéressants. Il est aussi capable de gérer la mise à l’échelle dynamiquement (moyennant quelques paramétrages) et ainsi absorber des pics de trafic. Par contre, il n’est pas adapté à tous les besoins !
DynamoDB est un moteur de base de données NoSQL non relationnel. Il permet de stocker et manipuler facilement des modèles de données, normés ou non, représentés par des objets JSON. Dans notre architecture, chaque domaine métier, composé d’un ensemble de lambdas servant de point d’API, est maître de ses propres données. Dans cette optique, nous avons créé une ou plusieurs tables DynamoDB par domaine métier pour stocker ces données. Le problème arrive lorsqu’il s’agit de créer des jointures entre ces données.
Notre modèle de données métier étant relationnel, nous avions besoin de définir des relations entre nos différentes données (provenant de domaines métiers différents). Malheureusement, les bases de données NoSQL ne sont pas conçues pour ce genre de besoin. Nous avons donc été confrontés à de gros problèmes de performance. Lorsqu’une lambda a besoin d’agréger des données de plusieurs domaines métiers, elle doit nécessairement aller requêter d’autres lambdas pour leur demander des informations. Imaginez donc une lambda qui récupère 10 lignes. Pour chacune, elle doit récupérer les jointures sur deux autres modèles métiers. Elle devra alors faire 20 appels de lambda pour récupérer les données ou requêter deux lambdas et récupérer des regroupements de données qu’il faut ensuite manipuler pour reformer les données. Cela ne peut pas fonctionner.
Les temps d’appels sont alors longs, la limite de la quantité de données retournée par les requêtes DynamoDB est rapidement atteinte ; autant vous dire que c’est un vrai casse-tête. Dans le cas d’un modèle de données relationnel, nous vous conseillons donc d’utiliser un système de base de données relationnel classique (Postgres, Oracle, Mysql), que vous pourrez éventuellement héberger avec Amazon RDS. C’est le parti que nous avons finalement pris, en mettant en place une base de données relationnelle par domaine métier, puis en créant des tables de copies des relations nécessaires. Ainsi, chaque domaine métier reste responsable de ses données, on fonctionne donc en SSOT.
Le domaine métier propriétaire d’une donnée notifie alors les autres domaines d’une modification afin que ceux-ci puissent répercuter les modifications s’ils ont besoin dans leur copie locale des données. Nous avons ainsi conservé les principes de propriété des données comme préconisé par les bonnes pratiques micro-service, et conservé l’indépendance de chaque domaine métier. Il peut fonctionner seul, grâce aux copies des données distantes qu’il conserve (s’il en a besoin pour fonctionner).
CONSEIL NUMÉRO 3 : ATTENTION AUX VPC
Il est fréquent d’avoir des demandes fortes en sécurité dans la mise en place de votre application. Notamment si vous utilisez d’autre services et d’autres outils qui ont besoin d’être hébergés sur une instance EC2 (service de calculs, envoi d’emails, logiciels statefull, ...), vous aurez besoin de sécuriser les accès à ces services. Amazon propose pour cela le service Amazon VPC. Cet ensemble d’outils de configuration réseau et sécurité permet de créer votre sous-réseau privé, d’y ajouter vos ressources, et d’y appliquer toutes les sécurités qu’il vous plaira (Firewall, IP fixes, connexion sécurisée SSH, ...). Le service VPC est très complet et très avancé.
En revanche, combiné avec l’utilisation des lambdas, il peut vite se transformer en cauchemar. En effet, si vous placez une ou plusieurs fonctions Lambda à l’intérieur d’un VPC, vous obligez Amazon à mettre en place un mécanisme d’ouverture de flux automatique pour qu’il puisse déclencher votre lambda : à chaque instanciation d’une lambda, (lors de la phase de réchauffage), Amazon crée une Gateway réseau dynamiquement, permettant d’ouvrir l’accès à votre lambda depuis l’extérieur de votre VPC. Ce n’est pas forcément gênant en soi. Par contre, la création de cette interface réseau est très coûteuse en temps : Amazon met près de 8 secondes à créer cette interface réseau. Aussi, vous vous retrouvez avec des appels de lambda qui prennent (pour la première instanciation) 8 à 10 secondes. Si votre lambda fait elle-même appel à une autre en passant par un appel HTTP, vous augmentez potentiellement le temps d’exécution de 8 secondes supplémentaires.
Le temps de réchauffage des lambdas peut être accepté et anticipé ergonomiquement, mais si votre utilisateur doit attendre entre 15 et 20 secondes pour avoir la réponse à sa requête, il risque fort d’être parti avant d’avoir eu le résultat qu’il espérait. Réfléchissez donc bien avant de choisir le modèle Lambda et le VPC, à l’architecture que vous désirez en connaissance de cause. Posez-vous donc la question : « Est ce qu’un temps de chargement de plusieurs secondes (ou plusieurs dizaines de secondes) est acceptable dans mon cas d’utilisation ? »
Des solutions potentielles
Si vous décidez tout de même d’utiliser Amazon Lambda et Amazon VPC ensemble, voici quelques pistes de fonctionnement qui pourraient vous permettre de contourner (partiellement) ce problème.
- Réchauffer les lambdas systématiquement. Il est possible de réchauffer vos lambdas de manière automatique : le but est de garder toujours vos lambdas chaudes à l'intérieur de votre VPC. Pour cela, vous utilisez un script faisant des appels fictifs de manière régulière (toutes les 20 minutes au maximum). Ainsi vous vous libérez du temps de chauffe de 8 secondes. Mais vous perdrez les principaux intérêts de la lambda, à savoir les coûts et les possibilités de mise à l'échelle (il faut 8s pour chaque nouvelle instance de votre lambda qu’AWS doit créer pour absorber la montée en charge). Dans le cadre de notre projet, nous avons opté pour cette solution, qui nous a permis, dans un premier temps, de palier le problème sans revoir entièrement l'architecture de l’application.
- Lambda passerelle. Vous pouvez également sortir vos lambdas publiques du VPC, puis créer une lambda « passerelle » (ou utiliser une instance EC2 passerelle) qui sera dans le réseau privé VPC, avec une interface réseau ouverte de manière constante. Ainsi vos lambdas publiques pourront interroger cette passerelle avec un système de sécurité que vous définirez, et accéder aux ressources privées de votre VPC.
Vous perdez cependant l’avantage de la montée en charge automatique des lambdas, puisque vous devez garder votre lambda passerelle chaude. Toutefois, si Amazon nécessite une nouvelle instance de cette lambda passerelle, il devra à nouveau créer une interface réseau dynamique, et le premier appel à cette nouvelle instance sera plus long.
Globalement, notre expérience avec Amazon Web Services a été plutôt concluante. Amazon propose un ensemble d'outils performants et, pour la grande majorité, bien pensés. Néanmoins, il est important d’appréhender les choix techniques et de s'assurer que les solutions choisies correspondent bien à un réel besoin. Nous conseillons donc vivement de prendre le temps de modéliser votre architecture et de valider le fonctionnement de votre vision au travers d’une preuve de concept (POC).
Article initialement publié en anglais sur le blog Behind the Code de Welcome To The Jungle