# Infrastructure as Code heißt nicht Terraform. CDK, Pulumi und die Frage, die wirklich entscheidet

Irgendwann um 2019 wurde „Infrastructure as Code“ in den meisten Gesprächen zum Synonym für Terraform, so wie „Suche“ Google bedeutet. Das ist ein vernünftiger Standard. Es ist auch das falsche Werkzeug für einen nennenswerten Anteil der Teams, und der Grund sind nicht die Funktionen, die alle drei ernsthaften Optionen haben, sondern *wer den Code schreiben und lesen wird*.

Wir betreiben unsere Kundenplattformen auf AWS CDK. Wir haben für andere Kunden Terraform und Pulumi ausgeliefert. Das ist der Vergleich, den wir gern vor der Entscheidung gelesen hätten, organisiert um die Frage, die entscheidet, mit den Kompromissen offen benannt und den Fällen, in denen wir trotzdem Terraform wählen würden.

## Die Frage

**Gehört die Infrastruktur dem Anwendungsteam oder einem separaten Plattformteam?**

Wenn die Leute, die die Next.js-App schreiben, dieselben sind, die die Infrastruktur schreiben, haben sie bereits eine Sprache, einen Paketmanager, einen Test-Runner und einen Editor für TypeScript eingerichtet. Ihnen eine zweite Sprache (HCL) mit eigener Semantik, eigenem Modulsystem und eigener Test-Geschichte zu geben, ist ein Preis, den jede Person jeden Tag zahlt. CDK oder Pulumi lässt sie Infrastruktur in der Sprache definieren, in der sie ohnehin denken, im selben Repository, mit demselben Review-Prozess, und Typen zwischen App und Infra teilen.

Wenn die Infrastruktur einem Plattformteam gehört, das viele Anwendungsteams in vielen Sprachen bedient, kippt die Rechnung. Dieses Team will eine Sprache für alle Infrastruktur, egal worin die Apps geschrieben sind, ein deklaratives Modell, das sich über Hunderte Module leicht reviewen lässt, ein riesiges Provider-Ökosystem und einen Plan-Output, den ein Nicht-Programmierer in einem Änderungsticket lesen kann. Das ist Terraforms Heimspiel, und die Grenzen von HCL sind dort ein Vorteil: Es ist schwer, darin zu clever zu sein.

Alles Weitere im Vergleich folgt aus dieser Antwort.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Entscheidungsverzweigung. Wem gehört die Infrastruktur? Anwendungsteam, das bereits TypeScript schreibt: CDK bei reinem AWS, Pulumi bei Multi-Cloud oder Nicht-AWS. Separates Plattformteam, das viele App-Teams und Sprachen bedient: Terraform oder OpenTofu. Unter jedem Zweig die Gründe: gleiche Sprache, gleiches Repo, geteilte Typen versus eine Sprache für alle Infra, deklarative reviewbare Pläne, größtes Provider-Ökosystem.">
<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">wem gehört die Infrastruktur?</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">dem Anwendungsteam</text><text x="230" y="156" text-anchor="middle" fill="#f1f3ff">schreibt schon TypeScript · gleiches Repo · gleiche Review</text><text x="230" y="176" text-anchor="middle" fill="#f1f3ff">geteilte Typen zwischen App und Infra</text><text x="230" y="204" text-anchor="middle" fill="#4fffb0" font-weight="700">CDK bei reinem AWS · Pulumi sonst</text><text x="230" y="222" text-anchor="middle" fill="#9aa3c7" font-size="11">eine zweite Sprache ist ein täglicher Preis für jede Person</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">einem separaten Plattformteam</text><text x="670" y="156" text-anchor="middle" fill="#f1f3ff">bedient viele Teams in vielen Sprachen</text><text x="670" y="176" text-anchor="middle" fill="#f1f3ff">will Pläne, die ein Nicht-Programmierer reviewen kann</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">HCLs Grenzen sind hier ein Vorteil: schwer, zu clever zu sein</text>
</g>
</svg>
</div>

## Die drei, ehrlich

| | Terraform / OpenTofu | AWS CDK | Pulumi |
|---|---|---|---|
| Sprache | HCL, deklarativ | TypeScript, Python, Java, Go, C#; synthetisiert zu CloudFormation | TypeScript, Python, Go, C#, Java, YAML; eigene Engine |
| Clouds | Alle, über Provider; das größte Ökosystem | Nur AWS (CDK for Terraform existiert, aber das ist Terraform) | Alle, über Provider, viele wickeln Terraforms ein |
| State | Eine State-Datei, die Sie verwalten: S3 + Locking, oder Terraform Cloud | CloudFormation hält ihn; nichts zu verwalten, nichts zu korrumpieren | Pulumi Cloud, oder selbst verwaltetes S3 |
| Plan / Diff | `terraform plan`: präzise, lesbar, der Goldstandard | `cdk diff`: gut für Ressourcen, schwach für IAM und manche Eigenschaftsänderungen | `pulumi preview`: gut, zwischen den beiden |
| Abstraktionen | Module; Komposition ist wortreich, keine echten Typen | Constructs mit echten Typen; L2-Constructs kodieren Best Practices (eine `Queue` bekommt Verschlüsselung, ein `Bucket` blockiert öffentlichen Zugriff) | Komponenten, echte Typen, plus Import von CDK-Constructs möglich |
| Testen | Plan-Assertions, Terratest (Go) | Jest gegen das synthetisierte Template; schnell und präzise | Unit-Tests in der Sprache, gemockt |
| Fehlermodus | State-Drift; ein zurückgelassener Lock; Provider-Upgrades | CloudFormations Grenzen: [500 Ressourcen pro Stack](/de/blog/cloudformation-500-resource-limit-split-a-stack), langsame Rollbacks, [Ersetzungssemantik](/de/blog/cloudformation-deleted-our-app-runner-services) | Engine-Bugs müssen Sie selbst debuggen; kleinere Community |
| Liest sich gut für | Reviewer, die nicht programmieren | Programmierer | Programmierer |
| Wer bezahlt | HashiCorp-Lizenzänderungen 2023; OpenTofu ist der Community-Fork | Kostenlos; AWS' Problem | Kostenloses OSS; der Cloud-Dienst ist kostenpflichtig |

Drei Dinge in dieser Tabelle entscheiden mehr Wahlen als der ganze Rest zusammen.

**State.** Terraforms State-Datei ist die Quelle der meisten Terraform-Vorfälle, zu denen wir gerufen wurden: ein korrupter State nach einem unterbrochenen Apply, ein veralteter Lock, ein State, der auf eine von Hand gelöschte Ressource verweist, eine schiefgegangene Migration zwischen Backends. CDK hat keine State-Datei; CloudFormation *ist* der State, und es ist AWS' Problem, ihn konsistent zu halten. Allein das entfernt eine ganze Kategorie betrieblicher Arbeit. Der Preis sind CloudFormations eigene Eigenheiten, die real sind, aber an die wir nie eine State-Datei verloren haben.

**Das Diff.** `terraform plan` ist wirklich die beste Änderungsvorschau der Branche, und ein großer Teil des Grundes, warum Plattformteams es lieben. `cdk diff` ist gut genug für den Alltag und schlecht für IAM: Es zeigt, dass sich eine Policy geändert hat, aber nicht immer wie. Wir kompensieren mit `cdk-nag` und der Regel, für jede Änderung, die IAM berührt, das synthetisierte IAM in den PR einzufügen. Wenn Ihr Review-Prozess lautet „jemand, der keinen Code schreibt, liest den Plan“, gewinnt Terraform hier, und zwar deutlich.

**Abstraktionen mit Typen.** In CDK liefert `new sqs.Queue(this, 'Q')` eine verschlüsselte Queue mit vernünftigen Defaults, und `queue.grantConsumeMessages(fn)` schreibt die IAM-Policy für Sie, korrekt, mit der exakten ARN. Das Äquivalent in Terraform ist ein Modul, das jemand geschrieben hat, oder zwanzig Zeilen Policy-JSON, die Sie von Hand schreiben und einmal falsch machen. Dieser Unterschied summiert sich über eine Plattform: Unsere CDK-Codebasis für [drei Konten](/de/blog/aws-three-accounts-one-cdk-codebase) hat etwa 6.000 Zeilen; das Terraform-Äquivalent haben wir auf grob das Doppelte geschätzt, vor Modulen.

## Wann wir trotzdem Terraform wählen würden

- **Multi-Cloud mit einem Plattformteam.** Eine Sprache, ein Workflow, jeder Provider. Pulumi kann das auch, aber das Terraform-Ökosystem aus Modulen und Providern ist ein Jahrzehnt tiefer.
- **Alles, wo der Reviewer kein Programmierer ist.** Compliance-getriebene Umgebungen, in denen das Änderungsticket einen Plan enthalten muss, den ein Auditor liest. HCL und `terraform plan` wurden dafür gebaut.
- **Nicht-AWS, eine Cloud, kleines Team.** GCP- oder Azure-Teams ohne starke TypeScript-Identität fahren mit Terraform meist besser als mit Pulumi, allein wegen der Community-Größe.
- **Ein Team, das es bereits hat, und es funktioniert.** Funktionierendes Terraform aus ästhetischen Gründen nach CDK zu migrieren, ist ein schlechter Tausch. Wir haben diesen Auftrag zweimal abgelehnt.

## Wann Pulumi statt CDK

Wenn das Team TypeScript-nativ ist *und* der Bestand nicht rein AWS ist: eine Cloudflare-Zone, ein Vercel-Projekt, ein Satz Datadog-Monitore, ein GCP-Bucket, neben den AWS-Ressourcen. CDK endet an der AWS-Grenze; Pulumi nicht. Pulumi hat außerdem das bessere Diff der beiden und eine schnellere Engine als CloudFormation für große Stacks. Der Preis ist eine kleinere Community und ein State-Backend, das Sie entweder bezahlen oder betreiben.

<div class="article-figure">
<svg viewBox="0 0 900 230" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Drei Spalten bewerten die Werkzeuge nach den entscheidenden Faktoren. State: Terraform selbst verwaltet, CDK nichts zu verwalten, Pulumi Dienst oder selbst verwaltet. Diff-Qualität: Terraform am besten, CDK schwach bei IAM, Pulumi gut. Typisierte Abstraktionen: Terraform Module ohne Typen, CDK L2-Constructs mit grant-Methoden, Pulumi Komponenten mit Typen. Clouds: Terraform alle, CDK nur AWS, Pulumi alle. Für Nicht-Programmierer lesbar: Terraform ja, andere nein.">
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<text x="20" y="24" fill="#f1f3ff" font-size="14" font-weight="700">Die Faktoren, die entscheiden</text>
<g fill="#f1f3ff" font-weight="700"><text x="20" y="56">Faktor</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">State zu verwalten</text><text x="260" y="88" fill="#ff6b8a">ja · die meisten Vorfälle leben hier</text><text x="480" y="88" fill="#4fffb0">keiner · CloudFormation ist der State</text><text x="700" y="88" fill="#ffd166">Dienst, oder selbst verwaltet</text>
<text x="20" y="112" fill="#f1f3ff">Änderungsvorschau</text><text x="260" y="112" fill="#4fffb0">plan · der Goldstandard</text><text x="480" y="112" fill="#ff6b8a">diff · schwach bei IAM</text><text x="700" y="112" fill="#ffd166">preview · gut</text>
<text x="20" y="136" fill="#f1f3ff">typisierte Abstraktionen</text><text x="260" y="136" fill="#ff6b8a">Module, keine Typen</text><text x="480" y="136" fill="#4fffb0">Constructs + grant()</text><text x="700" y="136" fill="#4fffb0">Komponenten, typisiert</text>
<text x="20" y="160" fill="#f1f3ff">Clouds</text><text x="260" y="160" fill="#4fffb0">alle · tiefstes Ökosystem</text><text x="480" y="160" fill="#ff6b8a">nur AWS</text><text x="700" y="160" fill="#4fffb0">alle</text>
<text x="20" y="184" fill="#f1f3ff">Nicht-Programmierer kann reviewen</text><text x="260" y="184" fill="#4fffb0">ja</text><text x="480" y="184" fill="#ff6b8a">nein</text><text x="700" y="184" fill="#ff6b8a">nein</text>
<line x1="20" y1="196" x2="880" y2="196" stroke="#2a3150"/>
<text x="450" y="220" text-anchor="middle" fill="#9aa3c7">Grün ist eine Stärke, Rot ein Preis, den Sie zahlen werden. Keine Spalte ist ganz grün. Wählen Sie danach, wer den Code schreibt und liest.</text>
</g>
</svg>
</div>

## Was wir betreiben, und warum

CDK, TypeScript, für jede reine AWS-Kundenplattform. Die Anwendungsteams sind TypeScript-Teams; die Infrastruktur liegt in `infra/` im selben Monorepo wie die Apps; die Route-Typen der API und die Umgebungsvariablennamen der Infra kommen aus demselben `packages/types`; eine Änderung an einer Queue und ihrem Consumer ist ein PR. Nie eine State-Datei. `cdk diff` in jeden PR eingefügt, IAM synthetisiert und eingefügt für alles, was es berührt, `cdk-nag` in CI, Jest-Assertions auf die Templates für die Invarianten, die uns wichtig sind (jeder Bucket blockiert öffentlichen Zugriff, jede Queue hat eine DLQ, keine `DESTROY`-Removal-Policy außerhalb eines benannten Teardown-Stacks).

Wir bezahlen dafür mit CloudFormations Grenzen und Langsamkeit, über die wir ausführlich geschrieben haben, und mit der IAM-Diff-Lücke, die die Regel „in den PR einfügen“ abdeckt. Für diese Teams ist es mit weitem Abstand der richtige Tausch. Einem Plattformteam, das zwölf Sprachen über zwei Clouds bedient, würden wir Terraform sagen, und es ernst meinen.

## Die Kurzfassung

Infrastructure as Code ist eine Praxis, kein Produkt. Die Praxis lautet: Infrastruktur liegt in einem Repository, Änderungen werden reviewt, nichts wird geklickt. Das Produkt wird davon bestimmt, wem der Code gehört. Anwendungsteam in TypeScript auf AWS: CDK. Dasselbe Team, mehr als AWS: Pulumi. Plattformteam, viele Sprachen, Pläne von Nicht-Programmierern gelesen: Terraform. Funktionierendes Terraform, das Sie bereits haben: behalten.

Wenn Sie gerade wählen oder eine Wahl geerbt haben, die nicht zum Team passt, [haben wir alle drei betrieben und sagen Ihnen, welche zu Ihrem passt](/contact), auch wenn die Antwort „die, die Sie haben“ lautet.
