| FOTO | AUTO | EDV | AUDIO |

09 — Infrastructure as Code

Teil D — Wie man betreibt. Zurück zur Übersicht.

Lernziel

Nach diesem Kapitel kannst du deklarative von imperativer Automatisierung unterscheiden, Provisionierung von Konfigurationsverwaltung abgrenzen und erklären, was Idempotenz und Konfigurationsdrift bedeuten. Du hast den Server für den KFZ-Rechner vollständig aus Code erzeugt.

9.1 Das Problem: Schneeflocken und Drift

In Kapitel 6 kam der Begriff schon vor: Eine Schneeflocke ist ein über Jahre von Hand gepflegtes System, das niemand mehr identisch nachbauen kann. Jede ist einzigartig, keine ist reproduzierbar.

Wie sie entsteht, kennt jeder:

2021-03  Server aufgesetzt, nach Anleitung im Wiki
2021-07  Paket nachinstalliert, weil etwas fehlte   (nicht dokumentiert)
2022-01  Kernel-Parameter angepasst, wegen Performance
2022-09  Zertifikat von Hand ersetzt                (Kollege war im Urlaub)
2023-04  Firewall-Regel ergänzt, "nur mal kurz zum Testen"
2024-11  Niemand traut sich mehr, das System neu zu starten

Die schleichende Abweichung zwischen dokumentiertem Sollzustand und tatsächlichem Ist-Zustand heißt Konfigurationsdrift. Sie ist die Ursache hinter dem Satz „auf der Abnahme läuft es aber„ — und sie wächst monoton, solange niemand eingreift.

Infrastructure as Code dreht das Verhältnis um: Nicht das System ist die Wahrheit, sondern das Repository. Weicht das System ab, wird es korrigiert — nicht die Dokumentation.

9.2 Deklarativ statt imperativ

Imperativ Deklarativ
Beschreibt den Weg das Ziel
Beispiel „installiere Paket X“ „Paket X soll vorhanden sein„
Zweiter Durchlauf kann Schaden anrichten ändert nichts
Typisch Shell-Skript Ansible, Terraform, Kubernetes
# Imperativ - der zweite Durchlauf hängt die Zeile erneut an
useradd conops
echo "conops ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/conops
# Deklarativ - beliebig oft ausführbar
- name: Dienstbenutzer sicherstellen
  ansible.builtin.user:
    name: conops
    shell: /bin/bash
    state: present

- name: sudo-Regel sicherstellen
  ansible.builtin.copy:
    dest: /etc/sudoers.d/conops
    content: "conops ALL=(ALL) NOPASSWD:ALL\n"
    mode: "0440"
    validate: /usr/sbin/visudo -cf %s

Die Eigenschaft, dass ein wiederholter Durchlauf nichts verändert, heißt Idempotenz. Sie ist der Kern der Sache: Nur so kann man den Automatismus regelmäßig laufen lassen und Drift damit aktiv beseitigen, statt ihn nur zu dokumentieren.

Der Prüfstein für jede IaC-Umsetzung: Lasse den Automatismus zweimal hintereinander laufen. Meldet der zweite Lauf Änderungen, ist die Beschreibung fehlerhaft — meist wegen eines Shell-Aufrufs ohne Zustandsprüfung.

Ein Automatismus, der bei jedem Lauf „geändert“ meldet, ist wertlos als Kontrollinstrument: Man kann dann nicht mehr erkennen, ob eine echte Abweichung vorlag oder nur das übliche Rauschen.

9.3 Zwei Ebenen: Provisionierung und Konfiguration

Ebene Frage Werkzeuge
Provisionierung Welche Systeme gibt es überhaupt? Terraform, OpenTofu, Pulumi, Cloud-Vorlagen
Konfiguration Wie sind sie eingerichtet? Ansible, Puppet, Chef, Salt
Anwendungsebene Was läuft darauf? Container, Kubernetes-Objekte

In der Praxis kombiniert man: Terraform erzeugt die virtuellen Maschinen, Netze und Speicher; Ansible richtet die Betriebssysteme ein; Container bringen die Anwendungen. In einer reinen Rechenzentrumsumgebung ohne Cloud-Anteil fällt die erste Ebene oft weg oder wird von der Virtualisierungsplattform übernommen.

Für den Rest dieses Kapitels bleiben wir bei Ansible, weil es ohne Agenten, ohne Server und ohne Zustandsdatei auskommt — und weil es in RHEL-Umgebungen ohnehin der Standard ist. Die ausführliche Behandlung findest du in der Ansible-Unterlage dieses Wikis; hier geht es nur um die Einordnung in den DevOps-Zusammenhang.

9.4 Zustand und Drift-Erkennung

Werkzeuge wie Terraform führen eine Zustandsdatei, die den zuletzt hergestellten Stand festhält. Daraus ergibt sich ein mächtiges Werkzeug: der Abgleich von Soll und Ist.

terraform plan            # Was würde sich ändern? Ändert nichts.
ansible-playbook site.yml --check --diff

Ein sehr wirkungsvoller und selten genutzter Kniff: Lass die Beschreibung nächtlich im Prüfmodus über alle Systeme laufen und melde jede gefundene Abweichung.

Damit weißt du am nächsten Morgen, wo jemand von Hand eingegriffen hat — und zwar bevor es zum Problem wird. In regulierten Umgebungen ist das zugleich ein belastbarer Nachweis, dass die Konfiguration der Vorgabe entspricht. Der Aufwand liegt bei einem Nachmittag; der Nutzen bei jeder Prüfung.

Die Zustandsdatei bei Terraform ist gleichzeitig ein empfindlicher Punkt: Sie enthält oft Geheimnisse im Klartext und darf niemals ins Git-Repository. Sie gehört in einen Objektspeicher mit Verschlüsselung und Sperrmechanismus.

9.5 Veränderlich oder unveränderlich

Mutable Immutable
Vorgehen bei Änderungen System wird angepasst System wird ersetzt
Drift möglich ausgeschlossen
Rollback Rückkonfiguration altes Abbild starten
Passt zu klassischen Servern Containern, Cloud-Instanzen

Der unveränderliche Ansatz ist der konsequentere: Statt einen Server zu patchen, baut man ein neues Abbild und tauscht ihn aus. Genau das machen Container ohnehin, weshalb sie und IaC so gut zusammenpassen.

In gewachsenen Umgebungen mit Datenbankservern und Altanwendungen bleibt der veränderliche Ansatz aber die Realität — und dort ist regelmäßige, idempotente Konfigurationsverwaltung das nächstbeste Mittel.

9.6 Geheimnisse in IaC

Zugangsdaten gehören nicht ins Repository — auch nicht in eine Ansible-Variablendatei.

Verfahren Eignung
ansible-vault einfache Fälle, alles in einem Repository
SOPS mit age oder GPG verschlüsselte Werte, Git-freundliche Diffs
HashiCorp Vault / OpenBao Unternehmen, rotierende und kurzlebige Geheimnisse
Geheimnisverwaltung des Cloud-Anbieters wenn ohnehin dort betrieben
ansible-vault encrypt_string 'GeheimesPasswort' --name 'db_passwort'

Bewährt hat sich die Trennung in eine unverschlüsselte Datei mit sprechenden Variablennamen und eine verschlüsselte mit den Werten. So bleibt im Klartext sichtbar, welche Geheimnisse existieren, ohne ihren Inhalt preiszugeben.

9.7 IaC testen

Beschreibungen von Infrastruktur sind Code — also gelten die Regeln aus Kapitel 4.

Stufe Werkzeug Prüft
Syntax ansible-playbook –syntax-check, terraform validate formale Korrektheit
Stil ansible-lint, tflint gute Praxis, typische Fehler
Sicherheit checkov, tfsec, kics offene Ports, fehlende Verschlüsselung
Verhalten molecule Rolle läuft in einem Container und ist idempotent
Probelauf –check, terraform plan was würde sich ändern
# Molecule: Rolle in einem Container prüfen, inklusive Idempotenz
molecule test

Der Idempotenztest von Molecule ist dabei der wertvollste Teil: Er führt die Rolle zweimal aus und schlägt fehl, wenn der zweite Lauf Änderungen meldet.

9.8 Lab: Der Server für den KFZ-Rechner aus Code

roles/kfz_host/tasks/main.yml
---
- name: Distributionsspezifische Variablen laden
  ansible.builtin.include_vars: "{{ ansible_facts['os_family'] }}.yml"

- name: Erforderliche Pakete installieren
  ansible.builtin.package:
    name: "{{ kfz_pakete }}"
    state: present

- name: Dienstbenutzer anlegen
  ansible.builtin.user:
    name: "{{ kfz_benutzer }}"
    shell: /bin/bash
    create_home: true
    state: present

- name: Lingering aktivieren, damit Container ohne Anmeldung laufen
  ansible.builtin.command: "loginctl enable-linger {{ kfz_benutzer }}"
  args:
    creates: "/var/lib/systemd/linger/{{ kfz_benutzer }}"

- name: Verzeichnis für Quadlet-Units anlegen
  ansible.builtin.file:
    path: "/home/{{ kfz_benutzer }}/.config/containers/systemd"
    state: directory
    owner: "{{ kfz_benutzer }}"
    group: "{{ kfz_benutzer }}"
    mode: "0755"

- name: Unprivilegierte Ports ab 80 freigeben
  ansible.posix.sysctl:
    name: net.ipv4.ip_unprivileged_port_start
    value: "80"
    sysctl_file: /etc/sysctl.d/99-rootless.conf
    state: present
    reload: true

- name: Datenverzeichnis anlegen
  ansible.builtin.file:
    path: "{{ kfz_datenverzeichnis }}"
    state: directory
    owner: "{{ kfz_benutzer }}"
    group: "{{ kfz_benutzer }}"
    mode: "0750"

- name: SELinux-Kontext für Containerdaten setzen
  community.general.sefcontext:
    target: "{{ kfz_datenverzeichnis }}(/.*)?"
    setype: container_file_t
    state: present
  when: ansible_facts['os_family'] == 'RedHat'
  notify: SELinux-Kontext anwenden

- name: Firewall für HTTPS öffnen
  ansible.posix.firewalld:
    service: https
    permanent: true
    immediate: true
    state: enabled
  when: ansible_facts['os_family'] == 'RedHat'

- name: Quadlet-Unit ausbringen
  ansible.builtin.template:
    src: kfz-rechner.container.j2
    dest: "/home/{{ kfz_benutzer }}/.config/containers/systemd/kfz-rechner.container"
    owner: "{{ kfz_benutzer }}"
    mode: "0644"
  notify: Container neu starten
roles/kfz_host/handlers/main.yml
---
- name: SELinux-Kontext anwenden
  ansible.builtin.command: "restorecon -Rv {{ kfz_datenverzeichnis }}"

- name: Container neu starten
  ansible.builtin.systemd_service:
    name: kfz-rechner.service
    state: restarted
    daemon_reload: true
    scope: user
  become: true
  become_user: "{{ kfz_benutzer }}"

Aufgaben:

  1. Führe das Playbook zweimal aus. Der zweite Lauf muss changed=0 melden.
  2. Verändere von Hand etwas auf dem Server — etwa die Firewall-Regel. Lass das Playbook mit –check –diff laufen. Es muss die Abweichung finden.
  3. Lösche die virtuelle Maschine und baue sie aus dem Playbook neu auf. Stoppe die Zeit.
  4. Ergänze eine Prüfung, die sicherstellt, dass der Dienst nach dem Durchlauf tatsächlich antwortet.
  5. Baue das Playbook in die Pipeline aus Kapitel 7 ein — zunächst nur mit –check.

Die dritte Aufgabe ist die eigentliche Prüfung. Solange niemand den Server einmal absichtlich gelöscht und neu erzeugt hat, ist unbewiesen, dass die Beschreibung vollständig ist. Fast immer fehlt beim ersten Versuch etwas — ein Zertifikat, eine Datei, eine Berechtigung, die vor Jahren jemand von Hand gesetzt hat.

Diese Übung einmal jährlich durchzuführen, ersetzt einen erheblichen Teil einer Notfallübung. Und sie beantwortet nebenbei die Reflexionsfrage aus Kapitel 6 mit einer Zahl statt mit einer Hoffnung.

9.9 Anti-Pattern

Anti-Pattern Folge Abhilfe
Shell-Skripte statt deklarativer Beschreibung nicht wiederholbar idempotente Module verwenden
Nach dem Automatismus von Hand nachbessern Drift kehrt sofort zurück Änderung in den Code, dann erneut ausführen
IaC nicht versioniert keine Historie, kein Rollback Repository wie für Anwendungscode
Geheimnisse im Klartext Kompromittierung Vault, SOPS, ansible-vault
Einmal ausgeführt und nie wieder Drift wächst unbemerkt regelmäßiger Prüflauf
Terraform-Zustandsdatei im Git Geheimnisse öffentlich, Konflikte Objektspeicher mit Sperre
Kein Idempotenztest Beschreibung meldet ständig Änderungen Molecule, zweiter Durchlauf
Produktion als Übungsfeld Ausfall beim Lernen erst Test, dann Abnahme, dann Produktion

9.10 Reflexionsfrage

Welcher Server in deiner Umgebung würde dir die meisten Sorgen bereiten, wenn er heute Nacht ausfiele — und warum ausgerechnet dieser?

Die Antwort benennt fast immer eine Schneeflocke. Und damit auch das erste sinnvolle Ziel für Infrastructure as Code.


Weiter mit Kapitel 10 — Container und Orchestrierung