# Infrastructure as code ne veut pas dire Terraform. CDK, Pulumi, et la question qui décide vraiment

Vers 2019, « infrastructure as code » est devenu synonyme de Terraform dans la plupart des conversations, comme « recherche » veut dire Google. C'est un défaut raisonnable. C'est aussi le mauvais outil pour une part significative des équipes, et la raison n'est pas les fonctionnalités, que les trois options sérieuses ont toutes, mais *qui va écrire et lire le code*.

Nous exploitons nos plateformes clients sur AWS CDK. Nous avons livré du Terraform et du Pulumi pour d'autres clients. Voici la comparaison que nous aurions aimé lire avant de choisir, organisée autour de la question qui décide, avec les compromis énoncés clairement et les cas où nous choisirions encore Terraform.

## La question

**L'infrastructure appartient-elle à l'équipe applicative, ou à une équipe plateforme séparée ?**

Si les personnes qui écrivent l'application Next.js sont celles qui écrivent l'infrastructure, elles ont déjà un langage, un gestionnaire de paquets, un lanceur de tests et un éditeur configurés pour TypeScript. Leur donner un second langage (HCL) avec sa propre sémantique, son propre système de modules et sa propre histoire de test est un coût payé chaque jour par chaque personne. CDK ou Pulumi leur permet de définir l'infrastructure dans le langage dans lequel elles pensent déjà, dans le même dépôt, avec le même processus de revue, et de partager des types entre l'application et l'infra.

Si l'infrastructure appartient à une équipe plateforme qui sert de nombreuses équipes applicatives dans de nombreux langages, le calcul s'inverse. Cette équipe veut un seul langage pour toute l'infrastructure quel que soit le langage des applications, un modèle déclaratif facile à relire sur des centaines de modules, un immense écosystème de providers, et une sortie de plan qu'un non-programmeur peut lire dans un ticket de changement. C'est le terrain de Terraform, et les limites de HCL y sont une qualité : il est difficile d'y être trop malin.

Tout le reste de la comparaison découle de cette réponse.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Embranchement de décision. À qui appartient l'infrastructure ? Équipe applicative qui écrit déjà TypeScript : CDK si tout AWS, Pulumi si multi-cloud ou hors AWS. Équipe plateforme séparée servant de nombreuses équipes et langages : Terraform ou OpenTofu. Sous chaque branche, les raisons : même langage, même dépôt, types partagés contre un langage pour toute l'infra, plans déclaratifs relisibles, plus grand écosystème de providers.">
<defs><marker id="arrI" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#9aa3c7"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<rect x="300" y="20" width="300" height="50" rx="12" fill="#151b2e" stroke="#7b8cff" stroke-width="1.5"/><text x="450" y="50" text-anchor="middle" fill="#f1f3ff" font-weight="700">à qui appartient l'infrastructure ?</text>
<line x1="380" y1="72" x2="230" y2="108" stroke="#9aa3c7" stroke-width="1.5" marker-end="url(#arrI)"/><line x1="520" y1="72" x2="670" y2="108" stroke="#9aa3c7" stroke-width="1.5" marker-end="url(#arrI)"/>
<rect x="40" y="110" width="380" height="120" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="230" y="134" text-anchor="middle" fill="#4fffb0" font-weight="700">à l'équipe applicative</text><text x="230" y="156" text-anchor="middle" fill="#f1f3ff">écrit déjà TypeScript · même dépôt · même revue</text><text x="230" y="176" text-anchor="middle" fill="#f1f3ff">types partagés entre application et infra</text><text x="230" y="204" text-anchor="middle" fill="#4fffb0" font-weight="700">CDK si tout AWS · Pulumi sinon</text><text x="230" y="222" text-anchor="middle" fill="#9aa3c7" font-size="11">un second langage est un coût quotidien pour chaque personne</text>
<rect x="480" y="110" width="380" height="120" rx="12" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="670" y="134" text-anchor="middle" fill="#ffd166" font-weight="700">à une équipe plateforme séparée</text><text x="670" y="156" text-anchor="middle" fill="#f1f3ff">sert de nombreuses équipes dans de nombreux langages</text><text x="670" y="176" text-anchor="middle" fill="#f1f3ff">veut des plans qu'un non-programmeur peut relire</text><text x="670" y="204" text-anchor="middle" fill="#ffd166" font-weight="700">Terraform · OpenTofu</text><text x="670" y="222" text-anchor="middle" fill="#9aa3c7" font-size="11">les limites de HCL sont une qualité ici : difficile d'être trop malin</text>
</g>
</svg>
</div>

## Les trois, honnêtement

| | Terraform / OpenTofu | AWS CDK | Pulumi |
|---|---|---|---|
| Langage | HCL, déclaratif | TypeScript, Python, Java, Go, C# ; synthétise en CloudFormation | TypeScript, Python, Go, C#, Java, YAML ; son propre moteur |
| Clouds | Tous, via des providers ; le plus grand écosystème | AWS seulement (CDK for Terraform existe, mais c'est du Terraform) | Tous, via des providers, beaucoup enveloppant ceux de Terraform |
| État | Un fichier d'état que vous gérez : S3 + verrouillage, ou Terraform Cloud | CloudFormation le détient ; rien à gérer, rien à corrompre | Pulumi Cloud, ou S3 auto-géré |
| Plan / diff | `terraform plan` : précis, lisible, la référence | `cdk diff` : bon pour les ressources, faible pour IAM et certains changements de propriétés | `pulumi preview` : bon, entre les deux |
| Abstractions | Modules ; la composition est verbeuse, pas de vrais types | Constructs avec de vrais types ; les constructs L2 encodent les bonnes pratiques (une `Queue` est chiffrée, un `Bucket` bloque l'accès public) | Composants, vrais types, plus la possibilité d'importer des constructs CDK |
| Tests | Assertions sur le plan, Terratest (Go) | Jest contre le template synthétisé ; rapide et précis | Tests unitaires dans le langage, avec mocks |
| Mode de défaillance | Dérive d'état ; un verrou oublié ; montées de version de providers | Les limites de CloudFormation : [500 ressources par stack](/fr/blog/cloudformation-500-resource-limit-split-a-stack), rollbacks lents, [sémantique de remplacement](/fr/blog/cloudformation-deleted-our-app-runner-services) | Les bugs du moteur sont à vous de déboguer ; communauté plus petite |
| Se lit bien pour | Des relecteurs qui ne programment pas | Des programmeurs | Des programmeurs |
| Qui paie | Changements de licence HashiCorp en 2023 ; OpenTofu est le fork communautaire | Gratuit ; le problème d'AWS | OSS gratuit ; le service cloud est payant |

Trois éléments de ce tableau décident plus de choix que tout le reste réuni.

**L'état.** Le fichier d'état de Terraform est la source de la plupart des incidents Terraform pour lesquels on nous a appelés : un état corrompu après un apply interrompu, un verrou périmé, un état qui référence une ressource supprimée à la main, une migration entre backends qui a mal tourné. CDK n'a pas de fichier d'état ; CloudFormation *est* l'état, et c'est le problème d'AWS de le garder cohérent. Cela seul supprime toute une catégorie de travail opérationnel. Le prix, ce sont les particularités propres de CloudFormation, qui sont réelles, mais auxquelles nous n'avons jamais perdu un fichier d'état.

**Le diff.** `terraform plan` est vraiment le meilleur aperçu de changement du secteur, et c'est une grande part de la raison pour laquelle les équipes plateforme l'adorent. `cdk diff` est suffisant pour le quotidien et mauvais pour IAM : il montre qu'une politique a changé mais ne montre pas toujours comment. Nous compensons avec `cdk-nag` et une règle qui consiste à coller l'IAM synthétisé dans la PR pour tout changement qui le touche. Si votre processus de revue est « quelqu'un qui n'écrit pas de code lit le plan », Terraform gagne ici, et de loin.

**Les abstractions typées.** En CDK, `new sqs.Queue(this, 'Q')` vous donne une file chiffrée avec des défauts raisonnables, et `queue.grantConsumeMessages(fn)` écrit la politique IAM pour vous, correctement, avec l'ARN exact. L'équivalent en Terraform est un module que quelqu'un a écrit, ou vingt lignes de JSON de politique que vous écrivez à la main et ratez une fois. Cette différence se cumule sur une plateforme : notre base de code CDK pour [trois comptes](/fr/blog/aws-three-accounts-one-cdk-codebase) fait environ 6 000 lignes ; l'équivalent Terraform, nous l'avons estimé à environ le double, avant les modules.

## Quand nous choisirions encore Terraform

- **Multi-cloud avec une équipe plateforme.** Un langage, un workflow, chaque provider. Pulumi peut le faire aussi, mais l'écosystème Terraform de modules et de providers a une décennie de profondeur en plus.
- **Tout ce où le relecteur n'est pas un programmeur.** Les environnements pilotés par la conformité où le ticket de changement doit contenir un plan qu'un auditeur lit. HCL et `terraform plan` ont été construits pour ça.
- **Hors AWS, un seul cloud, petite équipe.** Les équipes GCP ou Azure sans forte identité TypeScript sont généralement mieux avec Terraform qu'avec Pulumi, sur la seule taille de la communauté.
- **Une équipe qui l'a déjà et pour qui ça marche.** Migrer un Terraform qui fonctionne vers CDK pour des raisons esthétiques est un mauvais échange. Nous avons décliné ce travail deux fois.

## Quand Pulumi, plutôt que CDK

Quand l'équipe est nativement TypeScript *et* que le parc n'est pas purement AWS : une zone Cloudflare, un projet Vercel, un ensemble de moniteurs Datadog, un bucket GCP, à côté des ressources AWS. CDK s'arrête à la frontière d'AWS ; Pulumi non. Pulumi a aussi le meilleur diff des deux et un moteur plus rapide que CloudFormation pour les grands stacks. Le coût est une communauté plus petite et un backend d'état que vous payez ou que vous exploitez.

<div class="article-figure">
<svg viewBox="0 0 900 230" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Trois colonnes évaluant les outils sur les facteurs décisifs. État : Terraform géré par vous, CDK rien à gérer, Pulumi service ou auto-géré. Qualité du diff : Terraform le meilleur, CDK faible sur IAM, Pulumi bon. Abstractions typées : Terraform modules sans types, CDK constructs L2 avec méthodes grant, Pulumi composants typés. Clouds : Terraform tous, CDK AWS seulement, Pulumi tous. Lisible par un non-programmeur : Terraform oui, les autres non.">
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<text x="20" y="24" fill="#f1f3ff" font-size="14" font-weight="700">Les facteurs qui décident</text>
<g fill="#f1f3ff" font-weight="700"><text x="20" y="56">facteur</text><text x="260" y="56" fill="#ffd166">Terraform</text><text x="480" y="56" fill="#4fffb0">CDK</text><text x="700" y="56" fill="#7b8cff">Pulumi</text></g>
<line x1="20" y1="64" x2="880" y2="64" stroke="#2a3150"/>
<text x="20" y="88" fill="#f1f3ff">état à gérer</text><text x="260" y="88" fill="#ff6b8a">oui · la plupart des incidents vivent ici</text><text x="480" y="88" fill="#4fffb0">aucun · CloudFormation est l'état</text><text x="700" y="88" fill="#ffd166">service, ou auto-géré</text>
<text x="20" y="112" fill="#f1f3ff">aperçu des changements</text><text x="260" y="112" fill="#4fffb0">plan · la référence</text><text x="480" y="112" fill="#ff6b8a">diff · faible sur IAM</text><text x="700" y="112" fill="#ffd166">preview · bon</text>
<text x="20" y="136" fill="#f1f3ff">abstractions typées</text><text x="260" y="136" fill="#ff6b8a">modules, sans types</text><text x="480" y="136" fill="#4fffb0">constructs + grant()</text><text x="700" y="136" fill="#4fffb0">composants, typés</text>
<text x="20" y="160" fill="#f1f3ff">clouds</text><text x="260" y="160" fill="#4fffb0">tous · l'écosystème le plus profond</text><text x="480" y="160" fill="#ff6b8a">AWS seulement</text><text x="700" y="160" fill="#4fffb0">tous</text>
<text x="20" y="184" fill="#f1f3ff">un non-programmeur peut relire</text><text x="260" y="184" fill="#4fffb0">oui</text><text x="480" y="184" fill="#ff6b8a">non</text><text x="700" y="184" fill="#ff6b8a">non</text>
<line x1="20" y1="196" x2="880" y2="196" stroke="#2a3150"/>
<text x="450" y="220" text-anchor="middle" fill="#9aa3c7">Le vert est une force, le rouge un coût que vous paierez. Aucune colonne n'est toute verte. Choisissez selon qui écrit et lit le code.</text>
</g>
</svg>
</div>

## Ce que nous utilisons, et pourquoi

CDK, TypeScript, pour chaque plateforme client tout AWS. Les équipes applicatives sont des équipes TypeScript ; l'infrastructure vit dans `infra/` dans le même monorepo que les applications ; les types des routes de l'API et les noms des variables d'environnement de l'infra viennent du même `packages/types` ; un changement à une file et à son consommateur est une seule PR. Jamais de fichier d'état. `cdk diff` collé dans chaque PR, IAM synthétisé et collé pour tout ce qui le touche, `cdk-nag` en CI, assertions Jest sur les templates pour les invariants qui comptent (chaque bucket bloque l'accès public, chaque file a une DLQ, aucune politique de suppression `DESTROY` hors d'un stack de démontage nommé).

Nous payons ça avec les limites et la lenteur de CloudFormation, dont nous avons longuement parlé, et avec la lacune du diff IAM, que la règle du collage dans la PR couvre. Pour ces équipes, c'est le bon échange, de loin. Pour une équipe plateforme servant douze langages sur deux clouds, nous leur dirions Terraform, et nous le penserions.

## La version courte

Infrastructure as code est une pratique, pas un produit. La pratique : l'infrastructure est dans un dépôt, les changements sont relus, rien n'est cliqué. Le produit est choisi par qui possède le code. Équipe applicative en TypeScript sur AWS : CDK. Même équipe, plus qu'AWS : Pulumi. Équipe plateforme, de nombreux langages, des plans lus par des non-programmeurs : Terraform. Un Terraform qui fonctionne et que vous avez déjà : gardez-le.

Si vous choisissez, ou si vous avez hérité d'un choix qui ne correspond pas à l'équipe, [nous avons exploité les trois et nous vous dirons lequel correspond à la vôtre](/contact), y compris quand la réponse est « celui que vous avez ».
