| FOTO | AUTO | EDV | AUDIO |

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: base und workload.
  • 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_destroy fü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