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:

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.

8. Minimale Rechte und keine Geheimnisse im Code

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.

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