Zum Inhalt springen

OpenTofu

Open-Source Infrastructure-as-Code-Tool und Community-getriebener Fork von Terraform.

BefehlBeschreibung
brew install opentofuInstallation auf macOS mit Homebrew
snap install --classic opentofuInstallation auf Linux mit Snap
choco install opentofuInstallation auf Windows mit Chocolatey
curl -fsSL https://get.opentofu.org/install-opentofu.sh | shInstallation über Shell-Skript
docker run -it ghcr.io/opentofu/opentofuAusführung über Docker
tofu --versionInstallierte Version anzeigen
tofu -helpVerfügbare Befehle anzeigen
# tofuenv verwenden (wie tfenv für OpenTofu)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc

# Verfügbare Versionen auflisten
tofuenv list-remote

# Bestimmte Version installieren
tofuenv install 1.8.0
tofuenv use 1.8.0

# Version im Projekt festlegen
echo "1.8.0" > .opentofu-version
BefehlBeschreibung
tofu initArbeitsverzeichnis initialisieren und Provider herunterladen
tofu init -upgradeNeu initialisieren und Provider-/Modulversionen aktualisieren
tofu init -backend-config=prod.hclMit Backend-Konfigurationsdatei initialisieren
tofu planInfrastrukturänderungen vorschauen
tofu plan -out=plan.tfplanPlan in Datei speichern für späteres Apply
tofu plan -target=aws_instance.webNur Änderungen für bestimmte Ressource planen
tofu plan -var-file=prod.tfvarsPlan mit bestimmter Variablendatei
tofu applyInfrastrukturänderungen anwenden (fragt nach Bestätigung)
tofu apply -auto-approveÄnderungen ohne Bestätigung anwenden
tofu apply plan.tfplanEinen gespeicherten Plan anwenden
tofu destroyGesamte verwaltete Infrastruktur zerstören
tofu destroy -auto-approveOhne Bestätigung zerstören
tofu destroy -target=aws_instance.webNur bestimmte Ressource zerstören
BefehlBeschreibung
tofu validateKonfigurationssyntax validieren
tofu fmtKonfigurationsdateien formatieren
tofu fmt -checkPrüfen ob Dateien formatiert sind (CI-freundlich)
tofu fmt -recursiveAlle Dateien in Unterverzeichnissen formatieren
tofu outputAlle Ausgabewerte anzeigen
tofu output -jsonAusgaben im JSON-Format anzeigen
tofu output db_passwordBestimmten Ausgabewert anzeigen
tofu showAktuellen State in lesbarer Form anzeigen
tofu show -jsonState im JSON-Format anzeigen
tofu show plan.tfplanGespeicherte Plan-Details anzeigen
tofu graphRessourcen-Abhängigkeitsgraph generieren (DOT-Format)
tofu graph | dot -Tpng > graph.pngAbhängigkeitsgraph als PNG rendern
tofu refreshState mit realer Infrastruktur abgleichen
tofu providersIn der Konfiguration verwendete Provider auflisten
# Block für erforderliche Provider
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }

  # State-Verschlüsselung (OpenTofu-exklusives Feature)
  encryption {
    method "aes_gcm" "default" {
      keys = key_provider.pbkdf2.mykey
    }

    state {
      method   = method.aes_gcm.default
      enforced = true
    }

    plan {
      method   = method.aes_gcm.default
      enforced = true
    }

    key_provider "pbkdf2" "mykey" {
      passphrase = var.state_passphrase
    }
  }
}

# Provider mit Standardkonfiguration
provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      ManagedBy   = "opentofu"
      Environment = var.environment
      Project     = var.project_name
    }
  }
}

# Provider-Alias für Multi-Region
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

# Aliased Provider auf einer Ressource verwenden
resource "aws_instance" "west_server" {
  provider      = aws.west
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
}
# Grundlegende Ressource
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = "${var.project_name}-web"
  }
}

# Datenquelle (bestehende Ressourcen lesen)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# Lokale Werte
locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "opentofu"
  }

  az_count = length(data.aws_availability_zones.available.names)
}

# Count und for_each
resource "aws_subnet" "public" {
  count             = local.az_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-public-${count.index}"
    Type = "public"
  })
}

resource "aws_security_group_rule" "ingress" {
  for_each = {
    http  = { port = 80, cidrs = ["0.0.0.0/0"] }
    https = { port = 443, cidrs = ["0.0.0.0/0"] }
    ssh   = { port = 22, cidrs = [var.admin_cidr] }
  }

  type              = "ingress"
  from_port         = each.value.port
  to_port           = each.value.port
  protocol          = "tcp"
  cidr_blocks       = each.value.cidrs
  security_group_id = aws_security_group.main.id
}
BefehlBeschreibung
variable "name" { type = string }Eine String-Variable definieren
variable "port" { type = number default = 8080 }Variable mit Standardwert
variable "tags" { type = map(string) }Map-Variable
variable "cidrs" { type = list(string) }Listen-Variable
variable "config" { type = object({ ... }) }Objekt-Variable mit Struktur
variable "db_pass" { sensitive = true }Variable als sensitiv markieren (in Ausgabe verborgen)
variable "env" { validation { ... } }Variable mit benutzerdefinierter Validierung
BefehlBeschreibung
tofu plan -var="name=value"Variable über CLI übergeben
tofu plan -var-file="prod.tfvars"Variablen aus Datei übergeben
TF_VAR_name=value tofu planÜber Umgebungsvariable übergeben
terraform.tfvars-Datei erstellenAutomatisch geladene Variablenwerte
*.auto.tfvars-Datei erstellenAutomatisch geladene Variablendateien
# variables.tf
variable "environment" {
  type        = string
  description = "Deployment environment"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "instance_config" {
  type = object({
    instance_type = string
    volume_size   = number
    encrypted     = bool
  })
  default = {
    instance_type = "t3.micro"
    volume_size   = 20
    encrypted     = true
  }
}

variable "database_password" {
  type      = string
  sensitive = true
}

# outputs.tf
output "vpc_id" {
  description = "The ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "List of public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "web_url" {
  description = "URL of the web server"
  value       = "https://${aws_lb.web.dns_name}"
}

output "db_connection_string" {
  description = "Database connection string"
  value       = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
  sensitive   = true
}
# prod.tfvars
environment = "prod"
instance_config = {
  instance_type = "t3.large"
  volume_size   = 100
  encrypted     = true
}

# dev.tfvars
environment = "dev"
instance_config = {
  instance_type = "t3.micro"
  volume_size   = 20
  encrypted     = false
}
BefehlBeschreibung
tofu state listAlle Ressourcen im State auflisten
tofu state show aws_instance.webDetails einer bestimmten Ressource anzeigen
tofu state mv aws_instance.old aws_instance.newRessource im State umbenennen
tofu state rm aws_instance.webRessource aus State entfernen (behält reale Infrastruktur)
tofu state pullRemote-State auf stdout ausgeben
tofu state pushLokalen State an Remote-Backend übertragen
tofu import aws_instance.web i-1234567Bestehende Ressource in State importieren
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/awsProvider im State ersetzen
tofu force-unlock LOCK_IDFestsitzende State-Sperre aufheben
# S3-Backend (am häufigsten)
terraform {
  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tofu-locks"
    encrypt        = true
  }
}

# PostgreSQL-Backend
terraform {
  backend "pg" {
    conn_str = "postgres://user:pass@db.example.com/tofu_state?sslmode=require"
  }
}

# HTTP-Backend (funktioniert mit GitLab, Terraform Cloud-Alternativen)
terraform {
  backend "http" {
    address        = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod"
    lock_address   = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
    unlock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
    username       = "gitlab-ci-token"
    password       = var.ci_token
  }
}

# Lokales Backend (Standard, für Entwicklung)
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}
# State-Verschlüsselung mit AWS KMS
terraform {
  encryption {
    key_provider "aws_kms" "main" {
      kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }

    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.main
    }

    state {
      method   = method.aes_gcm.default
      enforced = true
    }
  }
}
BefehlBeschreibung
module "vpc" { source = "./modules/vpc" }Ein lokales Modul verwenden
module "vpc" { source = "github.com/org/repo//modules/vpc" }Ein GitHub-Modul verwenden
module "vpc" { source = "registry.opentofu.org/..." }OpenTofu-Registry-Modul verwenden
tofu getModule herunterladen und aktualisieren
tofu get -updateAktualisierung aller Module erzwingen
modules/
└── vpc/
    ├── main.tf          # Ressourcen
    ├── variables.tf     # Eingabevariablen
    ├── outputs.tf       # Ausgabewerte
    ├── versions.tf      # Provider-Anforderungen
    └── README.md        # Nutzungsdokumentation
# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block           = var.cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = merge(var.tags, { Name = var.name })
}

resource "aws_subnet" "public" {
  count                   = length(var.public_subnets)
  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnets[count.index]
  availability_zone       = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = merge(var.tags, {
    Name = "${var.name}-public-${count.index}"
  })
}

# modules/vpc/variables.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "azs" { type = list(string) }
variable "public_subnets" { type = list(string) }
variable "tags" { type = map(string) default = {} }

# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }

# Das Modul verwenden
module "vpc" {
  source = "./modules/vpc"

  name           = "production"
  cidr           = "10.0.0.0/16"
  azs            = ["us-east-1a", "us-east-1b", "us-east-1c"]
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  tags           = local.common_tags
}
BefehlBeschreibung
terraform durch tofu in Befehlen ersetzenCLI-Befehle sind Drop-In-kompatibel
tofu init -upgradeNeu initialisieren mit OpenTofu-Providern
Bestehende .tf-Dateien unverändert lassenHCL-Syntax ist vollständig kompatibel
Bestehende .tfstate-Dateien beibehaltenState-Format ist kompatibel
Bestehende .terraform.lock.hcl beibehaltenLock-Datei ist kompatibel
CI/CD auf opentofu/setup-opentofu Action aktualisierenGitHub Actions umstellen
Migration von Terraform zu OpenTofu:

1. OpenTofu neben Terraform installieren
2. Im Projektverzeichnis ausführen: tofu init
3. Ausführen: tofu plan
   - Ausgabe mit terraform plan vergleichen
   - Sollte identisch sein (keine Änderungen)
4. 'terraform' durch 'tofu' in CI/CD-Pipelines ersetzen
5. GitHub Actions aktualisieren:
   - Alt: hashicorp/setup-terraform
   - Neu: opentofu/setup-opentofu
6. Lock-Datei-Provider-Quellen aktualisieren (falls nötig):
   - tofu providers lock
7. State-Verschlüsselung aktivieren (OpenTofu-exklusiv):
   - Encryption-Block zum terraform {}-Block hinzufügen
8. Modul-Registry-Referenzen umstellen:
   - registry.terraform.io → registry.opentofu.org
   - (Beide funktionieren noch, aber OpenTofu-Registry wird bevorzugt)
name: OpenTofu Plan & Apply
on:
  push:
    branches: [main]
  pull_request:

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: 1.8.0

      - name: Init
        run: tofu init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Plan
        run: tofu plan -out=plan.tfplan -no-color
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Apply (nur main)
        if: github.ref == 'refs/heads/main'
        run: tofu apply -auto-approve plan.tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

State-Dateien im Ruhezustand verschlüsseln — nicht verfügbar in Terraform. Unterstützt PBKDF2, AWS KMS, GCP KMS und Azure Key Vault Key-Provider.

Im Gegensatz zur serverseitigen Verschlüsselung von Terraform Cloud verschlüsselt OpenTofu den State, bevor er Ihren Rechner verlässt, sodass kein Dritter Ihren Infrastruktur-State lesen kann.

OpenTofu pflegt eine eigene Provider- und Modul-Registry unter registry.opentofu.org, die langfristige Verfügbarkeit unabhängig von HashiCorps Lizenzentscheidungen sicherstellt.

  1. Remote-State mit Locking verwenden — ein S3 + DynamoDB-Backend (oder Äquivalent) konfigurieren, um gleichzeitige Änderungen zu verhindern und Teamzusammenarbeit zu ermöglichen.

  2. State-Verschlüsselung aktivieren — OpenTofus exklusives Verschlüsselungsfeature mit KMS oder PBKDF2 verwenden, um sensible Daten in State-Dateien zu schützen.

  3. Provider- und Modulversionen festlegen~>-Constraints verwenden (z.B. ~> 5.0), um Patch-Updates zu erlauben und Breaking Changes zu verhindern.

  4. Workspaces oder Verzeichnistrennung für Umgebungen verwenden — Dev-, Staging- und Production-State getrennt halten, um versehentliche umgebungsübergreifende Änderungen zu verhindern.

  5. Module für wiederverwendbare Muster schreiben — häufige Muster (VPCs, Datenbanken, Kubernetes-Cluster) in Module mit klaren Ein- und Ausgaben extrahieren.

  6. Variablen mit Constraints validierenvalidation-Blöcke zu Variablen hinzufügen, um ungültige Werte abzufangen, bevor sie den Cloud-Provider erreichen.

  7. Pläne vor dem Anwenden speicherntofu plan -out=plan.tfplan und dann tofu apply plan.tfplan verwenden, um sicherzustellen, dass genau das angewendet wird, was überprüft wurde.

  8. default_tags bei Providern verwenden — organisationsweite Tags (ManagedBy, Environment, Project) auf Provider-Ebene setzen, um konsistentes Tagging sicherzustellen.

  9. Bestehende Ressourcen importierentofu import verwenden, um nicht verwaltete Infrastruktur unter OpenTofu-Kontrolle zu bringen, anstatt sie neu zu erstellen.

  10. tofu fmt -check in CI ausführen — konsistente Formatierung über alle Teammitglieder hinweg erzwingen, indem CI-Builds bei unformatierten Dateien fehlschlagen.

  11. Datenquellen für externe Referenzen verwenden — bestehende Infrastruktur (AMIs, VPCs, DNS-Zonen) mit data-Blöcken referenzieren, anstatt IDs hartzucodieren.

  12. Pläne sorgfältig prüfen — immer die Plan-Ausgabe vor dem Anwenden lesen. Auf destroy- und replace-Aktionen achten, die Ausfallzeiten verursachen könnten.