Découvrir les Cloud Native Languages avec Pulumi
Le déploiement dans le cloud des multiples briques distribuées qui constituent une application cloud native est une tâche complexe. Les Cloud Native Languages dont fait partie Pulumi existent avant tout pour faciliter les déploiements dans le cloud. Cet article permet de découvrir de quelle manière Pulumi amène la force des langages de programmation dans le monde de l’Infrastructure as Code.
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; }
De nombreux outils permettant de déployer et de gérer cette infrastructure dans le cloud existent actuellement. Parmi les plus populaires, on retrouvera AWS CloudFormation ou encore Terraform que vous pouvez découvrir sur notre blog.
Depuis 2018, une nouvelle catégorie d’outils pour le déploiement émerge. Ces outils forment la toute nouvelle gamme des Cloud Native Languages. L’approche novatrice prise par ces outils est de mettre les langages de programmation connus au cœur de l’outillage. Il devient alors possible de faire de l'Infrastructure as Code directement dans le langage de son choix plutôt que d’utiliser du templating YAML ou JSON rapidement verbeux ou encore un DSL qui évolue lentement.
L’utilisation des langages de programmation classiques permet d’utiliser des concepts éprouvés pour organiser son code et pour le tester tout en permettant de profiter de l'écosystème souvent riche de ces langages. De plus, les langages populaires sont mieux connus et maîtrisés par les développeurs que les outils d’Infrastructure as Code classiques ce qui permet une montée en compétence plus rapide.
Les Cloud Native Languages se multiplient rapidement et parmi les outils récents, on retrouvera MetaParticle qui se focalise sur Kubernetes, Go Cloud qui intègre des constructions cloud directement dans le SDK de Go, AWS CDK qui permet d’abstraire les templates CloudFormation ou encore Ballerina qui est un tout nouveau langage qui se focalise entièrement sur le cloud et les microservices.
Cet article se focalise sur Pulumi car c’est l’outil le plus complet actuellement. Les autres outils se focalisent sur des cas d’utilisation bien précis et délimités.
Pulumi est un outil en ligne de commande open source développé depuis fin 2016 et dévoilé au public en juin 2018. Pulumi permet de créer, déployer et gérer une application cloud native et son infrastructure en utilisant un langage de programmation tel que Node.js, Go ou encore Python.
What’s Pulumi
Le rôle principal de Pulumi est le déploiement de l’infrastructure décrite par un développeur dans un programme. Les possibilités sont nombreuses, il est ainsi possible de déployer des conteneurs, un cluster Kubernetes, des machines EC2 ou encore une application Serverless avec son API et ses fonctions Lambda. Pulumi se revendique multi-cloud, il est donc possible de déployer ces ressources sur AWS, GCP ou encore Azure. Mais pour déployer toutes ces ressources cloud, il est nécessaire pour Pulumi de savoir interagir avec les APIs des clouds providers. Pour pouvoir facilement comprendre le fonctionnement de Pulumi, il est nécessaire de rappeler comment Terraform fonctionne.
Fonctionnement interne de Terraform
Du côté de Terraform, le fonctionnement est simple. Le Terraform Core est un binaire statique écrit en Go et capable de communiquer avec des plugins (qui sont eux aussi des binaires Go) en utilisant le protocole RPC de Google. Les données échangées par gRPC seront encodées au format binaire avec Protobuf. Il y a deux catégories de plugins, les Providers et les Provisioners. Les Provisioners permettent d'exécuter des scripts lors de la création ou la destruction des ressources déployées grâce aux Providers. Un Provisionner pourrait permettre, par exemple, d’exécuter un script pour nettoyer un état avant la destruction d’une ressource. Dans cet article, nous nous intéressons uniquement aux Providers.
La séparation nette entre le core de Terraform et les plugins permet à chacun d’avoir des responsabilités bien définies. Le core de Terraform est ainsi responsable de :
- lire et interpréter les fichiers de configuration et les modules (Infrastructure as Code)
- Gérer l’état des ressources
- Construire le graphe des ressources
- Planifier l’exécution
- Communiquer en RPC avec les Plugins
Tandis que les Providers de Terraform s’occupent de :
- Initialiser des librairies permettant de réaliser les appels API vers les fournisseurs d’infrastructure
- Authentifier auprès des fournisseurs d’infrastructure
- Définir, au travers d’un schéma, des ressources qui correspondent aux différents services du fournisseur
- Implémenter une API CRUD basique réalisant les appels API pour chaque ressource
Cette séparation franche est un facteur clef pour l’abondance des Providers Terraform et participe donc à la bonne santé de l’écosystème global de l’outil. Il est très simple de créer un nouveau Provider car cela ne nécessite pas de connaissances sur la théorie des graphes ou encore sur la manière dont le Core communique en RPC. La difficulté principale lors de la réalisation d’un Provider réside donc dans la réalisation d’un schéma cohérent capable de masquer les potentielles complexités au sein des APIs.
Un exemple typique des interactions entre les différentes briques composant Terraform est visualisable en prenant l’exemple de la commande terraform plan.
Maintenant que le fonctionnement interne de Terraform n’a plus de secrets pour nous, il sera plus facile de comprendre la logique derrière Pulumi.
Fonctionnement interne de Pulumi
Si l’on adopte une vision un peu haut niveau, on se rend compte que le fonctionnement général de Pulumi est très proche de celui de Terraform. L’engine de Pulumi est un peu l’équivalent du core de Terraform et c’est cet engine qui viendra interagir avec les Pulumi Providers. L’engine et les Providers de Pulumi sont aussi des binaires Go. La suite de l’article détaillera plus en profondeur les spécificités du fonctionnement de Pulumi.
La principale différence entre Pulumi et Terraform réside dans le besoin d’exécution et de compréhension du programme décrivant l’infrastructure. Pour chaque langage supporté par Pulumi il existe donc un Language Host.
Les Language Hosts
Le Language Host est simplement un programme implémentant une interface gRPC avec des données sérialisées au format Protobuf. Chaque Language Host est implémenté dans le langage dont il fournit la fonctionnalité. Concrètement, le Language Host Go est écrit en Go, le Language Host pour JavaScript/TypeScript est écrit en TypeScript, etc.
Un Language Host aura pour rôle de lancer l'exécution du programme pour pouvoir ensuite communiquer avec l’engine de Pulumi sur les ressources que le développeur souhaite déployer. Le Language Host est aussi responsable de la sérialisation des fonctions afin de faciliter le déploiement de code dans des services FaaS comme AWS Lambda.
Il est important de comprendre que le programme écrit par l’utilisateur de Pulumi reflète simplement une intention, une volonté de faire exister les ressources décrites. Le Language Host communique ensuite cette intention à l’engine de Pulumi qui sera chargé de déterminer les opérations à réaliser en fonction de l’état actuel. La suite des opérations est identique à un workflow typique de Terraform. L’engine de Pulumi communique directement en RPC avec ses Providers puis délègue, au besoin, aux Providers de Terraform.
Dans le cadre d’un déploiement de ressources avec un Provider Pulumi basé sur un Provider Terraform (AWS, GCP, Azure, …), la séquence des opérations pour réaliser la commande pulumi refresh est vraiment proche de celle pour réaliser un terraform plan. Le schéma montre bien que le Provider Pulumi appelle directement celui de Terraform pour aller lire l’état actuel des ressources sur le cloud. C’est d’ailleurs le seul point de contact entre Terraform et Pulumi lors de l’exécution de la commande. L’état connu est bien celui qui est managé par l’engine de Pulumi et non pas l’état de Terraform. Il est nécessaire de préciser que Pulumi ne fait jamais appel au core de Terraform, le seul point de contact entre Pulumi et Terraform se trouve au niveau des Providers.
Les Providers Pulumi
Il existe deux types de Providers Pulumi, ceux basés sur des Providers Terraform et les autres. Comme le montre le schéma du fonctionnement interne de Pulumi, les Providers Terraform sont inclus au sein des Providers Pulumi. L’ensemble formé par un Provider Pulumi et un Provider Terraform se retrouve dans un bridge qui permet de connecter ce duo au core de Pulumi.
Concrètement, au niveau du code, le Provider Pulumi implémente une interface possédant à la fois une référence à l_’engine_ de Pulumi pour les communications gRPC et une référence vers le Provider de Terraform.
La Provider Pulumi wrappe donc celui de Terraform ce qui a pour conséquence directe qu’une opération simple comme pulumi refresh n’implique que deux processus, un pour l’engine et un autre pour le Provider.
Les ingénieurs de chez Pulumi ont donc eu la bonne idée de réutiliser les Providers Terraform pour la partie déploiement des ressources ce qui nous permet de retrouver un workflow familier et fonctionnel. Le fait de wrapper les Providers Terraform permet aussi d’enrichir un peu les fonctionnalités de ces derniers. Cependant, il me reste encore à décrire ce que je trouve le plus intéressant dans la réutilisation des Providers de Terraform: la génération de code grâce à tfgen.
Génération du SDK
J’ai déjà évoqué l’existence d’un schéma pour chaque ressource d’un Provider Terraform. Ce schéma a beaucoup de valeur, car il décrit de manière exacte la configuration de la ressource. Le binaire tfgen va simplement parser le schéma de chacune des ressources d’un Provider Terraform pour ensuite générer un SDK dans chaque langage supporté par Pulumi. Ce SDK contient l’ensemble des interfaces permettant d’écrire un programme Pulumi.
Le SDK généré par tfgen ne contient aucune logique permettant de déployer les ressources. Ce n’est qu’une collection de classes et d’interfaces décrivant les différents paramètres de configuration des ressources.
Les Providers Pulumi non dépendants de Terraform
Pour finir sur le fonctionnement interne de Pulumi, il est nécessaire de rappeler qu’il existe des Providers qui ne sont pas basés sur les Providers Terraform. C’est le cas, par exemple, du Provider Kubernetes de Pulumi. Ce Provider est entièrement indépendant de Terraform. Son fonctionnement reste cependant le même, le Provider est capable de déployer l’ensemble des ressources Kubernetes (Deployment, Service, Namespace, PersistentVolume, …) grâce à l’implémentation d’unCRUD simple en son sein. Le SDK des langages supportés est lui aussi généré automatiquement en parsant la spécification OpenAPI officielle de Kubernetes.
À suivre ...
Le prochain article démontrera au travers d'un exemple comment Pulumi peut faciliter les déploiements dans l'univers de Kubernetes.