Ansible in der Praxis: Playbooks, dynamisches Inventory und Vault
Ansible-Playbooks, Rollen und Handler strukturieren, dynamisches Inventory fuer AD/VMware/Cloud bauen, Secrets mit Vault schuetzen und Windows-Hosts sauber automatisieren.
Warum Ansible im KMU-Alltag ankommt
Wer in einem Schweizer KMU 15 bis 200 Server betreut, kennt das Problem: Ein Patch-Skript hier, ein manuell gepflegtes Excel mit IP-Adressen dort, und die Config-Drift zwischen “Server-Alt” und “Server-Neu” merkt niemand, bis ein Audit oder ein Ausfall es schmerzhaft aufdeckt. Ansible loest das ohne Agenten auf den Zielsystemen: Linux-Hosts werden per SSH angesprochen, Windows-Hosts per WinRM, und die gesamte Logik liegt in menschenlesbaren YAML-Dateien, die sich versionieren lassen. Im Vergleich zu Terraform, das primaer fuer die Bereitstellung von Infrastruktur (VMs, Netzwerke, Cloud-Ressourcen) gedacht ist, spielt Ansible seine Staerke bei der Konfiguration innerhalb bestehender Systeme aus: Pakete installieren, Configs ausrollen, Dienste neu starten, Compliance durchsetzen. Viele Teams kombinieren beides: Terraform baut die Maschine, Ansible konfiguriert sie danach.
Dieser Artikel geht ueber die Ansible-Grundlagen hinaus und zeigt, wie du Playbooks strukturierst, die auch nach zwei Jahren und drei Teammitgliedern noch wartbar sind, wie dynamisches Inventory aus Active Directory, VMware oder der Cloud entsteht, wie Secrets mit Ansible Vault sauber verwaltet werden und wie du Windows-Hosts zuverlaessig per WinRM automatisierst.
Architektur: Control Node, Inventory, Module
Ansible braucht genau einen “Control Node” - einen Rechner oder eine CI/CD-Pipeline, auf der ansible respektive ansible-playbook installiert ist (Python 3.9+). Von dort aus verbindet sich Ansible ueber SSH (siehe SSH-Grundlagen, fuer Linux/Netzwerkgeraete) oder WinRM (Windows) zu den Zielsystemen und schiebt kleine Python-Module (bei Windows PowerShell-Module) rueber, die dort ausgefuehrt werden und anschliessend wieder verschwinden. Es gibt keinen dauerhaft laufenden Agenten auf den Zielsystemen - das ist der grosse Unterschied zu Puppet oder Chef und reduziert die Angriffsflaeche.
Drei Kernbestandteile bilden das Fundament:
- Inventory: Die Liste der Zielsysteme, statisch (INI/YAML-Datei) oder dynamisch (Plugin, das die Liste zur Laufzeit generiert).
- Playbook: Eine YAML-Datei mit einer geordneten Liste von “Plays”, die jeweils Hosts einer Rolle in Aufgaben (Tasks) zuordnen.
- Module: Die eigentlichen Bausteine (
ansible.builtin.apt,ansible.windows.win_service,community.general.timezoneusw.), die idempotent einen Zielzustand herstellen.
# inventory/hosts.ini - klassisches statisches Inventory
[webserver]
web01.kmu.local ansible_host=10.10.5.11
web02.kmu.local ansible_host=10.10.5.12
[datenbank]
db01.kmu.local ansible_host=10.10.5.20
[windows]
fileserver01.kmu.local ansible_host=10.10.5.30
[windows:vars]
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_port=5986
Playbook-Struktur: Roles, Handlers, Jinja2
Ein einzelnes, monolithisches Playbook mit 500 Zeilen ist der schnellste Weg zu einem unwartbaren Projekt. Der etablierte Standard ist die Rollenstruktur, die Ansible mit ansible-galaxy init selbst generiert:
ansible-galaxy init roles/webserver
Das erzeugt folgendes Skelett:
roles/webserver/
├── tasks/main.yml # Die eigentlichen Aufgaben
├── handlers/main.yml # Ereignisgesteuerte Aktionen (z.B. Dienst-Neustart)
├── templates/ # Jinja2-Vorlagen (.j2)
├── files/ # Statische Dateien zum Kopieren
├── vars/main.yml # Rollenspezifische Variablen (hohe Prioritaet)
├── defaults/main.yml # Standardwerte (niedrigste Prioritaet, ueberschreibbar)
└── meta/main.yml # Abhaengigkeiten zu anderen Rollen
Das uebergeordnete Playbook bindet die Rolle dann nur noch ein:
# site.yml
- name: Webserver konfigurieren
hosts: webserver
become: true
roles:
- webserver
vars_files:
- vault/secrets.yml
Tasks, Handlers und Jinja2-Templates im Zusammenspiel
Ein Handler wird nur ausgeloest, wenn ein Task ihn per notify anstoesst UND der Task tatsaechlich eine Aenderung vorgenommen hat (Status changed). Das ist der Kern von effizienter Automatisierung: Ein Nginx-Neustart passiert nur, wenn sich die Config wirklich geaendert hat - nicht bei jedem Playbook-Lauf.
# roles/webserver/tasks/main.yml
- name: Nginx installieren
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: vHost-Konfiguration ausrollen
ansible.builtin.template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/{{ vhost_name }}.conf
owner: root
group: root
mode: "0644"
notify: Nginx neu laden
- name: vHost aktivieren
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ vhost_name }}.conf
dest: /etc/nginx/sites-enabled/{{ vhost_name }}.conf
state: link
notify: Nginx neu laden
# roles/webserver/handlers/main.yml
- name: Nginx neu laden
ansible.builtin.service:
name: nginx
state: reloaded
Das Jinja2-Template zieht Variablen aus dem Inventory oder aus group_vars/host_vars:
{# roles/webserver/templates/vhost.conf.j2 #}
server {
listen 80;
server_name {{ vhost_name }};
root {{ webserver_docroot | default('/var/www/html') }};
{% if enable_ssl | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ vhost_name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ vhost_name }}.key;
{% endif %}
location / {
try_files $uri $uri/ =404;
}
}
Dynamisches Inventory: AD, VMware, Cloud
Statische INI-Dateien funktionieren fuer 10 Server. Sobald die Infrastruktur waechst oder sich haeufig aendert (VMs werden taeglich erstellt/geloescht, Autoscaling in der Cloud), wird die Pflege zum Vollzeitjob und die Liste veraltet garantiert. Dynamisches Inventory loest das, indem ein Plugin zur Laufzeit die aktuelle Systemlandschaft abfragt.
VMware vSphere als Quelle
# inventory/vmware.yml
plugin: community.vmware.vmware_vm_inventory
strict: false
hostname: vcenter01.kmu.local
username: "{{ vcenter_user }}"
password: "{{ vcenter_password }}"
validate_certs: true
with_tags: true
properties:
- name
- guest.ipAddress
- config.guestId
- summary.runtime.powerState
filters:
- summary.runtime.powerState == "poweredOn"
compose:
ansible_host: guest.ipAddress
keyed_groups:
- key: config.guestId
prefix: os
Voraussetzung: ansible-galaxy collection install community.vmware und pyvmomi auf dem Control Node. Wichtig fuer die Zukunft: Das Plugin vmware_vm_inventory gilt als aeltere Variante und wird schrittweise durch vmware.vmware.vms abgeloest - bei Neuprojekten lohnt sich ein Blick auf die aktuelle Collection-Dokumentation.
Active Directory / Windows-Umgebung
Fuer AD-zentrierte Umgebungen (siehe AD-Grundlagen) gibt es keinen offiziellen “AD-Inventory-Plugin” wie bei VMware. Der uebliche Weg im KMU-Umfeld: Ein kleines PowerShell- oder Python-Skript liest die Computer-Objekte einer Standort- oder Server-OU per LDAP-Filter aus und gibt sie im vom Ansible-Skript-Inventory erwarteten JSON-Format aus (ansible-inventory --list Format), oder man nutzt das generische constructed-Plugin in Kombination mit einer per Skript exportierten CSV/YAML-Liste. Alternativ bewaehrt sich in der Praxis oft ein einfacherer Ansatz: Ein CI-Job exportiert taeglich per Get-ADComputer -Filter * -SearchBase "OU=Server,DC=kmu,DC=local" eine YAML-Datei, die dann als statisches, aber automatisch aktualisiertes Inventory dient.
Cloud-Plugins (Azure, AWS)
# inventory/azure_rm.yml
plugin: azure.azcollection.azure_rm
include_vm_resource_groups:
- rg-webserver-prod
auth_source: auto
conditional_groups:
linux: "'Linux' in os_profile.system"
windows: "'Windows' in os_profile.system"
keyed_groups:
- key: tags.environment
prefix: env
# Dynamisches Inventory testen, ohne ein Playbook zu starten
ansible-inventory -i inventory/azure_rm.yml --graph
ansible-inventory -i inventory/vmware.yml --list --yaml
Ansible Vault: Secrets sicher verwalten
Passwoerter, API-Keys oder Zertifikats-Private-Keys duerfen niemals im Klartext ins Git-Repository (vergleiche Passwort-Management und Git fuer IT-Admins). Ansible Vault verschluesselt einzelne Dateien oder einzelne Variablenwerte mit AES256 symmetrisch.
# Neue verschluesselte Datei anlegen
ansible-vault create vault/secrets.yml
# Bestehende Klartext-Datei nachtraeglich verschluesseln
ansible-vault encrypt vars/db_passwords.yml
# Verschluesselte Datei zum Bearbeiten oeffnen
ansible-vault edit vault/secrets.yml
# Inhalt anzeigen, ohne dauerhaft zu entschluesseln
ansible-vault view vault/secrets.yml
# Nur einen einzelnen String verschluesseln (fuer inline-Nutzung im Playbook)
ansible-vault encrypt_string 'S3hrGeheim!23' --name 'db_admin_password'
Das Ergebnis von encrypt_string laesst sich direkt in eine YAML-Datei einfuegen:
# group_vars/datenbank/vault.yml
db_admin_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
386439343833633033653...gekuerzt...
Beim Playbook-Lauf muss das Vault-Passwort bereitgestellt werden - entweder interaktiv, per Datei oder per Skript, das das Passwort aus einem zentralen Secret-Store zieht:
# Interaktiv nach Passwort fragen
ansible-playbook site.yml --ask-vault-pass
# Passwort aus Datei lesen (Datei mit 0600-Rechten schuetzen!)
ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt
# Mehrere Vault-IDs fuer unterschiedliche Umgebungen (prod/test)
ansible-vault encrypt --vault-id prod@prompt vault/prod_secrets.yml
ansible-playbook site.yml --vault-id prod@~/.vault_pass_prod.txt
Fuer produktive Umgebungen mit mehreren Admins ist die Vault-ID-Funktion (--vault-id) wichtig: Sie erlaubt getrennte Passwoerter fuer Test- und Produktivumgebung, sodass ein Junior-Admin zwar Test-Secrets, aber keine Produktiv-Secrets entschluesseln kann.
Idempotenz und Check-Mode
Idempotenz bedeutet: Ein Playbook, das zehnmal hintereinander gegen denselben Host laeuft, liefert beim zweiten bis zehnten Lauf keine Aenderung mehr (Status ok, nicht changed), sofern sich der Zielzustand nicht veraendert hat. Das ist der Kernvorteil gegenueber reinen Bash-/PowerShell-Skripten, die bei jedem Lauf blind Befehle abfeuern.
# Simulation ohne echte Aenderungen (Dry-Run)
ansible-playbook site.yml --check
# Dry-Run kombiniert mit Diff-Anzeige der Aenderungen
ansible-playbook site.yml --check --diff
# Nur gegen eine Gruppe/einen Host testen
ansible-playbook site.yml --check --diff --limit webserver
# Nur bestimmte Tags ausfuehren (z.B. nur Config, kein Paket-Update)
ansible-playbook site.yml --tags "config" --skip-tags "pakete"
Ein haeufiger Fallstrick: Eigene shell- oder command-Tasks sind per Definition nicht idempotent, weil sie einfach einen Befehl ausfuehren. Die Loesung sind die Parameter creates oder removes, die Ansible mitteilen, wie es Idempotenz simulieren soll:
- name: Anwendung nur entpacken, wenn noch nicht vorhanden
ansible.builtin.command: tar xzf app-2.4.tar.gz -C /opt/app
args:
creates: /opt/app/bin/app.sh
Windows-Hosts via WinRM
Windows-Server werden nicht per SSH, sondern per WinRM (Windows Remote Management, Port 5985 HTTP / 5986 HTTPS) angesprochen. Auf dem Zielsystem muss WinRM konfiguriert und der passende Listener aktiv sein - Microsoft und die Ansible-Community stellen dafuer ein fertiges PowerShell-Skript bereit (ConfigureRemotingForAnsible.ps1), das per GPO oder manuell ausgefuehrt wird.
# Auf dem Windows-Zielsystem: WinRM fuer HTTPS mit selbstsigniertem Zertifikat vorbereiten
# (offizielles Ansible-Skript, per GPO-Startskript oder manuell ausfuehren)
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1" -OutFile "$env:TEMP\ConfigureRemotingForAnsible.ps1"
& "$env:TEMP\ConfigureRemotingForAnsible.ps1" -CertValidityDays 365
Auf dem Control Node muss die Python-Bibliothek pywinrm installiert sein sowie die Collection ansible.windows:
pip install pywinrm
ansible-galaxy collection install ansible.windows
Die Inventory-Variablen fuer Windows-Gruppen:
# group_vars/windows.yml
ansible_connection: winrm
ansible_winrm_transport: ntlm
ansible_port: 5986
ansible_winrm_server_cert_validation: ignore # nur mit selbstsigniertem Zertifikat, sonst 'validate'
ansible_user: "{{ vault_windows_admin_user }}"
ansible_password: "{{ vault_windows_admin_password }}"
Ein typisches Windows-Playbook nutzt die win_*-Module aus ansible.windows und community.windows:
- name: Windows-Fileserver konfigurieren
hosts: windows
tasks:
- name: Windows-Feature "File-Services" installieren
ansible.windows.win_feature:
name: FS-FileServer
state: present
- name: Freigabeordner erstellen
ansible.windows.win_file:
path: D:\Freigaben\Projekte
state: directory
- name: SMB-Freigabe einrichten
ansible.windows.win_share:
name: Projekte
path: D:\Freigaben\Projekte
full: Domaenen-Admins
read: Alle-Mitarbeiter
state: present
- name: Windows-Dienst sicherstellen
ansible.windows.win_service:
name: Spooler
state: started
start_mode: auto
Fehlerbehandlung: Tags, Handlers, Block/Rescue
Produktionsreife Playbooks brauchen kontrollierte Fehlerbehandlung. Ansible bietet dafuer Bloecke mit block/rescue/always, was strukturell an Try/Catch/Finally aus klassischen Programmiersprachen erinnert:
- name: Anwendungs-Update mit Rollback-Absicherung
block:
- name: Dienst stoppen
ansible.builtin.service:
name: meine-app
state: stopped
- name: Neue Version ausrollen
ansible.builtin.unarchive:
src: app-3.1.tar.gz
dest: /opt/app
remote_src: false
- name: Dienst starten
ansible.builtin.service:
name: meine-app
state: started
rescue:
- name: Bei Fehler alte Version wiederherstellen
ansible.builtin.command: /opt/app/rollback.sh
register: rollback_result
- name: Fehler fuer spaetere Analyse protokollieren
ansible.builtin.lineinfile:
path: /var/log/ansible-deploy-fehler.log
line: "{{ ansible_date_time.iso8601 }} - Rollback ausgeloest auf {{ inventory_hostname }}"
create: true
always:
- name: Health-Check unabhaengig vom Ergebnis ausfuehren
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:8080/health"
status_code: 200
register: health_check
ignore_errors: true
Tags helfen dabei, grosse Playbooks selektiv auszufuehren - besonders wertvoll, wenn ein Fix nur die Konfiguration betrifft und ein voller Paket-Update-Lauf zu lange dauern wuerde oder ein Wartungsfenster sprengt:
- name: Firewall-Regeln setzen
ansible.builtin.firewalld:
service: https
permanent: true
state: enabled
tags:
- security
- firewall
# Nur Firewall-Tasks im gesamten Playbook ausfuehren
ansible-playbook site.yml --tags firewall
# Alle Tasks ausser den mit "pakete" markierten
ansible-playbook site.yml --skip-tags pakete
Entscheidungshilfe: Wann welches Inventory?
| Szenario | Empfohlener Ansatz | Begruendung |
|---|---|---|
| Weniger als 20 statische Server, selten Aenderungen | Statisches INI/YAML-Inventory | Einfachste Wartung, kein zusaetzliches Plugin noetig |
| VMware-Cluster mit haeufig wechselnden VMs | community.vmware.vmware_vm_inventory oder vmware.vmware.vms | Immer aktuelle Liste direkt aus vCenter, Tag-basierte Filterung |
| Azure/AWS mit Autoscaling | azure_rm / aws_ec2 Plugin | VMs erscheinen/verschwinden automatisch im Inventory |
| AD-verwaltete Windows-Serverflotte ohne Cloud/Hypervisor-API | Taeglich per Skript exportierte YAML-Liste aus Get-ADComputer | Kein offizielles AD-Inventory-Plugin, aber stabil und nachvollziehbar |
| Multi-Cloud oder gemischte Umgebung | Mehrere Inventory-Quellen kombinieren (ansible.cfg mit Verzeichnis statt Einzeldatei) | Ansible fusioniert automatisch alle Quellen in einem Inventory-Verzeichnis |
Praxis-Ablauf: Neues Playbook produktionsreif machen
- Lokal mit
--check --diffgegen eine Testgruppe laufen lassen, nie direkt gegen Produktion. - Syntax-Check vorab:
ansible-playbook site.yml --syntax-check. - Linting mit
ansible-linteinbauen (deckt Stilfehler und haeufige Fallstricke wie fehlendebecome-Deklarationen auf). - Vault-Secrets niemals im Klartext committen - Pre-Commit-Hook mit
detect-secretsodergit-secretsergaenzen. - Rollout auf eine kleine Kanarien-Gruppe (
--limit kanarien), Health-Checks pruefen. - Vollrollout mit
serial: 5im Playbook, damit nicht alle Hosts gleichzeitig betroffen sind, falls doch ein Fehler durchrutscht. - Ergebnis dokumentieren - idealerweise landet der Playbook-Lauf als Log-Eintrag im Change-Prozess (siehe Change Management nach ITIL).
Fazit
Ansible ersetzt in Schweizer KMU-Umgebungen zuverlaessig die Sammlung handgeschriebener Skripte durch nachvollziehbare, versionierte und idempotente Automatisierung - ohne dass auf den Zielsystemen ein zusaetzlicher Agent installiert werden muss. Der Umstieg von statischem auf dynamisches Inventory lohnt sich, sobald die Infrastruktur sich haeufiger aendert als ein manuell gepflegtes Excel mithalten kann. Ansible Vault loest das Secret-Management-Problem elegant, solange das Vault-Passwort selbst konsequent aus dem Repository ferngehalten wird. Und mit Check-Mode, Tags sowie Block/Rescue-Strukturen laesst sich auch in gemischten Windows/Linux-Umgebungen ein Automatisierungsgrad erreichen, der manuelle Fehler drastisch reduziert.
Weiterlernen
- Ansible-Dokumentation: Getting Started
- Ansible Vault - offizielle Doku
- Managing Windows hosts with Ansible
- Windows Remote Management (WinRM) - Ansible Community Doku
- community.vmware Collection - VMware Dynamic Inventory
- ansible-lint - Best-Practice-Linting fuer Playbooks
Videos
Kommentare
Frage, Verbesserungsvorschlag oder eigene Erfahrung zu diesem Artikel? Schreib einen Kommentar. Neue Beiträge erscheinen nach kurzer Moderation.
- Lade Kommentare …