⚙️ Pulumi – Infrastructure as Code programmable
Guide IDEO-Lab pour concevoir, déployer et automatiser ton infrastructure Cloud avec Pulumi (AWS, Azure, GCP, Kubernetes…) en utilisant de “vrais” langages (TypeScript, Python, Go, .NET).
Vue d’ensemble Pulumi
Concept, différences vs Terraform, projets / stacks / state.
IaC Multi-Cloud ProgrammableInstallation & comptes
CLI Pulumi, backends de state, login Pulumi Cloud ou self-hosted.
CLI StatePremier projet & stack
pulumi new, structure d’un projet, gestion des stacks (dev / prod).
pulumi new StacksLangages & SDK
TypeScript, Python, Go, .NET – avantages, patterns de code.
TS Python GoProviders & écosystème
AWS, Azure, GCP, Kubernetes, Cloudflare, GitHub, DBs…
AWS Azure K8sState & backends
Pulumi Cloud, S3, Azure Blob, GCS. Organisation des environnements.
State BackendsPulumi + Kubernetes
Cluster, namespaces, Helm charts, déploiement applicatif.
K8s Helm3.2 Stack AWS IDEO-Lab
VPC + Subnets + Security Groups + S3 + EC2 (exemple complet).
Exemple AWSPulumi vs Terraform
Forces/faiblesses, quand choisir l’un ou l’autre, coexistence.
Terraform ComparatifCI/CD & pipelines
GitHub Actions, GitLab CI, protections sur les previews.
CI/CD Preview4.2 Sécurité & Policies
Secrets, encryption, Policy as Code (CrossGuard, OPA).
Security PoliciesCheat-sheet Pulumi
Commandes & patterns à retenir.
pulumi up pulumi stackPulumi en bref
Pulumi est une plateforme d’Infrastructure as Code (IaC) qui permet de décrire ton infrastructure Cloud avec des langages généralistes (TypeScript, Python, Go, C#, F#…).
Au lieu d’un DSL comme HCL, tu utilises ton langage habituel :
- Conditions, boucles, fonctions, classes, modules, tests unitaires…
- IDE complet (IntelliSense, refactorings, navigation, LSP).
- Réutilisation de code entre infra et application.
Principe clé : “Infra = logiciel”
L’infra n’est plus un ensemble de fichiers YAML/HCL figés, mais un vrai projet logiciel :
# Exemple minimal en TypeScript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("ideolab-bucket", {
acl: "private",
versioning: { enabled: true },
});
export const bucketName = bucket.id;
On peut factoriser, créer des libs internes, partager des composants “infrastructure” comme on le fait déjà pour du code applicatif.
Projects & Stacks
Pulumi Project
├── Pulumi.yaml # Métadonnées du projet (nom, runtime…)
├── Pulumi.dev.yaml # Config de stack “dev”
├── Pulumi.prod.yaml # Config de stack “prod”
├── index.ts / __main__.py # Code infra principal
└── infra/
├── network.ts # VPC, Subnets, Security Groups…
├── database.ts # RDS / Aurora / CosmosDB…
└── k8s.ts # Cluster & workloads Kubernetes
- Project : unité logique de code (repo, module).
- Stack : instance de ce project (dev, staging, prod, client-A…)
- State : représentation de l’infra déployée (ressources, outputs).
Cycle de vie d’un pulumi up
dev écrit du code infra (TypeScript / Python / Go / C#)
│
▼
pulumi preview # Plan d’exécution (diff)
│
▼
pulumi up # Création / update des ressources
│
▼
State mis à jour # Pulumi Cloud ou backend S3/Azure/GCS
│
▼
pulumi stack output # Récupération des outputs (URL, IDs…)
Le process ressemble énormément à Terraform (plan/apply), mais piloté par du code applicatif classique.
Cas d’usage DevOps typiques
- Plateforme SaaS multi-client : une stack par client, logique de provisioning dynamique.
- Infra d’équipe data / AI : clusters Kubernetes + buckets + data lakes + jobs batch.
- Landing zone multi-cloud : standardiser VPC, comptes, IAM, monitoring sur plusieurs providers.
Pourquoi Pulumi plaît aux devs
- Même langage pour app + infra.
- Facilité pour intégrer dans les monorepos.
- Tests unitaires sur l’infra (ex.
pytest,jest). - Possibilité de générer l’infra à partir de données (boucles, mapping JSON, YAML, API…).
Positionnement vs Terraform (vue macro)
| Critère | Pulumi | Terraform |
|---|---|---|
| Langage | TypeScript, Python, Go, .NET | HCL (DSL maison) |
| Courbe d’apprentissage | Très simple pour devs applicatifs | Très simple pour profils infra/cloud |
| Réutilisation de code | Excellente (fonctions, libs, tests…) | Modules et variables (plus limité) |
| Écosystème | Moderne, multi-cloud, cross-SaaS | Ultra-massif, standard de facto |
| Cas favoris | Plateformes “Infra as Software” complexes | Infra Cloud “classique” très standardisée |
Linux / macOS
curl -fsSL https://get.pulumi.com | sh
# Ajouter au PATH si besoin
export PATH=$PATH:$HOME/.pulumi/bin
pulumi version
Windows (PowerShell)
choco install pulumi # via Chocolatey
# ou
scoop install pulumi
SDKs (langages)
# TypeScript / JavaScript
npm install @pulumi/pulumi @pulumi/aws
# Python
pip install pulumi pulumi-aws
# Go
go get github.com/pulumi/pulumi/sdk/v3
# .NET
dotnet add package Pulumi
Le runtime est déterminé par le champ runtime dans Pulumi.yaml (nodejs, python, go, dotnet…).
Pulumi Cloud (par défaut)
Backend managé (SaaS), gère :
- Stockage du state, historique, audit.
- Prévisualisations, review de changements.
- Intégration CI/CD, RBAC, secrets chiffrés.
pulumi login # ouvre le navigateur
# ou
pulumi login https://app.pulumi.com
Backends self-managed
Utile si tu veux garder le state dans ton infra :
# Backend S3
pulumi login s3://ideolab-pulumi-state
# Backend Azure
pulumi login azblob://pulumi-state
# Backend GCS
pulumi login gs://pulumi-state
Dans ce cas, tu dois gérer toi-même sécurité, backups, lifecycle du bucket de state.
Créer son premier projet
mkdir ideolab-infra
cd ideolab-infra
pulumi new aws-typescript # ou aws-python, azure-python, kubernetes-go...
# Pulumi pose des questions (nom de project, stack, région…)
Configuration & secrets
# Config non sensible
pulumi config set aws:region eu-west-1
# Secret (mot de passe DB, token API…)
pulumi config set db:password super-secret --secret
# Lister la config de la stack active
pulumi config
Les secrets sont chiffrés côté backend. Pulumi peut aussi utiliser une clé KMS / KeyVault / etc. selon le backend.
Fichiers clés
Pulumi.yaml # Nom du projet, runtime, description
Pulumi.dev.yaml # Config propre à la stack “dev”
Pulumi.prod.yaml # Config propre à la stack “prod”
index.ts # Code principal (entrypoint) TS
infra/
network.ts # VPC, subnetting
app.ts # Load balancer, instances, lambdas
observability.ts # CloudWatch / Grafana / Logs
Extrait de Pulumi.yaml
name: ideolab-pulumi-aws
runtime: nodejs
description: "Infra IDEO-Lab AWS avec Pulumi (VPC + EC2 + RDS)"
backend:
url: s3://ideolab-pulumi-state
On peut facilement splitter le code en modules (network, db, k8s…) pour qu’un gros projet reste maintenable.
index.ts (entrypoint)
import "./infra/network";
import "./infra/app";
// ou créer les ressources directement ici :
/*
const vpc = new aws.ec2.Vpc("main-vpc", { ... });
...
*/
Module réutilisable
import * as aws from "@pulumi/aws";
export function createAppBucket(name: string) {
const bucket = new aws.s3.Bucket(name, {
versioning: { enabled: true },
serverSideEncryptionConfiguration: {
rule: { applyServerSideEncryptionByDefault: { sseAlgorithm: "AES256" } },
},
});
return bucket;
}
// index.ts
const staticBucket = createAppBucket("ideolab-static");
Structure Python
.
├── __main__.py
└── ideolab_infra/
├── __init__.py
├── network.py
└── app.py
__main__.py
import pulumi
from ideolab_infra.network import network_id
from ideolab_infra.app import app_url
pulumi.export("network_id", network_id)
pulumi.export("app_url", app_url)
Créer / sélectionner une stack
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
pulumi stack select dev
pulumi config set env dev
Organisation recommandée
- 1 project par domaine fonctionnel (ex: core-infra, data-platform…)
- Stacks alignées sur les environnements (dev / qa / prod / client-X…)
- State séparé par org / projet / stack pour limiter les blast radius.
Choisir son runtime
| Langage | Avantages |
|---|---|
| TypeScript | Excellent tooling, typage, écosystème Node, idéal monorepo. |
| Python | Ultra simple pour scripts DevOps / Data / ML. |
| Go | Rapide, binaire unique, très apprécié en infra. |
| .NET (C#, F#) | Parfait dans les shops Microsoft / Azure-centrés. |
Pattern “component” réutilisable
// TypeScript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export class WebApp extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
super("ideolab:component:WebApp", name, {}, opts);
const bucket = new aws.s3.Bucket(`${name}-static`);
const site = new aws.s3.BucketWebsiteConfigurationV2(`${name}-site`, {
bucket: bucket.id,
indexDocument: { suffix: "index.html" },
});
this.url = pulumi.interpolate`http://${bucket.websiteEndpoint}`;
this.registerOutputs({ url: this.url });
}
}
On peut packager ces composants dans des libs internes (npm, wheel, module Go…) pour les réutiliser sur tout IDEO-Lab.
Clouds principaux
| Provider | Package |
|---|---|
| AWS | @pulumi/aws, pulumi-aws |
| Azure | @pulumi/azure-native |
| GCP | @pulumi/gcp |
| Kubernetes | @pulumi/kubernetes |
Au-delà du Cloud “classique”
- Cloudflare (DNS, WAF, CDN).
- GitHub / GitLab (repos, teams, permissions).
- DBaaS (MongoDB Atlas, Neon, etc.).
- Monitoring & logs (Datadog, New Relic…).
Idéal pour gérer à la fois l’infra “physique” (VPC, K8s) et les couches services / SaaS autour (CI/CD, observabilité).
Ce que contient le state
- Liste des ressources créées (type, nom, ID provider).
- Valeurs d’inputs et d’outputs.
- Versioning des déploiements, historique.
pulumi stack export > state.json
# snapshot JSON complet de la stack
Bonnes pratiques d’organisation
- Un org Pulumi par entreprise (ex:
ideolab). - Projets alignés sur les domaines (infra-core, data-platform…).
- Stacks par environnement / client.
- Backups réguliers des buckets de state si backend S3/Azure/GCS.
Déployer un manifest K8s
import * as k8s from "@pulumi/kubernetes";
const appLabels = { app: "ideolab-api" };
const deployment = new k8s.apps.v1.Deployment("ideolab-api", {
spec: {
selector: { matchLabels: appLabels },
replicas: 3,
template: {
metadata: { labels: appLabels },
spec: {
containers: [{
name: "api",
image: "ghcr.io/ideolab/api:latest",
ports: [{ containerPort: 8000 }],
}],
},
},
},
});
Helm charts avec Pulumi
const nginxIngress = new k8s.helm.v3.Chart("ingress", {
chart: "ingress-nginx",
version: "4.10.0",
fetchOpts: { repo: "https://kubernetes.github.io/ingress-nginx" },
values: {
controller: {
replicaCount: 2,
service: { type: "LoadBalancer" },
},
},
});
Pulumi devient un “orchestrateur d’infra” complet : Cloud + cluster + workloads, avec un seul pulumi up.
Network + S3 (TypeScript)
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("ideolab-vpc", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
});
const publicSubnet = new aws.ec2.Subnet("ideolab-public", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
mapPublicIpOnLaunch: true,
});
const bucket = new aws.s3.Bucket("ideolab-artifacts", {
versioning: { enabled: true },
});
EC2 bastion + outputs
const sg = new aws.ec2.SecurityGroup("bastion-sg", {
vpcId: vpc.id,
ingress: [{
protocol: "tcp", fromPort: 22, toPort: 22,
cidrBlocks: ["0.0.0.0/0"], // à restreindre en prod !
}],
egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
});
const bastion = new aws.ec2.Instance("bastion", {
ami: "ami-0c02fb55956c7d316", // Ubuntu (ex.)
instanceType: "t3.micro",
subnetId: publicSubnet.id,
vpcSecurityGroupIds: [sg.id],
keyName: "ideolab-key",
});
export const bastionIp = bastion.publicIp;
export const bucketName = bucket.id;
export const vpcId = vpc.id;
Ces outputs sont visibles via pulumi stack output (et via Pulumi Cloud si tu l’utilises).
Quand Pulumi est particulièrement intéressant
- Tu as déjà des équipes très dev-oriented (TS / Python / Go).
- Tu veux factoriser un max le code infra, faire des libs internes.
- Tu as des stacks dynamiques (multi-clients, multi-regions) calculées à partir de données.
- Tu veux tester ton infra comme du code (TDD infra).
Coexistence possible
Tu peux très bien garder Terraform sur certains périmètres (legacy, équipes déjà outillées) et introduire Pulumi pour les nouvelles plateformes :
- Terraform pour la “landing zone” globale.
- Pulumi pour les workloads applicatifs / K8s / SaaS.
- Interop via les IDs de ressources (VPC existant, subnets, etc.).
GitHub Actions (exemple)
name: Pulumi Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pulumi/actions@v4
with:
command: up
stack-name: ideolab/dev
env:
PULUMI_ACCESS_TOKEN: $
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
AWS_REGION: eu-west-1
Bonnes pratiques
- 1 job “preview” (PR) + 1 job “deploy” (merge sur main).
- Token Pulumi en secret CI, jamais commité.
- Utiliser des stack tags pour tracer l’origine des déploiements.
- Logs centralisés vers CloudWatch / Datadog / ELK.
Gestion des secrets
# En CLI
pulumi config set db:password super-secret --secret
# En TypeScript
const dbPassword = pulumi.secret(
pulumi.config.require("db:password")
);
# Propager au provider
const db = new aws.rds.Instance("db", {
username: "ideolab",
password: dbPassword,
...
});
Policy as Code (CrossGuard)
CrossGuard permet de définir des politiques de sécurité (Node/TS) qui s’appliquent aux stacks :
// Exemple de règle simplifiée
policy.resourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => {
if (!bucket.versioning || !bucket.versioning.enabled) {
reportViolation("Les buckets S3 doivent avoir le versioning activé.");
}
});
Ces règles peuvent être intégrées dans les pipelines CI/CD pour bloquer un pulumi up non conforme (sécurité, coûts, best practices).
Commandes essentielles
pulumi new aws-typescript # init projet
pulumi stack ls # lister les stacks
pulumi stack select dev # sélectionner une stack
pulumi config set key value # config
pulumi config set key value --secret # secret
pulumi preview # plan
pulumi up # apply
pulumi destroy # détruire
pulumi stack output # lire les outputs
Debug & inspection
pulumi stack export > state.json # snapshot JSON
pulumi logs # logs de la dernière opération
pulumi whoami # organisation / user
Tu peux ajouter un petit alias shell par environnement (ex: pud pour “pulumi up dev”, pup pour prod avec confirmation renforcée).
