Als ich anfing, Terraform für die Bereitstellung von Azure-Infrastruktur einzusetzen, stieß ich schnell auf ein klassisches Henne-Ei-Problem: Der Zustand (State) soll im Azure Storage abgelegt werden, doch dafür muss der Storage erst bereitgestellt werden. Gleichzeitig soll der Storage selbst mit Terraform verwaltet werden, wie soll das funktionieren?
In diesem Beitrag erkläre ich, warum es wichtig ist, den Terraform-State in ein Remote-Backend auszulagern, wie das Henne-Ei-Problem entsteht und welche Lösungswege sich anbieten. Anschließend zeige ich Schritt für Schritt, wie ich das Problem gelöst habe. Zunächst lege ich den Storage mit einem lokalen Backend an und migriere anschließend den State ins Remote-Backend.
Warum überhaupt ein Remote‑Backend?
Terraform speichert den Zustand der Infrastruktur in einer Datei namens terraform.tfstate. Dieses State-File enthält Informationen über alle bereitgestellten Ressourcen und wird zur Berechnung künftiger Änderungen verwendet. Terraform nutzt den State, um aus den Konfigurationsdateien zu ermitteln, welche Azure-Ressourcen hinzugefügt, aktualisiert oder gelöscht werden müssen. Wird der State lokal gespeichert, hat das mehrere Nachteile:
- Teams können nicht gemeinsam arbeiten
- das State-File enthält oft sensible Informationen
- eine lokale Datei kann versehentlich gelöscht werden
Wenn man den State dagegen in Azure Blob Storage ablegt, nutzt man Azure-Features wie RBAC, Versionierung, automatisches Locking und Verschlüsselung.
Azure Storage sichert den State automatisch gegen parallele Zugriffe ab, indem ein Blob vor jedem Schreibvorgang gesperrt wird, um parallele Änderungen zu verhindern. Daten im Blob sind verschlüsselt, bevor sie persistiert werden. Dadurch wird die Zusammenarbeit erleichtert und die Sicherheit erhöht.
Das Henne‑Ei‑Problem
Um ein Remote-Backend einzurichten, muss Terraform bereits beim init wissen, wo der State abgelegt wird. Bei Azure sind dies der Name des Storage-Accounts, der Container, die Datei (Key) sowie ein Zugriffsschlüssel.
Genau diese Ressourcen existieren jedoch noch nicht, wenn man sie selbst mit Terraform erzeugen möchte.
Man möchte den State in einem Azure Blob Storage speichern, aber dazu muss der Storage bereits existieren. Dieses Paradoxon ist das sogenannte Henne-Ei-Problem.
Ohne Backend gibt es keinen State und ohne State lässt sich das Backend nicht deklarativ verwalten.
Lösungsstrategien im Überblick
Im Laufe der Zeit haben sich mehrere Ansätze etabliert, um das Henne-Ei-Problem zu lösen. Ihnen allen ist gemeinsam, dass die Ressourcen für den State zunächst bereitgestellt werden und das Remote-Backend erst danach aktiviert wird.
1. Bereitstellung per Portal
Die einfachste, aber wenig automatisierte Lösung ist es, den Storage-Account manuell über das Azure-Portal anzulegen. Anschließend fügt man im Terraform-Code einen Backend-Block ein und initialisiert den Code mit terraform init.
Der Vorteil ist, dass es schnell umzusetzen ist. Nachteil: Der Storage liegt außerhalb der Terraform-Konfiguration und muss manuell gepflegt werden.
2. Bereitstellung per Azure CLI
Eine Zwischenlösung zwischen Portal- und Terraform-Bereitstellung ist die Bereitstellung über die Azure CLI. Der Storage-Account und der Container werden per Skript oder mit wiederverwendbaren CLI-Befehlen erstellt. Dadurch ist zumindest eine nachvollziehbare und versionierbare Initialkonfiguration möglich, auch wenn diese noch nicht vollständig in Terraform abgebildet ist.
Der Vorteil dieser Lösung besteht darin, dass reproduzierbare CLI-Skripte genutzt werden können, ohne dass Portal-Klickerei erforderlich ist. Der Nachteil ist jedoch, dass diese Lösung weiterhin außerhalb von Terraform liegt und langfristig weniger robust und deklarativ ist.
3. Terraform Bootstrapping (lokal → remote)
Ein weiterer Ansatz besteht darin, den Storage mit Terraform anzulegen, während das Backend noch lokal ist. Dazu werden der Storage-Account, der Container und gegebenenfalls Rollen im Code definiert, Terraform mit lokalem Backend initialisiert und terraform apply ausgeführt. Nachdem die Ressourcen existieren, fügt man den Backend-Block hinzu und migriert den State mit terraform init.
Der Vorteil ist, dass das Backend vollständig per Terraform erzeugt wird und dadurch vollständig in Terraform verwaltet werden kann. Während der Nachteil darin besteht, dass der zweistufige Ablauf mit nachträglicher State-Migration fehleranfällig sein kann.
Meine Praxislösung: Storage per Terraform anlegen und State migrieren
Mein Ziel war es, das Problem so zu lösen, dass der Storage vollständig im Terraform-Code liegt, ohne dass aufwändige Scripte erforderlich sind. Deshalb habe ich mich für Variante 3 entschieden und diese noch automatisiert. Im Folgenden zeige ich Schritt für Schritt, wie ich einen Azure-Storage-Account mit Terraform anlege und den State ins Remote-Backend migriere.
1. Terraform Konfiguration und Backend (lokal) erstellen
Zuerst schreibe ich eine Terraform‑Konfiguration, die eine Ressourcengruppe, einen Storage‑Account und einen Container erstellt. Der Backend‑Block bleibt zunächst leer oder ist nicht vorhanden, sodass Terraform den lokalen State nutzt.
1# file: main.tf
2terraform {
3 required_providers {
4 azurerm = {
5 source = "hashicorp/azurerm"
6 version = "4.x.x"
7 }
8 }
9}
10
11provider "azurerm" {
12 features {}
13}
14
15resource "azurerm_resource_group" "this" {
16 name = "<RESOURCE_GROUP_NAME>"
17 location = "North Europe"
18}
19
20resource "azurerm_storage_account" "this" {
21 name = "<STORAGE_ACCOUNT_NAME>"
22 resource_group_name = azurerm_resource_group.this.name
23 location = azurerm_resource_group.this.location
24 account_tier = "Standard"
25 account_replication_type = "ZRS"
26}
27
28resource "azurerm_storage_container" "this" {
29 name = "<CONTAINER_NAME>"
30 storage_account_id = azurerm_storage_account.this.id
31}
32
33data "local_file" "backend_tf" {
34 filename = "${path.module}/backend.tf"
35}
36
37resource "local_file" "backend_tf_rendered" {
38 filename = "${path.module}/backend.tf"
39 content = replace(replace(templatefile("${path.module}/backend.tf", {
40 backend_resource_group_name = azurerm_resource_group.this.name
41 backend_storage_account_name = azurerm_storage_account.this.name
42 backend_container_name = azurerm_storage_container.this.name
43 }), "/*", ""), "*/", "")
44}Dieser Code legt einen Storage‑Account an, den wir als nächstes als Backend verwenden. Außerdem wird die backend.tf Datei geladen und überschrieben,m it den Daten aus dem gerade erstellten Remote-Backend. Wichtig ist, dass zu Beginn das Backend auskommentiert ist. Nur so wird ein lokales Backend verwendet. Das Template Backend sieht wie folgt aus.
# file: backend.tf
/*terraform {
backend "azurerm" {
resource_group_name = "${backend_resource_group_name}"
storage_account_name = "${backend_storage_account_name}"
container_name = "${backend_container_name}"
key = "terraform.tfstate"
}
}*/terraform init # Initialisiert das lokale Backend
terraform apply # Erstellt Ressourcengruppe, Storage-Account und ContainerNach dem apply existiert der Storage Account, aber der State liegt noch lokal auf deiner Maschine. Zusätzlich ist die backend.tf-Datei korrekt angepasst worden und der Kommentar wurde entfernt.
2. Backend-Block prüfen und State migrieren
Schauen wir uns nun den Backend-Block an, werden wir feststellen, dass alle Werte korrekt gesetzt worden und die Datei verwendet werden kann. Dafür einfach erneut terraform init ausführen und terraform erkennt das angegebene Backend.
1# file: backend.tf
2terraform {
3 backend "azurerm" {
4 resource_group_name = <RESOURCE_GROUP_NAME>
5 storage_account_name = <STORAGE_ACCOUNT_NAME>
6 container_name = <CONTAINER_NAME>
7 key = "terraform.tfstate"
8 }
9}
10Wichtig: Microsoft empfiehlt für die Authentifizierung, den Access Key über eine Umgebungsvariable zu setzen, damit der Schlüssel nicht im Code oder auf der Festplatte landet.
terraform init # Initialisiert das remote Backend und fragt, ob der State kopiert werden sollTerraform erkennt den neuen Backend-Block, fragt, ob der bisherige lokale State ins Remote-Backend verschoben werden soll und migriert ihn anschließend. Danach ist der terraform.tfstate nicht mehr lokal vorhanden, sondern liegt im Blob-Container. Alle weiteren plan/apply-Befehle arbeiten automatisch mit dem Remote-State.
Fazit
Das Henne-Ei-Problem beim Terraform-Backend tritt auf, weil das State-Backend existieren muss, damit Terraform es nutzen kann. Mit einem durchdachten Bootstrapping lässt sich dieses Dilemma jedoch elegant lösen. In meiner Praxis hat es sich bewährt, den Storage zunächst mit einem lokalen Backend anzulegen und anschließend den State ins Remote-Backend zu migrieren. So werden manuelle Schritte vermieden und dennoch eine saubere Infrastructure-as-Code-Verwaltung des Backends ermöglicht.

.png)