Prinzipien von Infrastructure as Code
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
1. Deklarativ statt imperativ
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.
2. Idempotenz
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.
3. Soll-Zustand, Ist-Zustand und Drift
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).
4. Mutable vs. Immutable Infrastructure
| 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.
5. Eine einzige Quelle der Wahrheit
Der Code im Versionskontrollsystem ist die maßgebliche Beschreibung der Infrastruktur. Daraus folgt:
- Jede Änderung beginnt als Commit, nicht auf dem System.
- Was nicht im Code steht, existiert offiziell nicht – und wird beim nächsten Abgleich gemeldet oder entfernt.
- Dokumentation beschreibt das Warum, der Code das Was. Eine Dokumentation, die Konfigurationswerte wiederholt, veraltet.
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 |
6. GitOps: Push und Pull
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.
7. Kleiner Explosionsradius
Der Blast Radius ist der Schaden, den ein einzelner Fehler anrichten kann. IaC macht Änderungen schnell – auch fehlerhafte.
- State aufteilen: Langlebige Grundlagen (Netz, Gallery) und austauschbare Workloads in getrennten Schichten mit eigenem State. Ein Fehler im Workload-Code kann das Netz nicht löschen. Im Lab:
baseundworkload. - Kleine Änderungen: Ein Merge Request, eine Absicht. Ein Plan mit drei Änderungen lässt sich prüfen, einer mit dreihundert nicht.
- Schutz kritischer Ressourcen:
prevent_destroyfür Datenbanken, Change-Klassifizierung für Netz und Firewall (siehe ITIL). - Getrennte Umgebungen: Eigene Subscription oder zumindest eigener State für dev, test und prod.
- Getrennte Identitäten: Lesen und Schreiben mit unterschiedlichen Rechten (siehe Pipeline).
8. Minimale Rechte und keine Geheimnisse im Code
- Menschen brauchen in der Zielumgebung Leserechte, keine Schreibrechte. Geschrieben wird nur von der Pipeline.
- Die Pipeline authentisiert sich über kurzlebige Tokens (OIDC), nicht über gespeicherte Passwörter oder Client Secrets.
- Passwörter, Schlüssel und Zertifikate gehören in einen Key Vault und werden zur Laufzeit abgerufen.
- Der Terraform-State kann sensible Werte enthalten und wird entsprechend geschützt: kein Git, Zugriff nur per Entra ID, Versionierung aktiv.
9. Testen und Prüfen
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.
- sicherheit.tftest.hcl
# 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.
10. Einfach vor clever
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.
- Lieber etwas Wiederholung als eine Abstraktion, die niemand mehr versteht.
- Module erst dann bauen, wenn ein Muster dreimal vorkommt.
- Sprechende Namen für Ressourcen und Variablen, auch wenn sie länger sind.
- Kommentare für das Warum (z. B. „Bewusst keine SSH-Regel“), nicht für das Offensichtliche.
Anti-Muster
| 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 |
Weiterführend
- IaC-Übersicht – Pets vs. Cattle und Reifegradmodell
- IaC-Lab – alle Prinzipien in einem durchgängigen Beispiel
- Terraform – das deklarative Werkzeug im Detail
- Ansible – Idempotenz in der Konfiguration
- DevOps – die Kultur hinter der Automatisierung
