Pulumi, à la découverte du challenger de Terraform

Rédigé par Didier NANDIEGOU le 13/03/2023
Temps de lecture: 8 minutes

Introduction

La démocratisation des pratiques DevOps en entreprise depuis quelques années a apporté son lot d’outils d’automatisation permettant de déployer de l’infrastructure cloud sous la forme de code déclaratif. On parle d’outils d’IAC (Infrastructure As Code) dont le plus célèbre est Terraform. Un des freins à l’adoption Terraform est l’apprentissage du langage HCL (Hashicorp Configuration Language) qui peut faire peur à certains utilisateurs. Un concurrent sur le marché de l’IAC se propose justement de faire tomber cette barrière en permettant aux développeuses et développeurs d’utiliser les langages de développement qu’ils maitrisent déjà.

Pulumi, le challenger qui peut tout changer

Logo Pulumi

C’est ici que Pulumi entre en jeu. Il s’agit d’un outil d’IAC open-source qui prend en charge plusieurs fournisseurs de Cloud public et privé comme AWS, AZURE ou OpenStack mais aussi Kubernetes. Il permet de déployer des ressources d’infrastructure en utilisant les langages standards JavaScript, TypeScript, .Net, Python et Go. L’avantage étant d’offir un confort d’utilisation aux développeuses et développeurs mais aussi la possibilité d’utiliser les structures telles que les fonctions, les boucles et les conditions.

Dans cet article, je vous propose d’explorer l’outil et de le comparer à Terraform. Attachez vos ceintures, on décolle !

1. Fonctionnement de Pulumi

Gestion des déploiements

Pulumi se base sur une notion d’état qui correspond aux ressources souhaitées sur l’infrastructure. Lorsque le programme Pulumi est exécuté avec un langage spécifique, le moteur de déploiement compare cet état souhaité avec l’état actuel des ressources et détermine quelles ressources doivent être créées, mises à jour ou supprimées. Le moteur utilise un ensemble de fournisseurs (tels qu’AWS, Azure, Kubernetes, etc.) afin de gérer ces ressources. Le moteur met alors à jour l’état de l’infrastructure avec des informations sur toutes les ressources qui ont été provisionnées ainsi que sur les opérations en attente.

Gestion des déploiements pulumi

Le Language Host

C’est le composant qui détermine le runtime a utiliser en fonction du langage de développement et transmettre au Deployment engine les ressources déclarées dans le code.

Le Deployment engine

Le deployment engine est le coeur de Pulumi. Il s’occupe de la gestion du state et s’assure que l’architecture déployée correspond bien à celui ci. C’est aussi le composant qui va utiliser le resource provider défini pour communiquer avec l’API du cloud provider où l’on déploie son infrastrucure.

Les resource providers

Ils permettent à Pulumi de créer, modifier et supprimer des ressources sur les providers compatibles. Les SDK disponibles sont utilisés pour effectuer les actions sur les ressources que le provider est capable de gérer.

2. Structure d’un projet Pulumi

Un projet Pulumi est composé de 3 fichiers principalement:

Pulumi.yaml : contient les métadonnées qui sont en fait la configuration du projet.

1name: aws-py-ec2-provisioners
2runtime:
3  name: python
4  options:
5    virtualenv: venv
6description: An example of manually configuring an AWS EC2 virtual machine

Pulumi.dev.yaml : contient la configuration d’un environnement ou stack. Cette configuration est donc spécifique à un environnement.

1config:
2  aws:region: eu-west-3
3  name: stack-dev

Le fichier de code: le code du programme Pulumi qui définit les ressources de la stack. En python ce sera par exemple le fichier main_.py.

3. Pulumi par la pratique

Dans cette section, je vais déployer une simple instance EC2 sur laquelle sera copié un fichier ‘myapp.conf’ et un Security Group sur AWS.

Installation

Pulumi peut être installé sur Windows, Mac et Linux. Il faut bien sur avoir déjà installé le runtime du langage souhaité et connecter pulumi avec le provider Cloud.

1$ curl -fsSL https://get.pulumi.com | sh
1$ pip install pulumi_aws

Configuration

Après installation, je récupère un exemple sur le dépot Github de Pulumi puis j’initie la stack.

pulumi init

Pulumi utilise directement les variables d’environnement pour récupérer les credentials AWS.

1$ export AWS_ACCESS_KEY_ID=<ACCESS_KEY_ID>
2$ export AWS_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY>
3$ export AWS_REGION=<AWS_REGION>

Dans le cas où vous utilisez les profiles avec plusieurs comptes AWS, vous pouvez utiliser la variable d’environnement

1$ export AWS_PROFILE=<PROFILE_NAME>

Premier déploiement Pulumi

Pour notre exemple, voici le template définissant les ressources :

 1import pulumi
 2import pulumi_aws as aws
 3import pulumi_command as command
 4import base64
 5
 6# Get the config ready to go.
 7config = pulumi.Config()
 8
 9# If keyName is provided, an existing KeyPair is used, else if publicKey is provided a new KeyPair
10# derived from the publicKey is created.
11key_name = config.get('keyName')
12public_key = config.get('publicKey')
13
14# The privateKey associated with the selected key must be provided (either directly or base64 encoded)
15def decode_key(key):
16    try:
17        key = base64.b64decode(key.encode('ascii')).decode('ascii')
18    except:
19        pass
20    if key.startswith('-----BEGIN RSA PRIVATE KEY-----'):
21        return key
22    return key.encode('ascii')
23
24private_key = config.require_secret('privateKey').apply(decode_key)
25
26# Create a new security group that permits SSH and web access.
27secgrp = aws.ec2.SecurityGroup('secgrp',
28    description='Foo',
29    ingress=[
30        aws.ec2.SecurityGroupIngressArgs(protocol='tcp', from_port=22, to_port=22, cidr_blocks=['0.0.0.0/0']),
31        aws.ec2.SecurityGroupIngressArgs(protocol='tcp', from_port=80, to_port=80, cidr_blocks=['0.0.0.0/0']),
32    ],
33)
34
35# Get the AMI
36ami = aws.ec2.get_ami(
37    owners=['amazon'],
38    most_recent=True,
39    filters=[aws.ec2.GetAmiFilterArgs(
40        name='name',
41        values=['amzn2-ami-hvm-*-x86_64-gp2'],
42    )],
43)
44
45# Create an EC2 server that we'll then provision stuff onto.
46size = 't2.micro'
47if key_name is None:
48    key = aws.ec2.KeyPair('key', public_key=public_key)
49    key_name = key.key_name
50server = aws.ec2.Instance('server',
51    instance_type=size,
52    ami=ami.id,
53    key_name=key_name,
54    vpc_security_group_ids=[ secgrp.id ],
55)
56connection = command.remote.ConnectionArgs(
57    host=server.public_ip,
58    user='ec2-user',
59    private_key=private_key,
60)
61
62# Copy a config file to our server.
63cp_config = command.remote.CopyFile('config',
64    connection=connection,
65    local_path='myapp.conf',
66    remote_path='myapp.conf',
67    opts=pulumi.ResourceOptions(depends_on=[server]),
68)
69
70# Execute a basic command on our server.
71cat_config = command.remote.Command('cat-config',
72    connection=connection,
73    create='cat myapp.conf',
74    opts=pulumi.ResourceOptions(depends_on=[cp_config]),
75)
76
77# Export the server's IP/host and stdout from the command.
78pulumi.export('publicIp', server.public_ip)
79pulumi.export('publicHostName', server.public_dns)
80pulumi.export('catConfigStdout', cat_config.stdout)
  • Je crée la clé ssh à utiliser pour accéder à l’instance EC2 (lignes 15-26)
1$ ssh-keygen -t rsa -f rsa -b 4096 -m PEM
2$ cat rsa.pub | pulumi config set publicKey --
3$ cat rsa | pulumi config set privateKey --secret --

La suite du code est assez explicite:

  • Création un groupe de sécurité avec des ouvertur de port en ingress
  • Choix de l’AMI
  • Définition de l’instance à déployer
  • Les commandes à exécuter ensuite sur la machine
  • Définition des outputs de la commande

Pour lancer le déploiement, il faut exécuter la commande suivante:

1pulumi up

La commande pulumi up va compiler le code et déterminer les actions (creation, modification, suppression) à appliquer en fonction du state. On peut observer les informations du péloiement sur la console

pulumi indeploiement console

ou sur l’interface web pulumi.

pulumi deploiement interface web

Cette dernière option est intéressante, puisqu’elle permet d’avoir des détails sur les changements appliqués, les sorties de commandes et offre même une visualisation graphique des ressources déployées.

pulumi graph

Il faut noter que l’interface web offre uniquement une visualisation des ressources, les accès à votre cloud provider restent sur la CLI.

On peut également utiliser la commande pulumi refresh qui permet d’appliquer les changements nécessaires en vérifiant l’état de la stack et en le comparant avec le state.

Nettoyage

Pour détruitre les ressources provisionnées, on utilise la commande suivante:

1pulumi destroy

Pour supprimer la stack sur le backend pulumi, on exécute cette commande:

1pulumi stack rm

4. Pulumi VS Terraform

Gestion des states

Terraform offre la souplesse de stocker le state localement, sur un stockage distant type S3 ou encore en ligne sur Terraform Cloud qui est payant pour les équipes de plus de 5 utilisateurs. De plus, l’utilisation de Terraform Cloud implique d’ajouter les informations de connexion du cloud provider à l’environnement Terraform Cloud. Avec Pulumi, le state est stocké gratuitement sur un compte Pulumi en ligne avec une visualisation détaillée des ressources et des déploiements. Bien que j’apprécie la souplesse de Terraform, il faut avouer que Pulumi gagne cette manche.

Logique de code

Le caractère déclaratif de Terraform a le grand intérêt de produire du code court et lisible mais les problèmes apparaissent très vite dès qu’on veut ajouter de la logique avec des conditions ou des boucles. Avec Pulumi, les développeuses et développeurs écrivent leur code en utilisant les strucures déjà disponibles dans les langages standards ce qui simplifie les choses.

Taille et complexité des projets

La structure des projets Pulumi sous forme de mini projets avec un fichier de code monolythique limite fortement les possibilités. Chaque stack fonctionne indépendament comme des environnements séparés. Il peut devenir complexe voire impossible de faire référence à des ressources dans une stack différente. Terraform permet de découpler les projets en créant des modules ou en utilisant plusieurs fichiers. On peut donc réutiliser les mêmes fichiers pour plusieurs environnements et d’utiliser des modules déjà existants dans le code. Terraform pousse le curseur plus loin en permettant même de faire référence à des ressources provisionnées dans un autre projet ou environnement.

Documentation

La documentation Pulumi est encore limitée si on la compare à celle de Terraform. En dehors du Github Pulumi, on trouve très peu d’exemples de code. Terraform est bien plus documenté et bénéficie de sa large adoption par la communauté qui est d’ailleurs très active. Terraform dispose aussi d’un repository communautaire riche de nombreux modules et d’exemples prêts à l’emploi.

Conclusion

Le projet Pulumi est encore jeune mais il a des qualités indéniables et je pense qu’on verra son utilisation s’étendre dans les années qui viennent. En l’état, déployer de l’infrastructure via Pulumi peut être intéressant pour les petites à moyennes équipes de qui ont besoin de déployer de l’infrastructure avec beaucoup de logique. L’approche consistant à utiliser des langages de programmation standards assure une prise en main assez rapide et la mise en place de tests dans les workflows de déploiement.

Les limitations vont se faire resentir lorsqu’il faudra déployer des architectures plus complexes avec plusieurs environnemts.

Même si Terraform n’est pas toujours évident à aborder, il reste plus adapté surtout pour faire évoluer l’infrastructure et réutiliser le code. La prise en charge des CDK ouvre désormais des persectives d’utilisation de langages comme Python, Java ou Typescript pour provisionner des ressources et pourrait bientôt permettre à Terraform de se défaire de son principal désavantage comparé à Pulumi.

Dans tous les cas, chez Cockpit io, nous garderons un oeil sur Pulumi, un outil d’IAC Opensource qui vaut le détour !