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.
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.
| 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.
| 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.
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.
| 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.
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.
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.
--- - 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
--- - 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:
changed=0 melden.–check –diff laufen. Es muss die Abweichung finden.–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.
| 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 |
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