Werkzeuge wechseln, Prinzipien bleiben. Wer die Ideen hinter Terraform, Ansible und Packer verstanden hat, kann jedes neue Werkzeug schnell einordnen. Diese Seite erklärt die Grundbegriffe, auf die alle anderen Seiten des Bereichs zurückgreifen.
Zurück zur IaC-Übersicht
Imperativ beschreibt den Weg: „Tu erst dies, dann das.“ Deklarativ beschreibt das Ziel: „So soll es aussehen.“ Das Werkzeug findet selbst heraus, welche Schritte nötig sind.
Beispiel: Ein Webserver soll existieren und nginx installiert haben.
# Imperativ – funktioniert genau einmal, beim zweiten Lauf gibt es Fehler az vm create -g rg-web -n web01 --image Ubuntu2404 ssh web01 'sudo apt-get install -y nginx'
# Deklarativ – beschreibt den Zielzustand, beliebig oft ausführbar
resource "azurerm_linux_virtual_machine" "web01" {
name = "web01"
# ...
}
| Aspekt | Imperativ | Deklarativ |
|---|---|---|
| Beschreibt | Schritte | Zielzustand |
| Wiederholbarkeit | Muss man selbst absichern | Eingebaut |
| Änderungen | Neues Skript für jede Änderung | Code anpassen, Werkzeug rechnet Differenz |
| Vorschau | Kaum möglich | terraform plan, ansible –check |
| Typische Werkzeuge | Bash, PowerShell, CLI-Aufrufe | Terraform, Ansible, Kubernetes-Manifeste |
Imperative Skripte haben weiterhin ihren Platz – etwa für einmalige Bootstrap-Schritte wie das Anlegen des State-Backends. Für alles, was dauerhaft verwaltet wird, ist deklarativ die bessere Wahl.
Eine Operation ist idempotent, wenn mehrfaches Ausführen dasselbe Ergebnis liefert wie einmaliges. Das ist die wichtigste Eigenschaft jeder Automatisierung: Man kann sie jederzeit gefahrlos erneut laufen lassen.
# Nicht idempotent – jeder Lauf hängt eine weitere Zeile an echo "PermitRootLogin no" >> /etc/ssh/sshd_config # Idempotent, aber mühsam und fehleranfällig grep -q '^PermitRootLogin no' /etc/ssh/sshd_config \ || echo "PermitRootLogin no" >> /etc/ssh/sshd_config
# Idempotent und lesbar – Ansible prüft selbst, ob etwas zu tun ist - name: Root-Login per SSH verbieten ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^#?PermitRootLogin' line: 'PermitRootLogin no'
Ein guter Test für jedes Playbook und jeden Terraform-Code: Der zweite Lauf direkt nach dem ersten darf nichts ändern. Ansible meldet dann changed=0, Terraform No changes. Mehr zu Ansible unter Ansible.
Deklarative Werkzeuge arbeiten mit einem Abgleich:
Soll-Zustand (Code) Ist-Zustand (Realität)
│ │
└──────► Vergleich ◄─────────┘
│
┌─────────┴─────────┐
gleich verschieden
│ │
nichts tun Differenz anwenden
Drift ist jede Abweichung zwischen Soll und Ist, die nicht über den Code entstanden ist – typischerweise durch Handarbeit im Portal oder per SSH. Drift ist gefährlich, weil der Code dann nicht mehr die Wahrheit beschreibt. Der nächste Apply überschreibt die Handänderung oder scheitert an ihr.
Drift wird erkannt (regelmäßiger terraform plan, siehe Pipeline) und verhindert (keine Schreibrechte für Menschen, kein SSH-Zugang, siehe Lab).
| Aspekt | Mutable (veränderlich) | Immutable (unveränderlich) |
|---|---|---|
| Änderung | Bestehender Server wird angepasst | Neuer Server aus neuem Image ersetzt den alten |
| Patches | apt upgrade auf laufenden Systemen | Neue Image-Version, Rollout |
| Zustand nach 2 Jahren | Einzigartig, schwer nachvollziehbar | Identisch mit dem Image |
| Rollback | Schwierig (was war vorher installiert?) | Alte Image-Version erneut ausrollen |
| Voraussetzung | Keine | Zustandslose Instanzen, Automatisierung |
| Typische Werkzeuge | Ansible gegen laufende Server, Satellite | Packer, Container-Images, Scale Sets |
In der Praxis gibt es Mischformen. Häufig ist das Betriebssystem-Image unveränderlich, während Configuration Management Details zur Laufzeit setzt. Das Lab geht den konsequenten Weg: Ansible läuft ausschließlich beim Image-Build.
Mutable Infrastructure ist nicht falsch. Für langlebige, zustandsbehaftete Systeme wie Datenbankserver oder Legacy-Anwendungen bleibt Configuration Management gegen laufende Systeme der richtige Ansatz. Entscheidend ist, dass auch dann nur der Code ändert und nie die Hand.
Der Code im Versionskontrollsystem ist die maßgebliche Beschreibung der Infrastruktur. Daraus folgt:
Alles als Code – nicht nur die Infrastruktur:
| Ebene | Beispiel | Werkzeug im Lab |
|---|---|---|
| Infrastruktur | Netze, VMs, Load Balancer | Terraform |
| Image | Betriebssystem mit Grundkonfiguration | Packer |
| Konfiguration | Pakete, Dienste, Dateien | Ansible |
| Bootstrap | Instanzspezifische Werte | cloud-init |
| Richtlinien | Pflicht-Tags, verbotene Einstellungen | Azure Policy, Checkov |
| Prozess | Prüfen, Freigeben, Ausrollen | .gitlab-ci.yml |
| Tests | Namenskonventionen, Sicherheitsvorgaben | terraform test |
GitOps bedeutet: Git ist die Steuerzentrale, Änderungen an der Infrastruktur passieren ausschließlich durch Änderungen im Repository. Es gibt zwei Varianten, den Zustand aus Git in die Realität zu bringen.
| Modell | Ablauf | Beispiele | Stärke |
|---|---|---|---|
| Push | Pipeline wird durch Commit ausgelöst und wendet Änderungen an | GitLab CI + Terraform (dieses Wiki) | Einfach, gut nachvollziehbar |
| Pull | Agent im Zielsystem holt regelmäßig den Soll-Zustand und gleicht ab | Flux, Argo CD (Kubernetes), ansible-pull | Korrigiert Drift selbstständig |
Das Push-Modell braucht eine zusätzliche Drift-Erkennung, das Pull-Modell hat sie eingebaut. Für VM-basierte Infrastruktur auf Azure ist Push mit nächtlichem Drift-Check der übliche Weg.
Der Blast Radius ist der Schaden, den ein einzelner Fehler anrichten kann. IaC macht Änderungen schnell – auch fehlerhafte.
base und workload.prevent_destroy für Datenbanken, Change-Klassifizierung für Netz und Firewall (siehe ITIL).Infrastruktur-Code wird geprüft wie Anwendungscode – und zwar möglichst früh (Shift Left). Je früher ein Fehler auffällt, desto billiger ist er.
| Stufe | Prüft | Werkzeug | Dauer |
|---|---|---|---|
| Formatierung | Einheitlicher Stil | terraform fmt | Sekunden |
| Syntax | Gültiger Code | terraform validate, packer validate | Sekunden |
| Lint | Fehleranfällige Muster | TFLint, ansible-lint | Sekunden |
| Sicherheit | Unsichere Einstellungen | Checkov, Trivy | Sekunden |
| Unit-Tests | Eigene Regeln und Konventionen | terraform test mit Mock-Provider | Sekunden |
| Plan | Tatsächliche Auswirkung auf die Umgebung | terraform plan | Minuten |
| Integration | Funktioniert das Ergebnis wirklich? | Testumgebung aufbauen, prüfen, abreißen | Minuten bis Stunden |
Beispiel für einen Unit-Test in der Schicht base: Er prüft Sicherheitsvorgaben, ohne Azure zu kontaktieren. Der Mock-Provider (ab Terraform 1.7) ersetzt die echte Azure-Anbindung.
# base/tests/sicherheit.tftest.hcl
mock_provider "azurerm" {}
run "subnetz_ohne_impliziten_internetzugang" {
command = plan
assert {
condition = azurerm_subnet.app.default_outbound_access_enabled == false
error_message = "Das App-Subnetz darf keinen impliziten Outbound-Zugang haben."
}
}
run "kein_ssh_in_der_nsg" {
command = plan
assert {
condition = alltrue([
for r in azurerm_network_security_group.app.security_rule :
r.destination_port_range != "22"
])
error_message = "Die NSG darf SSH (Port 22) nicht freigeben – Nutzvieh wird nicht gestreichelt."
}
}
run "namenskonvention" {
command = plan
variables {
prefix = "iaclab"
}
assert {
condition = azurerm_resource_group.base.name == "rg-iaclab-base"
error_message = "Ressourcengruppen folgen dem Schema rg-<prefix>-<schicht>."
}
}
cd base terraform init -backend=false terraform test
In der Pipeline gehört terraform test in die Stufe validate.
Terraform und Ansible erlauben Schleifen, Bedingungen und verschachtelte Module. Das verleitet dazu, Code möglichst allgemein zu schreiben. Aber Infrastruktur-Code wird öfter gelesen als geschrieben – im Review, bei der Fehlersuche, im Audit.
| Anti-Muster | Symptom | Besser |
|---|---|---|
| ClickOps | „Ich hab's schnell im Portal eingestellt“ | Jede Änderung per Merge Request |
| Snowflake-Server | Niemand traut sich, den Server neu zu starten | Aus Image und Code reproduzierbar |
| Apply vom Laptop | Unklar, wer wann was ausgerollt hat | Apply nur durch die Pipeline |
| State im Git-Repository | Konflikte, Secrets im Klartext in der Historie | Remote Backend mit Sperre und Zugriffskontrolle |
| Monolithischer State | Plan dauert ewig, jeder Fehler betrifft alles | Schichten mit eigenem State |
| Secrets im Code | Zugangsdaten in terraform.tfvars | Key Vault, OIDC |
ignore_changes als Pflaster | Drift wird versteckt statt behoben | Nur für bewusst extern gesteuerte Werte (z. B. Autoscale) |
| Nicht gepinnte Versionen | Gestern lief es noch, heute nicht | Provider-Versionen und Lock-Datei einchecken |
| Konfiguration zur Laufzeit ohne Not | Instanzen unterscheiden sich je nach Startzeit | Konfiguration ins Image |
| Der eine Admin, der es weiß | Urlaub = Stillstand | Code, Review, Dokumentation |