Cloudflare IaC: Terraform + Wrangler
Das Kernproblem
Cloudflares Tooling ist in zwei Welten geteilt:
- Terraform verwaltet die Infrastruktur: Zonen, DNS-Einträge, Zugriffsrichtlinien, Firewall-Regeln, Kontoebenen-Einstellungen und die Existenz von Ressourcen wie KV-Namespaces, D1-Datenbanken, R2-Buckets und Queues.
- Wrangler verwaltet die Anwendungsbereitstellung: Worker-Code, Pages-Projekte, Bindungen zu diesen Ressourcen und die lokale Entwicklung.
Die Herausforderung besteht darin, dass Wrangler Ressourcen-IDs benötigt, die Terraform erstellt. Eine KV-Namespace-ID, eine D1-Datenbank-ID, ein Queue-Name – das sind Ausgaben von terraform apply, die in wrangler.toml (oder wrangler.jsonc) fließen müssen, bevor wrangler deploy funktionieren kann.
Diese Fähigkeit kodifiziert ein DRY-, automatisierbares Muster zur Überbrückung dieser Lücke.
Aufteilung der Verantwortlichkeiten
Terraform besitzt
- Zonen und DNS-Einträge
- KV-Namespaces (
cloudflare_workers_kv_namespace) - D1-Datenbanken (
cloudflare_d1_database) - R2-Buckets (
cloudflare_r2_bucket) - Queues (
cloudflare_queue) - Zugriffsrichtlinien, WAF-Regeln, Page-Regeln
- Kontoebenen-Einstellungen
- Jede Ressource, die vor der Bereitstellung des Anwendungscodes existieren sollte
Wrangler besitzt
- Worker-Quellcode und Bündelung
- Pages-Projektbereitstellung
- Bindungskonfiguration (Verbindung von Workern mit den von Terraform erstellten Ressourcen)
- Lokale Entwicklung (
wrangler dev) - Secrets (via
wrangler secret put) - Bereitstellung (
wrangler deploy)
Die Grauzone
Einige Ressourcen können von beiden Tools verwaltet werden. Der Leitgedanke: Wenn mehrere Worker oder Projekte eine Ressource teilen, besitzt Terraform sie. Wenn sie auf einen einzelnen Worker beschränkt ist, kann Wrangler sie besitzen. Im Zweifelsfall bevorzugen Sie Terraform – es bietet Zustandsverfolgung, Drifterkennung und eine einzige Quelle der Wahrheit für die Infrastruktur.
Die Auto-Provisioning-Funktion von Wrangler (die KV-/R2-/D1-Ressourcen beim ersten Deploy erstellt) ist für Prototyping praktisch, sollte aber nicht in Produktions-Pipelines verwendet werden. Sie erstellt Ressourcen außerhalb des Terraform-Zustands, was zu Drift und Ressourcen führt, die nicht systematisch verwaltet oder abgebaut werden können.
Das Brückenmuster
Übersicht
terraform apply → terraform output -json → Brücken-Skript → wrangler.jsonc → wrangler deploy
Das Brücken-Skript ist ein Node.js-Skript im Projekt, das über package.json aufgerufen wird. Es liest Terraform-Ausgaben und schreibt sie in die Wrangler-Konfigurationsdatei. Dasselbe Skript wird lokal und in CI/CD ausgeführt – keine Duplizierung.
Schritt 1: Terraform-Ausgaben
Definieren Sie Ausgaben für jede Ressourcen-ID, die Wrangler benötigt:
# outputs.tf
output "kv_namespace_id" {
value = cloudflare_workers_kv_namespace.my_kv.id
}
output "d1_database_id" {
value = cloudflare_d1_database.my_db.id
}
output "r2_bucket_name" {
value = cloudflare_r2_bucket.my_bucket.name
}
output "account_id" {
value = var.cloudflare_account_id
}
output "zone_id" {
value = cloudflare_zone.my_zone.id
}
Schritt 2: Das Brücken-Skript
Erstellen Sie scripts/sync-wrangler-config.mjs – ein Node.js-Skript, das Terraform-Ausgaben liest und die Wrangler-Konfiguration generiert (oder patcht):
#!/usr/bin/env node
// scripts/sync-wrangler-config.mjs
//
// Liest Terraform-Ausgaben und schreibt wrangler.jsonc mit den korrekten Ressourcen-IDs.
// Lokal oder in CI ausführen – gleicher Befehl, gleiches Ergebnis.
import { execSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..");
// --- Konfiguration ---
// Passen Sie diese Pfade an Ihr Projektlayout an.
const TERRAFORM_DIR = join(ROOT, "terraform");
const WRANGLER_CONFIG_PATH = join(ROOT, "wrangler.jsonc");
const TEMPLATE_PATH = join(ROOT, "wrangler.template.jsonc");
// --- Terraform-Ausgaben lesen ---
function getTerraformOutputs() {
try {
const raw = execSync("terraform output -json", {
cwd: TERRAFORM_DIR,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
});
const outputs = JSON.parse(raw);
// terraform output -json kapselt jeden Wert in { value, type, sensitive }
const flat = {};
for (const [key, entry] of Object.entries(outputs)) {
flat[key] = entry.value;
}
return flat;
} catch (err) {
console.error("Fehler beim Lesen der Terraform-Ausgaben. Haben Sie 'terraform apply' ausgeführt?");
console.error(err.message);
process.exit(1);
}
}
// --- Wrangler-Konfiguration generieren ---
function generateConfig(outputs) {
if (!existsSync(TEMPLATE_PATH)) {
console.error(`Vorlage nicht gefunden: ${TEMPLATE_PATH}`);
console.error("Erstellen Sie eine wrangler.template.jsonc mit __PLACEHOLDER__-Token.");
process.exit(1);
}
let template = readFileSync(TEMPLATE_PATH, "utf-8");
// Ersetzen Sie __PLACEHOLDER__-Token durch Terraform-Ausgabewerte.
// Token-Format: __UPPER_SNAKE_CASE__ zugeordnet zu Terraform-Ausgabenamen.
for (const [key, value] of Object.entries(outputs)) {
const token = `__${key.toUpperCase()}__`;
template = template.replaceAll(token, String(value));
}
// Warnen bei nicht ersetzten Token
const remaining = template.match(/__[A-Z_]+__/g);
if (remaining) {
console.warn("Warnung: Nicht ersetzte Token in der Vorlage:", [...new Set(remaining)]);
}
writeFileSync(WRANGLER_CONFIG_PATH, template, "utf-8");
console.log(`Geschrieben: ${WRANGLER_CONFIG_PATH}`);
}
// --- Hauptprogramm ---
const outputs = getTerraformOutputs();
generateConfig(outputs);
Schritt 3: Die Vorlage
Erstellen Sie wrangler.template.jsonc – eine Wrangler-Konfiguration mit Platzhalter-Token, wo die Terraform-Ausgaben hingehören:
{
// Generiert aus wrangler.template.jsonc von scripts/sync-wrangler-config.mjs
// Nicht direkt bearbeiten – stattdessen die Vorlage bearbeiten.
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
"account_id": "__ACCOUNT_ID__",
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "__KV_NAMESPACE_ID__"
}
],
"d1_databases": [
{
"binding": "MY_DB",
"database_id": "__D1_DATABASE_ID__",
"database_name": "my-db"
}
],
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "__R2_BUCKET_NAME__"
}
]
}
Schritt 4: package.json-Skripte
{
"scripts": {
"sync": "node scripts/sync-wrangler-config.mjs",
"predeploy": "npm run sync",
"deploy": "wrangler deploy",
"dev": "npm run sync && wrangler dev"
}
}
npm run deploy führt automatisch zuerst sync aus (über den predeploy-Hook). Derselbe npm run deploy-Befehl funktioniert lokal und in CI/CD – DRY.
Schritt 5: .gitignore
# Vom Sync-Skript generiert – nicht committen
wrangler.jsonc
# Terraform
terraform/.terraform/
terraform/*.tfstate*
Die generierte wrangler.jsonc sollte nicht committet werden. Die Vorlage und das Sync-Skript sind die Quelle der Wahrheit.
CI/CD-Integration
Die CI/CD-Pipeline führt genau dieselben Befehle aus, die ein Entwickler lokal ausführt. Der einzige Unterschied ist, wie der Terraform-Zustand und die Anmeldeinformationen bereitgestellt werden.
Generische Pipeline-Struktur
# Pseudocode – an die Syntax Ihrer CI/CD-Plattform anpassen
steps:
# 1. Terraform wendet Infrastrukturänderungen an
- name: terraform
run: |
cd terraform
terraform init
terraform apply -auto-approve
# 2. Brücken-Skript synchronisiert Terraform-Ausgaben in die Wrangler-Konfiguration
# 3. Wrangler stellt die Anwendung bereit
- name: deploy-worker
run: npm run deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Das war's. npm run deploy ruft predeploy → sync → wrangler deploy auf. Gleich wie lokal.
Anmeldeinformationen
- Terraform: benötigt ein Cloudflare-API-Token mit Berechtigung zur Verwaltung von Infrastrukturressourcen. Bereitstellung über die Umgebungsvariable
CLOUDFLARE_API_TOKENoder in der Provider-Konfiguration. - Wrangler: benötigt ein Cloudflare-API-Token mit Berechtigung zum Bereitstellen von Workern. Oft das gleiche Token, kann aber für das Prinzip der geringsten Rechte getrennt werden.
- Terraform-Zustand: Verwenden Sie ein entferntes Backend (S3, GCS, Terraform Cloud usw.), damit CI und lokal denselben Zustand teilen.
Umgebungs-Promotion
Für Multi-Umgebungs-Setups (Staging, Produktion) verwenden Sie Terraform-Workspaces oder separate Zustandsdateien und Wrangler-Umgebungen:
// In wrangler.template.jsonc
{
"name": "my-worker",
"env": {
"staging": {
"kv_namespaces": [{ "binding": "MY_KV", "id": "__STAGING_KV_NAMESPACE_ID__" }]
},
"production": {
"kv_namespaces": [{ "binding": "MY_KV", "id": "__PRODUCTION_KV_NAMESPACE_ID__" }]
}
}
}
Das Sync-Skript verarbeitet dies natürlich – fügen Sie einfach die entsprechenden Terraform-Ausgaben hinzu.
Alternative: Von Terraform verwaltete Bindungen
Für Teams, die Terraform als einzige Quelle der Wahrheit für alles – einschließlich der Bereitstellung – haben möchten, kann die Ressource cloudflare_worker_version Bindungen direkt verwalten. In diesem Modell bündelt Wrangler nur den Code, und Terraform übernimmt den Upload und die Verdrahtung der Bindungen:
resource "cloudflare_worker_version" "my_worker" {
account_id = var.cloudflare_account_id
worker_id = cloudflare_worker.my_worker.id
main_module = "index.js"
compatibility_date = "2025-01-01"
bindings = [
{
type = "kv_namespace"
name = "MY_KV"
namespace_id = cloudflare_workers_kv_namespace.my_kv.id
},
{
type = "d1"
name = "DB"
id = cloudflare_d1_database.my_db.id
},
{
type = "r2_bucket"
name = "MY_BUCKET"
bucket_name = cloudflare_r2_bucket.my_bucket.name
}
]
}
Mit diesem Ansatz:
wrangler deploy --dry-run --outdir distbündelt den Code- Terraform lädt das Bundle hoch und verdrahtet alle Bindungen über Ressourcenreferenzen
- Kein Brücken-Skript, keine Vorlage – Terraform löst IDs nativ auf
Kompromiss: Sie verlieren die lokalen Entwicklungsergonomien von Wrangler für die Bindungskonfiguration. Sie benötigen weiterhin ein wrangler.toml für wrangler dev, was eine gewisse Duplizierung bedeutet. Das Brückenmuster (oben) ist im Allgemeinen besser, wenn Entwickler regelmäßig wrangler dev verwenden, während der reine Terraform-Ansatz für CI-zentrierte Workflows geeignet ist, bei denen die lokale Entwicklung Mocks oder eine minimale Konfiguration verwendet.
Wählen Sie das Brückenmuster, wenn das Team regelmäßig wrangler dev ausführt. Wählen Sie von Terraform verwaltete Bindungen, wenn Terraform bereits das Bereitstellungstool ist und die lokale Entwicklung keine echten Bindungen benötigt.
Typsicherheit: wrangler types --check
Nach dem Generieren oder Aktualisieren der Wrangler-Konfiguration führen Sie wrangler types aus, um die TypeScript-Bindungen neu zu generieren. Verwenden Sie in CI --check, um den Build fehlschlagen zu lassen, wenn die Typen veraltet sind:
{
"scripts": {
"typecheck": "wrangler types --check",
"predeploy": "npm run sync && npm run typecheck",
"deploy": "wrangler deploy"
}
}
Dies erfasst Diskrepanzen zwischen von Terraform verwalteten Ressourcen und den TypeScript-Typen des Workers vor der Bereitstellung.
Anpassung an ein bestehendes Projekt
Wenn Sie dieses Muster auf ein bereits bestehendes Projekt anwenden:
-
Prüfen Sie, was existiert. Sehen Sie sich die aktuelle
wrangler.toml/wrangler.jsoncauf hartcodierte Ressourcen-IDs an. Prüfen Sie, ob Terraform bereits verwendet wird. Sehen Sie sich die CI/CD-Konfiguration an. -
Identifizieren Sie die Ressourcen. Listen Sie jede Ressourcen-ID in der Wrangler-Konfiguration auf. Bestimmen Sie, welche bereits in Terraform sind und welche importiert werden müssen.
-
Importieren Sie bestehende Ressourcen in Terraform mit
terraform import, anstatt sie neu zu erstellen. Dies vermeidet Ausfallzeiten und Datenverlust. -
Erstellen Sie die Vorlage. Kopieren Sie die bestehende Wrangler-Konfiguration in
wrangler.template.jsoncund ersetzen Sie hartcodierte IDs durch__PLACEHOLDER__-Token. -
Schreiben Sie das Brücken-Skript. Beginnen Sie mit dem Referenzskript oben und passen Sie Pfade und Ausgabenamen an.
-
Verdrahten Sie package.json. Fügen Sie die Skripte
sync,predeployunddeployhinzu. -
Aktualisieren Sie CI/CD. Ersetzen Sie alle manuellen ID-Übergabe- oder Konfigurationsgenerierungsschritte durch
npm run deploy. -
Fügen Sie
wrangler.jsonczu.gitignorehinzu, sobald die generierte Datei als funktionierend bestätigt wurde.
Häufige Muster
Mehrere Worker in einem Monorepo
Wenn ein Repository mehrere Worker enthält, erhält jeder seine eigene Vorlage, und das Sync-Skript verarbeitet alle:
project/
├── terraform/
├── workers/
│ ├── api/
│ │ ├── src/
│ │ └── wrangler.template.jsonc
│ └── cron/
│ ├── src/
│ └── wrangler.template.jsonc
├── scripts/
│ └── sync-wrangler-config.mjs
└── package.json
Das Sync-Skript iteriert über die Worker-Verzeichnisse und wendet dieselbe Token-Ersetzungslogik auf jede Vorlage an.
Secrets
Secrets sollten nicht durch das Brücken-Skript oder die Vorlage gehen. Verwenden Sie wrangler secret put (interaktiv) oder wrangler secret bulk (aus einer JSON-Datei, nützlich in CI). Secrets werden pro Worker in der Cloudflare-API verwaltet, nicht in Konfigurationsdateien.
In CI:
echo '{"API_KEY":"...","DB_PASSWORD":"..."}' | wrangler secret bulk
Vorschau-Umgebungen
Für PR-basierte Vorschau-Bereitstellungen kann das Sync-Skript ein Umgebungsargument akzeptieren:
{
"scripts": {
"deploy:preview": "npm run sync -- --env preview && wrangler deploy --env preview"
}
}
Fehlerbehebung
| Symptom | Wahrscheinliche Ursache |
|---|---|
sync schlägt fehl mit "Fehler beim Lesen der Terraform-Ausgaben" | Terraform wurde nicht angewendet, oder Sie sind im falschen Verzeichnis. Führen Sie zuerst terraform apply aus. |
Nicht ersetzte __TOKENS__ in generierter Konfiguration | Terraform-Ausgabenname stimmt nicht mit dem Token überein. Überprüfen Sie, ob die terraform output-Namen dem Token-Muster in Großbuchstaben entsprechen. |
| Wrangler-Deploy schlägt fehl mit "resource not found" | Die Ressourcen-ID von Terraform ist veraltet oder stammt aus der falschen Umgebung. Führen Sie erneut terraform apply und dann npm run sync aus. |
| Drift zwischen Terraform-Zustand und Cloudflare | Eine Ressource wurde außerhalb von Terraform geändert (möglicherweise durch Wrangler-Auto-Provisioning). Führen Sie terraform plan aus, um Drift zu erkennen und zu beheben. |
Referenz
scripts/sync-wrangler-config.mjs– Das Brücken-Skript (siehe Verzeichnisscripts/für eine produktionsreife Version mit Argumentparsing und Multi-Worker-Unterstützung)references/terraform-wrangler-mapping.md– Kartiert Terra...
[truncated for translation]