Zum Inhalt springen

Terraform-Patterns für die Teamzusammenarbeit

Workspaces, Remote State, Module-Registries und Review-Workflows – die Patterns, die Terraform beherrschbar halten, wenn ein ganzes Team mitarbeitet.

3 Min. Lesezeit
Terraform-Workspace-Struktur mit Konfigurationen für mehrere Umgebungen

Terraform funktioniert gut, wenn ein einzelner Entwickler ein kleines Projekt betreut. Sobald ein ganzes Team gleichzeitig an der Infrastruktur arbeitet, wird alles, was für eine Person funktioniert hat, zur Konfliktquelle: beschädigte State-Dateien, auseinanderlaufende Umgebungen und undokumentierte Änderungen an Ressourcen. Diese Patterns adressieren genau die Zusammenarbeitsprobleme, die auftreten, sobald Infrastruktur zur Aufgabe eines ganzen Teams wird.

State-Isolation

Die häufigste Ursache für Terraform-Katastrophen im Team ist gemeinsam genutzter State ohne saubere Isolation. Ein Entwickler führt terraform apply in der Produktionsumgebung aus, während ein anderer gerade Änderungen testet – und beide schreiben in dieselbe State-Datei.

hclhcl
# ❌ Single state file for all environments
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "terraform.tfstate"  # One file for everything
    region = "us-east-1"
  }
}
 
# ✅ Separate state per environment
# infrastructure/environments/production/backend.tf
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
 
# infrastructure/environments/staging/backend.tf
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "staging/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Ein Lock über DynamoDB verhindert gleichzeitige Applies. Wenn bereits jemand den Produktions-State verändert, wartet das terraform apply des zweiten Entwicklers oder schlägt mit einem Lock-Fehler fehl – statt den State stillschweigend zu beschädigen.

Module-Versionierung

Gemeinsam genutzte module ohne Versionierung erzeugen eine gefährliche Kopplung: Aktualisiert man ein module für einen Service, kann das jeden anderen Service brechen, der dieses module verwendet.

hclhcl
# ❌ Referencing modules by local path — changes affect everyone immediately
module "ecs_service" {
  source = "../../modules/ecs-service"
  # If someone modifies this module, every consumer changes on next apply
}
 
# ✅ Versioned module references — consumers upgrade explicitly
module "ecs_service" {
  source  = "app.terraform.io/mycompany/ecs-service/aws"
  version = "~> 2.1.0"  # Accept 2.1.x patches, pin major.minor
 
  service_name   = "api"
  container_port = 3000
  desired_count  = 3
}

Wenn Teams ihre module in einer Registry veröffentlichen (Terraform Cloud, Artifactory oder auch nur Git-Tags), entscheiden die Konsumenten selbst, wann sie aktualisieren. Ein module-Update durchläuft seinen eigenen PR-, Test- und Release-Zyklus, bevor irgendeine Infrastruktur es tatsächlich nutzt.

Variablen-Hierarchie

Teams brauchen ein einheitliches Muster, um Variablen über mehrere Umgebungen hinweg zu verwalten, ohne die Konfiguration zu duplizieren.

hclhcl
# modules/ecs-service/variables.tf — module defines its interface
variable "service_name" {
  description = "Name of the ECS service"
  type        = string
}
 
variable "desired_count" {
  description = "Number of task replicas"
  type        = number
  default     = 2
}
 
variable "cpu" {
  description = "CPU units for the task (1024 = 1 vCPU)"
  type        = number
  default     = 256
}
 
variable "memory" {
  description = "Memory in MB for the task"
  type        = number
  default     = 512
}
hclhcl
# environments/production/terraform.tfvars
desired_count = 5
cpu           = 1024
memory        = 2048
 
# environments/staging/terraform.tfvars
desired_count = 2
cpu           = 256
memory        = 512

Jedes Umgebungsverzeichnis enthält seine eigene terraform.tfvars-Datei. Die Standardwerte von module dienen als sinnvolle Basis, die einzelne Umgebungen bei Bedarf überschreiben können.

Code-Review-Workflow

Jede Infrastrukturänderung sollte einen sichtbaren Plan erzeugen, den Reviewer vor dem Merge bewerten können.

ymlyaml
# .github/workflows/terraform-pr.yml
name: Terraform Plan
on:
  pull_request:
    paths: ["infrastructure/**"]
 
jobs:
  plan:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        environment: [staging, production]
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
 
      - name: Init
        run: terraform init
        working-directory: infrastructure/environments/${{ matrix.environment }}
 
      - name: Validate
        run: terraform validate
        working-directory: infrastructure/environments/${{ matrix.environment }}
 
      - name: Plan
        id: plan
        run: |
          terraform plan -no-color -input=false \
            -out=${{ matrix.environment }}.tfplan 2>&1 | tee plan_output.txt
        working-directory: infrastructure/environments/${{ matrix.environment }}

Die im PR geposteten Plan-Ausgaben zeigen genau, welche Ressourcen erstellt, geändert oder gelöscht werden. So erkennen Reviewer gefährliche Änderungen – etwa das Löschen einer Datenbank – bevor sie in die Produktion gelangen.

Namenskonventionen

Einheitliche Namensgebung beseitigt jede Unklarheit darüber, was von Terraform verwaltet wird und was manuell angelegt wurde.

hclhcl
# ❌ Arbitrary names — impossible to trace back to Terraform
resource "aws_security_group" "sg1" {
  name = "my-sg"
}
 
# ✅ Structured naming with standard tags
locals {
  name_prefix = "${var.project}-${var.environment}"
}
 
resource "aws_security_group" "api" {
  name        = "${local.name_prefix}-api-sg"
  description = "Security group for API servers"
  vpc_id      = var.vpc_id
 
  tags = {
    Name        = "${local.name_prefix}-api-sg"
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    Module      = "ecs-service"
  }
}

Das Tag ManagedBy = "terraform" zeigt in der Konsole auf einen Blick, welche Ressourcen über IaC verwaltet werden und welche manuell entstanden sind.

Bestehende Ressourcen importieren und übernehmen

Nur selten starten Teams auf der grünen Wiese. Bereits vorhandene, manuell erstellte Ressourcen müssen in die Verwaltung durch Terraform importiert werden, ohne sie neu anzulegen.

shbash
# Import an existing RDS instance into Terraform state
terraform import aws_db_instance.main myapp-production-db
 
# After import, write the matching configuration
# terraform plan should show no changes if config matches reality

Die wichtigsten Punkte

  1. State pro Umgebung isolieren – getrennte State-Dateien mit DynamoDB-Lock verhindern Katastrophen durch gleichzeitige Änderungen
  2. Module versionieren – die Konsumenten sollten selbst bestimmen, wann sie module-Änderungen übernehmen
  3. Tfvars pro Umgebung verwenden – gleiche module, unterschiedliche Parameter, einheitliche Struktur
  4. Plan in der CI bei jedem PR – Infrastrukturänderungen sichtbar und überprüfbar machen, bevor sie angewendet werden
  5. Namensgebung und Tags standardisieren – ManagedBy = "terraform" macht IaC-verwaltete Ressourcen sofort erkennbar
  6. Vor dem Neuanlegen importieren – bestehende Ressourcen sicher in die Terraform-Verwaltung überführen
Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX