Pulumi par la pratique : Serverless
Cet article est le troisième et dernier de la série sur Pulumi et les Cloud Native Languages. Après nos expérimentations avec Kubernetes, il est temps de s’intéresser au déploiement d’une application Serverless.
body .gist .highlight { background: #202020; } body .gist tr:nth-child(2n+1) { background: #202020; } body .gist tr:nth-child(2n) { background: #202020; } body .gist .gist-meta { display:none; } body .gist .highlight { background: #141414; } body .gist .blob-num, body .gist .blob-code-inner, body .gist .highlight, body .gist .pl-enm, body .gist .pl-ko, body .gist .pl-mo, body .gist .pl-mp1 .pl-sf, body .gist .pl-ms, body .gist .pl-pdc1, body .gist .pl-scp, body .gist .pl-smc, body .gist .pl-som, body .gist .pl-va, body .gist .pl-vpf, body .gist .pl-vpu, body .gist .pl-mdr { color: #aab1bf; } body .gist .pl-mb, body .gist .pl-pdb { font-weight: 700; } body .gist .pl-c, body .gist .pl-c span, body .gist .pl-pdc { color: #5b6270; font-style: italic; } body .gist .pl-sr .pl-cce { color: #56b5c2; font-weight: 400; } body .gist .pl-ef, body .gist .pl-en, body .gist .pl-enf, body .gist .pl-eoai, body .gist .pl-kos, body .gist .pl-mh .pl-pdh, body .gist .pl-mr { color: #61afef; } body .gist .pl-ens, body .gist .pl-vi { color: #be5046; } body .gist .pl-enti, body .gist .pl-mai .pl-sf, body .gist .pl-ml, body .gist .pl-sf, body .gist .pl-sr, body .gist .pl-sr .pl-sra, body .gist .pl-src, body .gist .pl-st, body .gist .pl-vo { color: #56b5c2; } body .gist .pl-eoi, body .gist .pl-mri, body .gist .pl-pds, body .gist .pl-pse .pl-s1, body .gist .pl-s, body .gist .pl-s1 { color: #97c279; } body .gist .pl-k, body .gist .pl-kolp, body .gist .pl-mc, body .gist .pl-pde { color: #c578dd; } body .gist .pl-mi, body .gist .pl-pdi { color: #c578dd; font-style: italic; } body .gist .pl-mp, body .gist .pl-stp { color: #818896; } body .gist .pl-mdh, body .gist .pl-mdi, body .gist .pl-mdr { font-weight: 400; } body .gist .pl-mdht, body .gist .pl-mi1 { color: #97c279; background: #020; } body .gist .pl-md, body .gist .pl-mdhf { color: #df6b75; background: #200; } body .gist .pl-corl { color: #df6b75; text-decoration: underline; } body .gist .pl-ib { background: #df6b75; } body .gist .pl-ii { background: #e0c184; color: #fff; } body .gist .pl-iu { background: #e05151; } body .gist .pl-ms1 { color: #aab1bf; background: #373b41; } body .gist .pl-c1, body .gist .pl-cn, body .gist .pl-e, body .gist .pl-eoa, body .gist .pl-eoac, body .gist .pl-eoac .pl-pde, body .gist .pl-kou, body .gist .pl-mm, body .gist .pl-mp .pl-s3, body .gist .pl-mq, body .gist .pl-s3, body .gist .pl-sok, body .gist .pl-sv, body .gist .pl-mb { color: #d19965; } body .gist .pl-enc, body .gist .pl-entc, body .gist .pl-pse .pl-s2, body .gist .pl-s2, body .gist .pl-sc, body .gist .pl-smp, body .gist .pl-sr .pl-sre, body .gist .pl-stj, body .gist .pl-v, body .gist .pl-pdb { color: #e4bf7a; } body .gist .pl-ent, body .gist .pl-entl, body .gist .pl-entm, body .gist .pl-mh, body .gist .pl-pdv, body .gist .pl-smi, body .gist .pl-sol, body .gist .pl-mdh, body .gist .pl-mdi { color: #df6b75; }
L’outil Pulumi sera nécessaire pour déployer les exemples. Des instructions d’installation sont disponibles à cette adresse : https://pulumi.io/quickstart/install.html. L’ensemble des exemples de code est présent à cette adresse: https://github.com/Tirke/try-pulumi/tree/master/serverless
Les exemples de code fournis ne sont pas adaptés à une utilisation dans un environnement de production. La plupart des exemples utilisent des ressources cloud préconfigurées avec des valeurs raisonnables, mais toutefois éloignées des besoins réels d’un système en production.
Déploiement en Serverless
Pulumi permet de facilement déployer une application Serverless. Dans ce paradigme, la frontière entre le code applicatif et l’infrastructure est relativement perméable. L’approche propre à Pulumi de mélanger infrastructure et applicatif est une alternative novatrice au déploiement d’une architecture Serverless.
Notre exemple permet de déployer, en quelques lignes de code, une table d’une BDD NoSQL, une fonction lambda qui pourra interagir avec cette table et une API qui nous fournira un endpoint HTTP pour déclencher l’exécution de notre lambda. J’ai volontairement omis de préciser le cloud provider pour chacun de ces services, car je vais utiliser des interfaces multi-cloud.
Concrètement, chaque ressource présente dans le package @pulumi/cloud est une interface de haut niveau avec une implémentation déjà existante pour chaque cloud provider supporté. Lorsque l’on décide d’utiliser une ressource du package @pulumi/cloud, le cloud provider à utiliser lors du déploiement, sera choisi en fonction de la valeur du paramètre cloud:provider de la stack actuelle. Chaque programme Pulumi est déployé au sein d’une stack qui contient de la configuration et l’état du déploiement. Les stacks sont souvent utilisées pour exprimer les différents environnements de l’application (dev, staging, production) ou encore des feature branches.
En fonction du provider choisi, new cloud.Table pourra donc être déployée en tant que table AWS DynamoDB, Table Storage sur Azure ou encore DataStore sur GCP. Pour l’instant, la bibliothèque @pulumi/cloud supporte AWS et Azure, le support pour GCP devrait être ajouté prochainement. Pour l’exemple nous utiliserons la valeur aws pour le paramètre cloud:provider, la table sera donc une table DynamoDB, l’API viendra du service API Gateway et les fonctions (FaaS) reliées à l’API du service AWS Lambda.
Le début de notre code est très explicite, on souhaite déployer une table et une API pour pouvoir appeler nos lambdas. La suite du code contient notre lambda métier qui n’est rien d’autre qu’une version moderne du démodé compteur de visiteur, indispensable à tout bon site des années 2000.
cloud.API est un composant haut niveau de Pulumi. Ce composant est le parfait exemple d’un composant contenant à la fois des ressources cloud (ici une API) et des méthodes pour interagir directement avec les ressources sous-jacentes. L’objet api possède ainsi un ensemble de méthodes correspondant aux verbes HTTP classiques. Ces méthodes permettent de déclarer des routes avec des handlers qui seront déployés sur le service FaaS du cloud provider choisi.
Une des particularités de ce handler réside dans ses paramètres d’entrées. Les connaisseurs de Node.js ou du framework Express reconnaîtront les classiques req, res qui correspondent à la requête et à la réponse HTTP. Les paramètres en entrée des fonctions dans les différents services FaaS sont en règle générale bien différents. L’existence de cette interface familière aux habitués du langage Node est entièrement possible grâce aux capacités d’abstraction fournies par Pulumi. Il est tout à fait possible de construire un ensemble de méthodes pour interagir finement avec les ressources cloud puis de packager le tout dans un composant haut niveau avec une API claire et concise.
On retrouve toujours la notion de composant haut niveau avec l’objet counter qui n’est rien d’autre qu’une table de base de données. La table expose les différentes méthodes classiques pour l’insertion, la modification ou la suppression d’une donnée en base. Ces fonctionnalités sont généralement implémentées directement avec le SDK du cloud provider correspondant. Il faut comprendre que l’implémentation concrète sera choisie au moment du déploiement en fonction d’un paramètre indiquant le cloud provider désiré.
Ainsi, il est nécessaire de comprendre qu’avec Pulumi, il existe des dépendances dites de design time et des dépendances qui seront pour le runtime. Cette notion est importante dans une application Pulumi Serverless car le code métier des lambdas est mélangé avec le code d’infrastructure. Et le framework est capable de distinguer les dépendances nécessaires lors de l'exécution de nos fonctions des dépendances qui ne sont nécessaires que lors de la phase de déploiement. Il est important de se souvenir de cette distinction, car selon la manière dont le code est implémenté, les performances de l’outil Pulumi seront plus ou moins bonnes.
Pour faire simple, il est a minima nécessaire de wrapper le code destiné au runtime dans une fonction plutôt que d’avoir une variable dans un scope global. À l’heure actuelle, ce comportement est connu, mais pas encore documenté. C’est un des pièges à éviter lors de l’utilisation de Pulumi dans une approche Serverless.
La commande pulumi update permet de lancer le déploiement de notre application. On obtiendra en sortie le endpoint de notre compteur de visite au format JSON.
N’oubliez pas la commande pulumi destroy -y pour supprimer les différentes ressources à la fin de vos expérimentations.
Ouverture
Un autre outil de déploiement d’applications Serverless est le Framework Serverless. Pulumi est un sérieux concurrent à cet outil déjà populaire et plutôt mature. L’approche langage de programmation permet d’éviter la verbosité du templating YAML.
Le framework Serverless est parfois contraignant notamment dans le cadre d’AWS où le framework est une abstraction de CloudFormation. Malheureusement, il arrive que les contraintes et les écueils typiques à CloudFormation resurgissent lors de l’utilisation du framework Serverless ce qui peut vite devenir handicapant.
Mais ce n’est pas tout, avec les langages de programmation il est plus facile de mettre en place des tests ou encore d’abstraire, de packager et de réutiliser des parties de code comme nous l’avons vu au travers des composants haut niveau du module @pulumi/cloud.
L’approche novatrice de Pulumi est très attractive. Pulumi donne véritablement l’impression que nous sommes en train de réaliser un programme cloud native, un programme qui intègre de manière transparente l’ensemble des services cloud existants.