Technischer Leitfaden

Infrastructure as Code mit 200 Ressourcen: Was Terraform-Tutorials dir nicht sagen

Produktions-IaC-Muster für echte Systeme. State Management im großen Maßstab, Modul-Design, CDK + Terraform Hybrid, Drift-Erkennung, GitOps mit Flux und 30+ AWS-Services verwalten.

20. April 202616 Min. LesezeitOronts Engineering Team

IaC ist nicht "terraform init"

Jedes Terraform-Tutorial fängt gleich an: schreib eine .tf-Datei, führe terraform init aus, dann terraform apply, und schau zu, wie deine EC2-Instanz erscheint. Damit kommst du von null auf eine Ressource. Es bereitet dich nicht darauf vor, 200+ Ressourcen über 30 AWS-Services hinweg zu verwalten, mit einem Team von Ingenieuren, die alle sicher Infrastrukturänderungen machen müssen.

Wir verwalten Infrastruktur für mehrere Produktionssysteme, von Kubernetes-Clustern mit Pimcore und OpenSearch bis zu Serverless-Architekturen mit Lambda, DynamoDB und API Gateway. Die Muster in diesem Artikel haben die Produktion überlebt. Wie wir Anwendungen auf dieser Infrastruktur deployen, findest du auf unserer Cloud-Services Seite.

State Management im großen Maßstab

Terraform State ist die kritischste Datei deiner Infrastruktur. Er bildet deine .tf-Dateien auf echte Ressourcen ab. Verlier ihn, und Terraform weiß nicht, was existiert. Beschädige ihn, und Terraform zerstört möglicherweise Produktionsressourcen.

Remote State (Nicht verhandelbar)

# backend.tf
terraform {
    backend "s3" {
        bucket         = "company-terraform-state"
        key            = "prod/platform/terraform.tfstate"
        region         = "eu-central-1"
        encrypt        = true
        dynamodb_table = "terraform-locks"
    }
}
RegelWarum
Remote State in S3 (oder Äquivalent)Lokale State-Dateien gehen verloren, können nicht geteilt werden
Verschlüsselung im RuhezustandState enthält Secrets (Datenbankpasswörter, API-Keys)
DynamoDB LockingVerhindert, dass zwei Ingenieure gleichzeitig apply ausführen
Versionierung auf dem S3-BucketWiederherstellung nach State-Korruption durch Rollback

State-Organisation

Eine riesige State-Datei für alles ist eine Wartungskatastrophe. Aufteilen nach Umgebung und Domäne:

terraform/
├── environments/
│   ├── prod/
│   │   ├── platform/       # EKS, VPC, Netzwerk
│   │   ├── databases/      # RDS, ElastiCache, OpenSearch
│   │   ├── compute/        # Lambda, ECS, Fargate
│   │   ├── storage/        # S3-Buckets, CloudFront
│   │   └── monitoring/     # CloudWatch, Alarme
│   ├── staging/
│   │   └── (gleiche Struktur)
│   └── dev/
│       └── (gleiche Struktur)
├── modules/
│   ├── vpc/
│   ├── eks-cluster/
│   ├── rds-postgres/
│   ├── opensearch/
│   ├── redis/
│   └── lambda-function/
└── global/
    ├── iam/                 # IAM-Rollen, Policies
    ├── route53/             # DNS-Zonen
    └── ecr/                 # Container-Registries

Jedes Verzeichnis ist ein separater Terraform-Workspace mit eigener State-Datei. Änderungen am Netzwerk gefährden nicht die Datenbank. Änderungen am Monitoring erfordern keinen Plan, der jede Ressource anfasst.

Cross-State-Referenzen

Workspaces müssen sich gegenseitig referenzieren. Der VPC-Workspace gibt die VPC-ID aus. Der Datenbank-Workspace liest sie:

# In databases/main.tf
data "terraform_remote_state" "platform" {
    backend = "s3"
    config = {
        bucket = "company-terraform-state"
        key    = "prod/platform/terraform.tfstate"
        region = "eu-central-1"
    }
}

resource "aws_db_instance" "main" {
    vpc_security_group_ids = [data.terraform_remote_state.platform.outputs.db_security_group_id]
    db_subnet_group_name   = data.terraform_remote_state.platform.outputs.db_subnet_group_name
}

Modul-Design

Wann du ein Modul extrahieren solltest

Nicht jede Ressource braucht ein Modul. Extrahiere, wenn:

  • Das gleiche Muster an 3+ Stellen verwendet wird (DRY)
  • Die Ressourcengruppe eine klare Grenze hat (VPC, Datenbank-Cluster)
  • Die Konfiguration sinnvolle Defaults hat, die Duplikation reduzieren

Extrahiere nicht, wenn:

  • Es nur einmal verwendet wird (vorzeitige Abstraktion)
  • Das Modul 20+ Variablen hätte (zu viele Stellschrauben)
  • Die Abstraktion wichtige Details versteckt (Netzwerk, Sicherheit)

Modul-Interface-Design

Ein gutes Modul hat wenige erforderliche Variablen, sinnvolle Defaults und klare Outputs:

# modules/rds-postgres/variables.tf
variable "name" {
    description = "Name der Datenbankinstanz"
    type        = string
}

variable "vpc_id" {
    description = "VPC, in die deployt wird"
    type        = string
}

variable "subnet_ids" {
    description = "Subnetze für die DB-Subnetzgruppe"
    type        = list(string)
}

variable "instance_class" {
    description = "RDS-Instanztyp"
    type        = string
    default     = "db.t3.medium"
}

variable "engine_version" {
    description = "PostgreSQL-Version"
    type        = string
    default     = "15.4"
}

variable "allocated_storage" {
    description = "Speicher in GB"
    type        = number
    default     = 50
}

variable "multi_az" {
    description = "Multi-AZ-Deployment aktivieren"
    type        = bool
    default     = false  # true für Prod, false für Staging/Dev
}

Der Modul-Konsument schreibt:

module "database" {
    source     = "../../modules/rds-postgres"
    name       = "pimcore-prod"
    vpc_id     = module.vpc.vpc_id
    subnet_ids = module.vpc.private_subnet_ids
    multi_az   = true
}

Fünf Zeilen statt fünfzig. Das Modul kümmert sich um Security Groups, Parameter Groups, Subnetzgruppen, Verschlüsselung, Backup-Retention und Monitoring.

CDK + Terraform: Der pragmatische Hybrid

Manche Teams setzen voll auf CDK. Andere voll auf Terraform. Wir nutzen beides, und es funktioniert.

AnwendungsfallToolWarum
Netzwerk, Datenbanken, ClusterTerraformDeklarativ, Plan-vor-Apply, State Management
Lambda-Funktionen + API GatewayCDKBesseres Lambda-Bundling, API-Gateway-Constructs
Komplexe IAM-PoliciesCDKTypeScript-Logik für bedingte Policies
Kubernetes-RessourcenKustomize + FluxGitOps, Reconciliation-Loops
Statische InfrastrukturTerraformEinfach, lesbar, gut verstanden

Die Grenze ist klar: Terraform verwaltet Infrastruktur, die sich selten ändert (VPC, RDS, EKS-Cluster). CDK verwaltet Infrastruktur, die sich mit Application-Deployments ändert (Lambda-Funktionen, API-Routen). Kustomize + Flux verwaltet Kubernetes-Workloads.

Die Koexistenz funktioniert über Outputs. Terraform gibt die VPC-ID, den Cluster-Endpoint und den Datenbank-Connection-String aus. CDK liest sie aus dem SSM Parameter Store oder dem Terraform Remote State.

Das Drift-Problem

Drift passiert, wenn jemand Infrastruktur über die Konsole ändert (ClickOps), über einen CLI-Befehl oder über ein anderes Tool. Der reale Zustand weicht vom Terraform State ab.

Drift erkennen

# Plan regelmäßig ausführen (CI, geplanter Job)
terraform plan -detailed-exitcode

# Exit-Codes:
# 0 = keine Änderungen (State stimmt mit Realität überein)
# 1 = Fehler
# 2 = Änderungen erkannt (Drift!)

Führe Drift-Erkennung in CI nach Zeitplan aus (täglich für Produktion, wöchentlich für Staging). Alarmiere bei erkanntem Drift. Nicht automatisch beheben. Zuerst untersuchen.

Häufige Drift-Ursachen

UrsachePrävention
Konsolenänderungen (ClickOps)"Keine Konsolenänderungen"-Policy durchsetzen. SCPs zur Einschränkung verwenden.
Auto-Scaling-ÄnderungenAuto-Scaling-Attribute in Terraform ignorieren (lifecycle { ignore_changes })
AWS-Service-UpdatesProvider-Versionen pinnen. Bewusst aktualisieren.
Terraform eines anderen TeamsSeparate State-Dateien pro Team/Domäne.
Manueller Hotfix während eines IncidentsÄnderung dokumentieren. Nach dem Incident in Terraform umsetzen.
# Auto-Scaling-Änderungen ignorieren (erwarteter Drift)
resource "aws_ecs_service" "app" {
    desired_count = 2

    lifecycle {
        ignore_changes = [desired_count]  # Auto-Scaling ändert diesen Wert
    }
}

GitOps mit Flux

Für Kubernetes-Workloads nutzen wir Flux für GitOps. Der Reconciliation-Loop ersetzt kubectl apply durch ein Pull-basiertes Modell.

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Git Repo    │────▶│  Flux       │────▶│  Kubernetes  │
│  (Manifests) │     │  Controller │     │  Cluster     │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
                          │ Reconciliation alle 1 Min.
                          │ Erkennt Drift
                          │ Wendet automatisch an
# flux-system/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
    name: platform
    namespace: flux-system
spec:
    interval: 1m
    sourceRef:
        kind: GitRepository
        name: infrastructure
    path: ./kubernetes/resources/overlay/prod
    prune: true
    healthChecks:
        - apiVersion: apps/v1
          kind: Deployment
          name: pimcore
          namespace: production

Flux pollt das Git-Repo jede Minute. Wenn sich Manifests geändert haben, wendet es sie an. Wenn jemand eine Ressource manuell geändert hat (Drift), setzt Flux sie auf den Git-Stand zurück. Das ist echte Reconciliation, nicht nur Deployment-Automatisierung.

Sealed Secrets

Secrets dürfen nicht als Klartext in Git. Verwende Bitnami Sealed Secrets:

# Secret für den Cluster verschlüsseln
kubeseal --cert sealed-secrets.pem \
    -f secrets/database-secrets.yaml \
    -o yaml > secrets/database-secrets-sealed.yaml

# Die versiegelte Version committen (sicher in Git)
# Flux wendet sie an, der Controller entschlüsselt sie im Cluster

Wie wir Secrets in Pimcore-Kubernetes-Deployments konkret handhaben, beschreibt unser Pimcore-Upgrade-Guide, der die vollständige Deployment-Reihenfolge behandelt.

30+ AWS-Services verwalten

Im Enterprise-Maßstab verwaltest du viele Services. Organisation ist entscheidend.

Service-Katalog

KategorieServicesTerraform-Modul?
NetzwerkVPC, Subnetze, NAT, ALB, Route53Ja (vpc-Modul)
ComputeEKS, ECS Fargate, LambdaJa (pro Service-Typ)
DatenbankRDS PostgreSQL, DynamoDBJa (rds-postgres-Modul)
CacheElastiCache RedisJa (redis-Modul)
SucheOpenSearchJa (opensearch-Modul)
SpeicherS3, EFSInline (einfach genug)
CDNCloudFrontInline
MessagingSQS, MSK (Kafka), RabbitMQInline
AuthCognitoCDK (komplexe Konfiguration)
MonitoringCloudWatch, X-RayInline
CI/CDECR, CodeBuildInline
SicherheitIAM, KMS, Secrets ManagerGlobaler Workspace

Tagging-Strategie

Jede Ressource muss getaggt sein für Kostenverteilung, Ownership und Lifecycle-Management:

locals {
    common_tags = {
        Environment = var.environment    # prod, staging, dev
        Project     = var.project_name   # pimcore, commerce, ai
        ManagedBy   = "terraform"
        Team        = var.team           # platform, backend, data
        CostCenter  = var.cost_center
    }
}

resource "aws_instance" "example" {
    tags = merge(local.common_tags, {
        Name = "pimcore-web-1"
        Role = "web"
    })
}

Filtere den AWS Cost Explorer nach dem Project-Tag, um genau zu sehen, was jedes System kostet. Filtere nach ManagedBy, um manuell erstellte Ressourcen zu finden (nicht als "terraform" getaggt).

Häufige Fehler

  1. Eine State-Datei für alles. Ein terraform plan, der 200 Ressourcen anfasst, dauert Minuten, und ein Fehler betrifft alles. Aufteilen nach Domäne und Umgebung.

  2. Kein State Locking. Zwei Ingenieure führen gleichzeitig terraform apply aus. Die Änderungen von einem gehen verloren oder der State ist beschädigt. DynamoDB Locking verwenden.

  3. Module mit 20+ Variablen. Wenn dein Modul-Interface genauso komplex ist wie die rohen Ressourcen, bringt die Abstraktion keinen Mehrwert. Halte Modul-Interfaces klein.

  4. Drift automatisch beheben. Drift zu erkennen ist gut. Ihn automatisch zu fixen ist gefährlich. Der "Drift" könnte ein valider Hotfix während eines Incidents sein. Erst untersuchen, dann revertieren.

  5. Secrets in State-Dateien. Terraform State enthält jedes Attribut jeder Ressource, einschließlich Datenbankpasswörter. State im Ruhezustand verschlüsseln und Zugriff einschränken.

  6. Kein Provider-Version-Pinning. Ein Provider-Update ändert das Ressourcenverhalten. Versionen in required_providers pinnen und bewusst aktualisieren.

  7. ClickOps für "nur dieses eine Mal". Konsolenänderungen erzeugen Drift, der bis zum nächsten terraform plan unsichtbar ist. Infrastructure-as-Code für alles durchsetzen.

  8. Kein Tagging. Ohne Tags kannst du keine Kosten zuordnen, keine Ownership identifizieren und keine manuell erstellten Ressourcen finden.

Wichtigste Erkenntnisse

  • State nach Domäne und Umgebung aufteilen. Netzwerk, Datenbanken, Compute und Monitoring sollten separate Workspaces sein. Änderungen an einem sollten nicht einen anderen gefährden.

  • Module sind für Muster, nicht für Abstraktion. Extrahiere, wenn die gleiche Ressourcengruppe 3+ Mal vorkommt. Erstelle keine Module für einmalige Ressourcen.

  • CDK + Terraform ist pragmatisch. Terraform für statische Infrastruktur, CDK für Lambda/API Gateway, Kustomize + Flux für Kubernetes. Jedes Tool dort, wo es am stärksten ist.

  • Drift-Erkennung ist ein geplanter Job. Führe terraform plan täglich in CI aus. Alarmiere bei Drift. Untersuche vor dem Beheben.

  • GitOps mit Flux bietet echte Reconciliation. Nicht nur Deployment-Automatisierung. Flux erkennt und revertiert manuelle Änderungen. Sealed Secrets halten Credentials sicher in Git.

  • Alles taggen. Environment, Projekt, Team, Cost Center, Managed-by. Ohne Tags sind Kostenzuordnung und Ressourcen-Auditing unmöglich.

Wir verwalten Infrastruktur für Cloud-Deployments, Custom-Software-Plattformen und Data-Engineering-Systeme. Wenn du Hilfe mit IaC im großen Maßstab brauchst, sprich mit unserem Team oder fordere ein Angebot an.

Behandelte Themen

Terraform ProduktionIaC LektionenInfrastructure as Code PraxisTerraform AWSCDK vs TerraformTerraform State ManagementGitOps FluxTerraform Module

Bereit, produktionsreife KI-Systeme zu bauen?

Unser Team ist spezialisiert auf produktionsreife KI-Systeme. Lass uns besprechen, wie wir deinem Unternehmen helfen können.

Gespräch starten