Ansible-Playbooks in der Praxis: Inventory, Idempotenz, Vault und rollende Bereitstellung
EdwardMoon
Ansible-Playbooks verwalten Pakete, Konfigurationsdateien und Dienstzustände mehrerer Linux-Server deklarativ. Das verringert ausgelassene manuelle Schritte und Unterschiede zwischen Umgebungen. Wer nach einem erfolgreichen ping sofort alle Server bearbeitet, command und shell ungeprüft einsetzt oder Passwörter in YAML ablegt, kann mit der Automatisierung jedoch auch Ausfälle und Datenoffenlegung schneller verbreiten.
Dieser Leitfaden ist weder an eine bestimmte Distribution noch an eine alte Ansible-Version gebunden. Er verbindet eine isolierte Python-Umgebung, YAML-Inventory, ansible.builtin-FQCN, Idempotenz, Handler, check und diff, Vault, serial sowie block und rescue zu einem überprüfbaren Betriebsablauf. Vor der Anwendung die offizielle Dokumentation der eingesetzten ansible-core- und Collection-Versionen prüfen.

Wichtige Bestandteile eines Ansible-Playbooks
| Bestandteil | Aufgabe | Vorgabe für den Betrieb |
|---|---|---|
| Inventory | Zielhosts, Gruppen und Verbindungsvariablen | dev, stage und prod trennen und doppelte Variablendefinitionen minimieren |
| Play | Zielgruppe und Ausführungsrichtlinien | hosts, become, serial und Fehlerstrategie ausdrücklich festlegen |
| Task | Ein einzelner Modulaufruf | Namen, FQCN und einen ausdrücklichen Zielzustand verwenden |
| Module | Konkrete Aktionen für Pakete, Dateien, Dienste und weitere Ressourcen | Wenn möglich spezialisierte Module statt command oder shell verwenden |
| Handler | Folgeaktion, die nur nach einer Änderung ausgeführt wird | Für geprüfte Dienstneustarts nach Konfigurationsänderungen verwenden |
| Role | Wiederverwendbares Bündel aus tasks, handlers, templates und defaults | Kleine Verantwortungsbereiche bilden und Schnittstellenvariablen dokumentieren |
| Collection | Verteilungseinheit für Module, Plugins und Rollen | Getestete Versionen festlegen und Änderungen prüfen |
Ausführungsumgebung vorbereiten
Auf dem Steuerknoten eine venv pro Projekt erstellen, statt Pakete direkt mit dem System-Python zu vermischen. ansible-core, Python, Konfigurationsdateien und Collection-Pfade dokumentieren, damit sich die Automatisierung reproduzieren lässt.
Ansible in einer Python-venv installieren
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install ansible-core ansible-lint
ansible --version
ansible-playbook --version
ansible-lint --version
python -m pip freeze > requirements-lock.txt
Für produktive Projekte getestete ansible-core- und Bibliotheksversionen über eine Lock-Datei oder ein freigegebenes Paket-Repository festlegen. Wer stets ungeprüft die neueste Version installiert, verliert bei geänderten Collection-Abhängigkeiten oder Python-Anforderungen die Reproduzierbarkeit.
Projektverzeichnis
install -d inventories/dev/group_vars/all inventories/prod/group_vars/all
install -d roles/web/{tasks,handlers,templates,defaults}
install -d playbooks/templates
find . -maxdepth 3 -type d | sort
Sichere Grundeinstellungen in ansible.cfg
[defaults]
inventory = inventories/dev/hosts.yml
roles_path = roles
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
forks = 10
timeout = 15
[privilege_escalation]
become = False
become_ask_pass = True
Beispiele mit deaktiviertem host_key_checking verhindern die Erkennung von Man-in-the-Middle-Angriffen. Den SSH-Hostschlüsselfingerabdruck über einen vertrauenswürdigen Weg prüfen und in known_hosts aufnehmen. become nur für die Plays oder Tasks aktivieren, die es benötigen; Passwörter nicht in Konfigurationsdateien speichern.
Ein YAML-Inventory erstellen
Gruppennamen sollten Rollen und Ausfallbereiche beschreiben; ansible_host enthält die tatsächliche Verbindungsadresse. Separate Inventory-Quellen für Entwicklung und Produktion verringern das Risiko einer versehentlichen prod-Auswahl stärker als eine reine Trennung über Tags in derselben Datei.
inventories/dev/hosts.yml
all:
children:
web:
hosts:
dev-web-01:
ansible_host: 192.0.2.11
dev-web-02:
ansible_host: 192.0.2.12
database:
hosts:
dev-db-01:
ansible_host: 192.0.2.21
vars:
ansible_user: automation
ansible_become: true
Inventory-Syntax und Zielhosts prüfen
ansible-inventory -i inventories/dev/hosts.yml --graph
ansible-inventory -i inventories/dev/hosts.yml --list | jq .
ansible web -i inventories/dev/hosts.yml -m ansible.builtin.ping --limit dev-web-01
ansible.builtin.ping führt keinen ICMP-Ping aus, sondern prüft die Ansible-Verbindung und die Ausführung von Python auf dem Ziel. Ein Erfolg bestätigt SSH, Python und die erforderlichen Rechte, aber nicht die Bereitschaft von Paketquellen, Speicherplatz oder Dienstabhängigkeiten.
Das erste Playbook mit Handlern
Das folgende Play beschreibt Paketinstallation, Konfigurationsvorlage und Dienstzustand. Jeder Task erhält einen verständlichen Namen und einen FQCN. Der Handler wird nur benachrichtigt, wenn template tatsächlich eine Änderung vornimmt. So entfallen unnötige Neustarts.
playbooks/web.yml
---
- name: Configure web servers
hosts: web
become: true
gather_facts: true
tasks:
- name: Ensure Nginx is installed
ansible.builtin.package:
name: nginx
state: present
- name: Render Nginx virtual host
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: '/usr/sbin/nginx -t -c %s'
notify: Restart Nginx
- name: Ensure Nginx is enabled and running
ansible.builtin.service:
name: nginx
enabled: true
state: started
handlers:
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restarted
Dieses Beispiel verwaltet die vollständige /etc/nginx/nginx.conf eines neuen Übungshosts. Damit nginx -t -c %s die temporäre Datei als eigenständige Hauptkonfiguration prüfen kann, enthält die Vorlage events und http. Die Hauptkonfiguration eines vorhandenen produktiven Nginx damit nicht ungeprüft überschreiben, da sonst andere virtuelle Hosts entfallen können. Eine produktive Rolle, die nur einzelne vhosts verteilt, benötigt eine gesonderte Prüfung der zusammengesetzten Gesamtkonfiguration.
Beispiel für eine Jinja-Vorlage
Den folgenden Inhalt unter playbooks/templates/nginx.conf.j2 speichern. Die Variablen wie gezeigt in inventories/dev/group_vars/all/app.yml ablegen. Für app_port den Port der tatsächlich laufenden Anwendung einsetzen.
app_server_name: app.example.internal
app_port: 8080
events {
worker_connections 1024;
}
http {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
listen 80;
server_name {{ app_server_name }};
location /healthz {
access_log off;
return 200 "ok\n";
}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Vorabprüfung mit syntax, check und diff
Nach der Syntaxprüfung den Check-Modus auf einem einzelnen Host des Entwicklungs-Inventory ausführen und den Diff prüfen. Der Check-Modus simuliert Änderungen, wird aber nicht von allen Modulen unterstützt. Plays, deren Bedingungen Ergebnisse vorheriger Tasks verwenden, können sich dabei anders darstellen als bei einer tatsächlichen Ausführung.
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --diff --limit dev-web-01
Ein Diff zeigt vorherige und neue Konfigurationswerte und kann Geheimnisse offenlegen. Für sensible Template-Tasks diff: false und no_log: true prüfen; außerdem Zugriff und Aufbewahrung von CI-Logs begrenzen. Nach erfolgreicher Prüfung mit beibehaltenem –limit tatsächlich ausführen und den Umfang anschließend gruppenweise erweitern.
ansible-lint playbooks/web.yml
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --limit dev-web-01
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --limit dev-web-01
Idempotente Playbooks schreiben
| Unzuverlässiger Ansatz | Empfohlener Ansatz | Begründung |
|---|---|---|
| shell: echo >> file | lineinfile·blockinfile·template | Verhindert Duplikate bei erneuter Ausführung |
| shell: yum install | package·dnf | Liest den Istzustand und führt nur erforderliche Änderungen aus |
| Bei jedem Lauf mit command initialisieren | creates, removes oder ein spezialisiertes Modul verwenden | Abschlussbedingung ausdrücklich festlegen |
| Dienst bei jedem Lauf neu starten | Handler notify | Nur bei geänderter Konfiguration neu starten |
| Alle Fehler ignorieren | failed_when·block/rescue | Erwartete Fehler von tatsächlichen Ausfällen unterscheiden |
Abschlussbedingungen für command festlegen
- name: Initialize application database once
ansible.builtin.command:
argv:
- /usr/local/bin/myapp-init
- --database
- /var/lib/myapp/app.db
creates: /var/lib/myapp/.initialized
register: init_result
# Das command-Modul meldet anhand von creates selbst skipped oder changed.
creates verhindert den command-Aufruf, wenn der angegebene Pfad bereits existiert. Sicherstellen, dass das Initialisierungsprogramm den Marker nach erfolgreichem Abschluss tatsächlich erzeugt. Ein pauschales changed_when: false verbirgt Änderungen und verhindert Handler-Auslösungen. Die Bedingung anhand der Bedeutung von Exitcode und Ausgabe formulieren.
Geheimnisse mit Ansible Vault schützen
Ansible Vault verschlüsselt Variablen und Dateien und reduziert damit Klartextgeheimnisse im Repository. Wie die offizielle Dokumentation betont, schützt Vault jedoch nur gespeicherte Daten. Während der Ausführung entschlüsselte Werte können in Modulargumenten, Diffs, debug-Ausgaben oder Fehlermeldungen erscheinen. Deshalb zusätzlich no_log, eingeschränkte Diffs und kontrollierten Logzugriff einsetzen.
Zeichenfolgen über die Terminaleingabe verschlüsseln
read -rsp 'Secret value: ' SECRET_VALUE
echo
printf '%s' "$SECRET_VALUE" | ansible-vault encrypt_string --vault-id prod@prompt --stdin-name 'db_password' > inventories/prod/group_vars/all/vault.yml
unset SECRET_VALUE
chmod 0600 inventories/prod/group_vars/all/vault.yml
Mit einer Vault-ID ausführen
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --vault-id prod@prompt --check --limit prod-web-01
# Mit !vault verschlüsselte YAML-Variablen werden beim Playbook-Aufruf mit --vault-id entschlüsselt.
Ausgabe sensibler Tasks unterdrücken
- name: Render application secret configuration
ansible.builtin.template:
src: app-secret.conf.j2
dest: /etc/myapp/secret.conf
owner: root
group: root
mode: '0600'
no_log: true
diff: false
no_log unterdrückt Ausgaben des betreffenden Tasks, bildet aber keine Sicherheitsgrenze gegen Schadcode, separate debug-Tasks oder Logs auf dem Zielsystem. Nach Möglichkeit einen externen Secret Manager und kurzlebige Zugangsdaten verwenden. Auch Vault-Passwortdateien gehören nicht in Git.
Rollende Bereitstellung mit Ansible
Nach der Bereitstellung zuerst mit meta: flush_handlers die benachrichtigten Neustarts ausführen und danach den Healthcheck durchführen. Für ein Upgrade entweder die freigegebene Version im Paketnamen angeben oder im Änderungsfenster myapp_package_state ausdrücklich auf latest setzen. present aktualisiert ein bereits installiertes Paket nicht automatisch.
Mit serial die Stapelgröße begrenzen, statt alle Produktionsserver gleichzeitig zu verändern. max_fail_percentage bricht ab, wenn der Fehleranteil im aktuellen Stapel den Wert überschreitet. Soll bereits ein Fehler unter zwei Hosts zum Abbruch führen, die strikte Überschreitung berücksichtigen und beispielsweise 49 statt 50 setzen.
---
- name: Roll out application safely
hosts: web
become: true
serial: 2
max_fail_percentage: 49
pre_tasks:
- name: Confirm target batch
ansible.builtin.debug:
msg: "Deploying to {{ ansible_play_batch }}"
tasks:
- name: Deploy application package
ansible.builtin.package:
name: myapp
state: "{{ myapp_package_state | default('present') }}"
notify: Restart MyApp
- name: Restart changed services before checking health
ansible.builtin.meta: flush_handlers
- name: Verify local health endpoint
ansible.builtin.uri:
url: http://127.0.0.1:8080/healthz
status_code: 200
return_content: false
register: health
retries: 10
delay: 3
until: health.status == 200
handlers:
- name: Restart MyApp
ansible.builtin.service:
name: myapp
state: restarted
Dieses Minimalbeispiel erklärt die Struktur. Hinter einem Loadbalancer jeden Host des Stapels zunächst aus dem Verkehr nehmen und bestehende Verbindungen auslaufen lassen. Danach bereitstellen, den Healthcheck ausführen und den Host wieder aufnehmen. Auch bei delegate_to und API-Modulen Zertifikatsprüfung, Fehlerbehandlung und sichere Wiederholbarkeit kontrollieren.
Fehlerbehandlung und Wiederherstellung
block wendet gemeinsame Angaben wie become und when auf zusammengehörige Tasks an; rescue und always beschreiben den Fehlerablauf. Bei erfolgreichem rescue gilt der ursprüngliche Fehler als behoben, sodass das Play fortgesetzt werden kann. Syntaxfehler und nicht erreichbare Hosts werden von rescue nicht behandelt. Für Verbindungsfehler sind daher eigene Richtlinien und Überwachung erforderlich.
- name: Back up current configuration
ansible.builtin.copy:
src: /etc/myapp/myapp.conf
dest: /var/backups/myapp.conf.pre-ansible
remote_src: true
owner: root
group: root
mode: '0600'
- name: Update service configuration with recovery
block:
- name: Render candidate configuration
ansible.builtin.template:
src: myapp.conf.j2
dest: /etc/myapp/myapp.conf
owner: root
group: root
mode: '0640'
validate: '/usr/local/bin/myapp --check-config %s'
notify: Restart MyApp
rescue:
- name: Restore previous configuration
ansible.builtin.copy:
src: /var/backups/myapp.conf.pre-ansible
dest: /etc/myapp/myapp.conf
remote_src: true
owner: root
group: root
mode: '0640'
- name: Stop this host after recovery
ansible.builtin.fail:
msg: Configuration deployment failed and was restored
always:
- name: Record completion state
ansible.builtin.debug:
msg: "Configuration block finished for {{ inventory_hostname }}"
Nicht davon ausgehen, dass eine einzelne Wiederherstellungsdatei zugleich Anwendung und Datenbankschema zurücksetzt. Eine eigene Betriebsanweisung erstellen, die Prüfungen vor und nach der Bereitstellung, mögliche Paket-Rollbacks, Datenänderungen und den Ausführungszeitpunkt der Handler berücksichtigt.
Qualitätsprüfungen und Ausführungsreihenfolge
- Versionen von ansible-core, Python und Collections sowie den vorgesehenen Git-Commit dokumentieren.
- Ziele, Variablen und Gruppen mit inventory –graph und –list prüfen.
- ansible-lint und –syntax-check erfolgreich durchlaufen lassen.
- –check –diff auf einem einzelnen Entwicklungshost ausführen.
- Sensible Diffs und den Geltungsbereich von no_log prüfen.
- Nach der tatsächlichen Ausführung auf einem Host den Healthcheck und changed=0 bei einem erneuten Lauf prüfen.
- Abbruch- und Wiederherstellungsverfahren in stage mit kleinen serial-Stapeln testen.
- prod im freigegebenen Änderungsfenster bereitstellen und recap, Anwendungsmetriken sowie Logs aufbewahren.
ansible-config dump --only-changed
ansible-inventory -i inventories/prod/hosts.yml --graph
ansible-lint playbooks roles
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --check --diff --limit prod-web-01 --vault-id prod@prompt
Checkliste für den Betrieb
- Inventory-Quellen und Schreibrechte für dev, stage und prod sind getrennt.
- SSH-Hostschlüsselprüfung bleibt aktiv; Automatisierungskonto und sudo-Rechte sind auf das Nötige begrenzt.
- Jeder Task hat einen Namen und FQCN; command und shell besitzen Abschluss- und Änderungsbedingungen.
- Konfigurationsänderungen werden mit validate und Handlern abgesichert; erneute Läufe ergeben changed=0.
- Vault, no_log, eingeschränkte Diffs und kontrollierter Logzugriff werden gemeinsam eingesetzt.
- Der Umfang wächst schrittweise über syntax, lint, check, diff, Einzelhost und kleine serial-Stapel.
- Für von block/rescue nicht behandelte Erreichbarkeits- und Syntaxfehler sowie Daten-Rollbacks bestehen Verfahren.
- Commit, Inventory, limit, ausführende Person, recap und nachträgliche Prüfungen werden im Änderungsprotokoll festgehalten.
Offizielle Dokumentation und weiterführende Artikel
- Offizieller Ansible-Inventory-Leitfaden
- Offizielle Dokumentation zu Check- und Diff-Modus
- Offizielle Ansible-Handler-Dokumentation
- Offizielle Dokumentation zu block, rescue und always
- Offizielle Ansible-Vault-Dokumentation
- Offizielle ansible-lint-Dokumentation
- Python venv erstellen und betreiben
Zusammenfassung
Sichere Ansible-Automatisierung bildet Zielzustände, Änderungsbedingungen, Fehlergrenzen und die Prüfreihenfolge in Code ab. Inventories nach Umgebung trennen, Idempotenz mit spezialisierten FQCN-Modulen und Handlern herstellen und den Schutzumfang von Vault beachten. Erst nach erfolgreichen Syntax-, Lint-, Check- und Diff-Prüfungen, Einzelhosttests, serial-Stapeln und Wiederherstellungstests den Einsatz in der Produktion ausweiten.