Infrastruktur als Code: Prinzipien, die skalieren
Behandle Infrastruktur wie Anwendungscode: versionieren, reviewen, testen und über eine Pipeline ausrollen, statt sich durch Konsolen zu klicken.

Sich durch die Konsole eines Cloud-Anbieters zu klicken, um Ressourcen anzulegen, funktioniert genau einmal. Beim zweiten Mal, wenn du dasselbe Setup brauchst — in Staging, in einer neuen Region, nach einem Incident — rekonstruierst du es aus dem Gedächtnis. Infrastruktur als Code (IaC) löst dieses Problem, indem Infrastruktur in deklarativen Dateien definiert wird, die versioniert, reviewt und über dieselben Pipelines ausgerollt werden wie Anwendungscode.
Deklarativ vs. imperativ
IaC-Tools lassen sich in zwei Kategorien einteilen. Deklarative Tools (Terraform, CloudFormation, Pulumi) beschreiben den gewünschten Endzustand. Imperative Tools (Ansible, Shell-Skripte) beschreiben die Schritte, um diesen Zustand zu erreichen.
# Declarative (Terraform): "I want this to exist"
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets-production"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}# Imperative (shell script): "Do these steps in order"
aws s3api create-bucket --bucket myapp-assets-production --region us-east-1
aws s3api put-bucket-versioning --bucket myapp-assets-production \
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket myapp-assets-production \
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'Der deklarative Ansatz gewinnt mit wachsender Größe, weil Terraform den Zustand nachverfolgt. Existiert der Bucket bereits mit einer anderen Konfiguration, aktualisiert Terraform ihn. Ändert jemand die Konfiguration manuell, zeigt terraform plan diese Abweichung. Das imperative Skript schlägt einfach fehl oder erzeugt Duplikate.
State-Management
Terraform verwaltet eine State-Datei, die deine deklarierten Ressourcen auf die tatsächlichen Cloud-Ressourcen abbildet. Diese Datei ist die maßgebliche Quelle für alles, was Terraform verwaltet.
# ❌ Local state file — works for one person, breaks for teams
terraform {
# Default: terraform.tfstate stored locally
}
# ✅ Remote state with locking — safe for team collaboration
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/api/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks" # Prevents concurrent modifications
}
}Modulstruktur
Mit wachsender Infrastruktur werden monolithische Konfigurationsdateien unhandlich. Module kapseln wiederverwendbare Infrastrukturmuster.
infrastructure/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── rds/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── ecs-service/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── environments/
│ ├── production/
│ │ └── main.tf
│ └── staging/
│ └── main.tf
└── global/
└── iam/
└── main.tf
# environments/production/main.tf — composes modules
module "vpc" {
source = "../../modules/vpc"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
environment = "production"
}
module "database" {
source = "../../modules/rds"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_class = "db.r6g.xlarge"
engine_version = "14.7"
environment = "production"
}
module "api_service" {
source = "../../modules/ecs-service"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
container_image = "registry.example.com/api:v1.2.0"
desired_count = 3
environment = "production"
}Review-Prozess für Änderungen
Infrastrukturänderungen sollten denselben Review-Prozess durchlaufen wie Anwendungscode. Führe terraform plan in der CI aus, um vor dem Anwenden genau zu zeigen, was sich ändern wird.
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ["infrastructure/**"]
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: infrastructure/environments/production
- name: Terraform Plan
run: terraform plan -no-color -out=tfplan
working-directory: infrastructure/environments/production
- name: Comment Plan on PR
uses: actions/github-script@v7
with:
script: |
const plan = require('fs').readFileSync('tfplan.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
});Häufige Fehler vermeiden
# ❌ Hardcoded values scattered across files
resource "aws_instance" "api" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = "subnet-0bb1c79de3EXAMPLE"
}
# ✅ Variables with sensible defaults and validation
variable "instance_type" {
description = "EC2 instance type for the API servers"
type = string
default = "t3.medium"
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Only t3 instance types are allowed."
}
}
resource "aws_instance" "api" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = module.vpc.private_subnet_ids[0]
}Die wichtigsten Punkte
- Deklaratives IaC erkennt Drift — Terraform zeigt, wenn die Realität vom Code abweicht
- Remote State mit Locking ist für Teams Pflicht — lokale State-Dateien führen zu Konflikten
- Module schaffen wiederverwendbare Infrastrukturmuster — kombiniere sie je nach Umgebung
- Führe plan bei jedem PR in der CI aus — Reviewer sollten genau sehen, was sich ändert
- Parametrisiere alles — hartcodierte Werte verhindern die Wiederverwendung über Umgebungen hinweg
- Versioniere deinen Infrastrukturcode — mit demselben Branching, Reviewing und CI/CD wie Anwendungscode


