Alle Artikel
12. September 2026
8 mins

Terraform-Drift: wenn Konsole und Code auseinanderlaufen

Von Tim Rutte, Cloud & Software ArchitectThemaAWS & Cloud

Eine blau gezeichnete Konstruktionszeichnung auf Transparentpapier über einem Holzmodell; die Kanten decken sich nicht.

Die Erzählung über Infrastruktur als Code ist ordentlich: Alles steht im Repository, die Umgebung entsteht auf Knopfdruck, und niemand klickt mehr in einer Konsole herum. In Umgebungen, die vor dem Code da waren, ist die Wirklichkeit eine andere.

Dort gibt es Ressourcen, die niemand codifiziert hat, Ressourcen, die im Code stehen und in der Konsole verändert wurden, und Ressourcen, die es im Code gibt und in der Wirklichkeit nicht mehr. Der Unterschied zwischen beschriebenem und tatsächlichem Zustand heißt Drift, und er ist in einer gewachsenen Umgebung kein Ausnahmefall.

Dieser Artikel handelt davon, wie man Drift sichtbar macht, welche drei Antworten es darauf gibt und wo Codifizierung bewusst aufhört.

Drift ist normal, nicht ein Versagen des Teams

Die erste Reaktion auf gefundenen Drift ist meistens eine Schuldfrage: Wer hat das von Hand geändert? Das ist die falsche Frage, weil sie das Verhalten erklärt, ohne es zu ändern.

Drift entsteht aus vier Quellen, und nur eine davon ist Nachlässigkeit.

Der Notfall. Um drei Uhr nachts wird eine Sicherheitsgruppe geöffnet, eine Instanz vergrößert, ein Limit angehoben. Das ist richtig so, und es wäre falsch, in diesem Moment einen Zweig aufzumachen und auf eine Prüfung zu warten. Was fehlt, ist der Schritt danach.

Der Anbieter selbst. Ein verwalteter Dienst setzt Werte, die er für richtig hält: ein Standard-Tag, eine angepasste Konfiguration nach einer Versionserhöhung, eine automatisch erzeugte Regel. Das ist kein Mensch und trotzdem Drift.

Ein anderes Werkzeug. Kubernetes legt einen Lastverteiler an, ein Autoskalierer ändert eine Instanzanzahl, eine Anwendung erzeugt eine Warteschlange. Alles legitim, alles nicht im Code.

Die Abkürzung. Jemand klickt etwas, weil es schneller geht. Das ist die Quelle, die alle vor Augen haben, und in meiner Erfahrung die kleinste der vier.

Aus dieser Aufzählung folgt die Haltung, die trägt: Drift wird gemessen und beantwortet, nicht verhindert. Eine Umgebung ohne jeden Drift gibt es nur dort, wo niemand mehr Zugang hat.

Drift sichtbar machen

Der technische Teil ist erfreulich klein. terraform plan vergleicht ohnehin den beschriebenen mit dem tatsächlichen Zustand; es fehlt nur, dass jemand regelmäßig hinsieht.

# -detailed-exitcode: 0 = gleich, 1 = Fehler, 2 = Drift
terraform plan -detailed-exitcode -lock=false -out=/dev/null
case $? in
  0) echo "kein Drift" ;;
  2) echo "DRIFT" ;;
  *) echo "Fehler beim Planen"; exit 1 ;;
esac

Wichtig ist -lock=false: Ein Lauf, der nur nachsieht, soll niemanden blockieren, der gerade wirklich etwas ändert.

Der Lauf gehört in einen Zeitplan, einmal täglich, mit einer Meldung in den Chat. Zwei Dinge entscheiden darüber, ob daraus etwas Nützliches wird.

Die Meldung nennt die Ressourcen, nicht nur die Zahl. „Drift in 14 Ressourcen" liest niemand zweimal. „Sicherheitsgruppe web-sg: eine Regel mehr" wird gelesen.

Die Meldung geht an jemanden mit Namen. Eine Nachricht in einem Kanal ohne Adressat wird nach zwei Wochen weggeklickt. Das ist kein technisches, sondern ein organisatorisches Detail, und es entscheidet über den Nutzen der ganzen Einrichtung.

Die drei Antworten auf Drift

Jeder Fund bekommt eine von drei Antworten, und alle drei sind richtig. Falsch ist nur, keine zu geben.

Übernehmen. Die Änderung war gut, also kommt sie in den Code. Das ist der Normalfall nach einem Notfall: Die Sicherheitsgruppe bleibt offen, aber sie steht jetzt im Repository, mit einem Kommentar, warum.

Zurückdrehen. Die Änderung war nicht gewollt, terraform apply stellt den beschriebenen Zustand wieder her. Das ist der zweithäufigste Fall und der, bei dem der Plan vorher gelesen werden muss, weil ein Zurückdrehen auch Daten kosten kann.

Ignorieren, ausdrücklich. Manche Felder gehören nicht in den Code, weil sie von außen gesetzt werden. Dafür gibt es eine Erklärung im Code statt einer stillschweigenden Hinnahme:

resource "aws_ecs_service" "api" {
  name            = "api"
  cluster         = aws_ecs_cluster.haupt.id
  task_definition = aws_ecs_task_definition.api.arn
  desired_count   = 2

  lifecycle {
    # Der Autoskalierer setzt desired_count im Betrieb. Stuende der Wert
    # nicht hier, wuerde jedes apply die Anzahl auf 2 zuruecksetzen, auch
    # mitten in einer Lastspitze. Die 2 oben ist der Startwert, nicht der
    # Sollwert.
    ignore_changes = [desired_count]
  }
}

Der Kommentar im Beispiel ist der eigentliche Inhalt. Ein ignore_changes ohne Begründung ist in zwei Jahren nicht von einer Nachlässigkeit zu unterscheiden, und dann traut sich niemand, es zu entfernen.

Wie viel überhaupt in Code gehört, hängt davon ab, was aus der Migration entstanden ist: Lift-and-Shift oder Replatforming.

Was bewusst nicht in den Code kommt

Vollständigkeit ist kein Ziel. Drei Arten von Ressourcen lasse ich regelmäßig draußen, und in allen drei Fällen ist das eine Entscheidung, die dokumentiert wird.

Was ein anderes Werkzeug besitzt. Ein Lastverteiler, den Kubernetes anlegt und verwaltet, gehört Kubernetes. Ihn zusätzlich zu codifizieren erzeugt zwei Eigentümer für eine Ressource, und der Streit darüber endet in einem Kreislauf aus Änderungen.

Was einmalig und gefährlich ist. Das Wurzelkonto, die Organisationsstruktur, ein Schlüssel für die Verschlüsselung. Diese Dinge werden selten geändert, und ein versehentliches Zerstören wiegt schwerer als der Gewinn an Reproduzierbarkeit. Wo sie doch in den Code kommen, gehört ein Schutz davor.

resource "aws_kms_key" "daten" {
  description             = "Verschluesselung der Anwendungsdaten"
  deletion_window_in_days = 30

  lifecycle {
    # Ein versehentliches Zerstoeren macht alle damit verschluesselten
    # Daten unlesbar. Das wiegt schwerer als jede Bequemlichkeit.
    prevent_destroy = true
  }
}

Was ohnehin verschwindet. Eine Umgebung, die in drei Monaten abgeschaltet wird, muss nicht beschrieben werden. Das ist dieselbe Logik wie beim Einfrieren eines Altsystems.

Bestehendes übernehmen, und wo das aufhört

Für Umgebungen, die vor dem Code da waren, ist der Import der erste Schritt. Seit es Importblöcke gibt, ist er nachvollziehbar und im Plan sichtbar, statt ein Befehl zu sein, den jemand einmal getippt hat.

import {
  to = aws_security_group.web
  id = "sg-0a1b2c3d4e5f"
}

resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.haupt.id
  # ... Regeln, abgeschrieben vom Bestand
}

Der Ablauf, der sich bewährt hat, ist immer derselbe: importieren, plan laufen lassen, den Code so lange angleichen, bis der Plan leer ist. Ein leerer Plan ist die Abnahme. Solange dort noch Änderungen stehen, beschreibt der Code etwas anderes als die Wirklichkeit, und das ist schlechter als gar kein Code, weil es Sicherheit vortäuscht.

Wo man aufhört: Ich importiere von außen nach innen. Netz, Sicherheitsgruppen, Rechte, Datenbanken, Rechenkapazität. Was danach noch übrig ist, sind meist Einzelstücke aus Experimenten, und die bekommen keine Beschreibung, sondern eine Entscheidung: gebraucht oder weg.

Der Zustand ist die einzige Sache, die wirklich kaputtgehen kann

Code lässt sich zurücksetzen, Ressourcen lassen sich neu anlegen. Die Zustandsdatei ist das einzige Stück, dessen Verlust echten Schaden anrichtet: Danach weiß Terraform nicht mehr, welche Ressource zu welchem Block gehört, und ein apply würde alles noch einmal anlegen.

Drei Einstellungen verhindern die schlimmsten Fälle, und alle drei kosten nichts.

terraform {
  backend "s3" {
    bucket = "firma-terraform-state"
    key    = "produktion/terraform.tfstate"
    region = "eu-central-1"

    # Sperre gegen gleichzeitige Laeufe. Ohne sie schreiben zwei Personen
    # gleichzeitig, und der Zustand danach beschreibt keine Wirklichkeit.
    use_lockfile = true

    encrypt = true
  }
}

Dazu Versionierung auf dem Speicherort, damit eine ältere Fassung des Zustands wiederherstellbar ist. Das ist die Sicherung, die man einmal im Jahr braucht und dann dringend.

Und die Regel, die daraus folgt: Niemand bearbeitet die Zustandsdatei von Hand. Wo etwas nicht stimmt, gibt es terraform state mv und terraform state rm, beide protokolliert und beide umkehrbar. Ein Texteditor auf einer Zustandsdatei ist der Weg zu einem Nachmittag, den niemand vergisst.

Der häufigste Ort für Handarbeit an der Konsole sind Berechtigungen, und dort lohnt das Aufräumen zuerst: IAM aufräumen in einem gewachsenen AWS-Konto.

Die Regel für den Notfall

Zum Schluss der Teil, an dem sich entscheidet, ob die ganze Einrichtung gelebt wird oder nur existiert.

Um drei Uhr nachts wird nicht über einen Zweig geändert. Wer das verlangt, bekommt entweder einen längeren Ausfall oder heimliche Änderungen, und beides ist schlechter als eine offene Regel.

Die Regel, die ich empfehle, hat drei Teile. Im Notfall darf jeder mit Zugang direkt ändern, ohne Rückfrage. Die Änderung wird in derselben Schicht gemeldet, eine Zeile im Vorfallskanal, keine Dokumentation. Am nächsten Werktag entscheidet jemand, ob sie übernommen oder zurückgedreht wird, und der tägliche Lauf von oben stellt sicher, dass sie nicht vergessen wird.

Damit ist Drift kein Verstoß mehr, sondern ein Vorgang mit einem Weg. Das ist der Unterschied zwischen einer Umgebung, in der Infrastruktur als Code wirklich gilt, und einer, in der das Repository ein Wunschbild beschreibt.

Wie ich Terraform in einer bestehenden Umgebung einführe, steht auf einer eigenen Seite; der Import von oben ist dort der erste Arbeitsblock.

Dieser Artikel gehört zu einer Reihe über Systeme, die es schon gibt. Der Rückblick ordnet alle Artikel der Reihe nach Anlass.