TL;DR — Kurzzusammenfassung
Meistern Sie Ansible-Playbooks und Roles fuer die Infrastrukturautomatisierung. Inventar, Module, Jinja2, ansible-vault, Fehlerbehandlung und LAMP-Deployment.
Infrastruktur in grossem Massstab ohne Automatisierung zu verwalten fuehrt unweigerlich zu Konfigurationsdrift, vergessenen Schritten und Vorfaellen mitten in der Nacht. Ansible-Playbooks und -Roles bieten eine strukturierte, wiederholbare Moeglichkeit, genau festzulegen, wie jeder Server aussehen soll, und diesen Zustand idempotent ueber die gesamte Flotte durchzusetzen. Dieser Leitfaden deckt den vollstaendigen Ansible-Arbeitsablauf ab — von der Inventarverwaltung und Playbook-Architektur ueber Roles, Jinja2-Templates, ansible-vault und Fehlerbehandlung bis hin zu einem LAMP-Deployment als abschliessendes Praxisbeispiel.
Voraussetzungen
- Ansible auf einem Steuerknoten installiert (Ubuntu 22.04+ oder jede Linux/macOS-Maschine)
- SSH-Schluessel-basierter Zugriff auf einen oder mehrere verwaltete Hosts
- Python 3 auf allen Ziel-Hosts (auf den meisten Distributionen vorinstalliert)
- Grundlegende Kenntnisse der YAML-Syntax und der Linux-Kommandozeile
Ansible-Architektur: Das Push-Modell
Ansible verwendet eine agentenlose Push-Architektur. Ihr Steuerknoten sendet Konfigurationen an verwaltete Knoten ueber SSH. Es laeuft kein Daemon, es gibt keine Zustandsdatenbank und es muss nichts auf den Zielen installiert werden ausser einem funktionierenden Python-Interpreter.
Die Ausfuehrungskette lautet: Inventar → Playbook → Module.
- Ansible liest das Inventar, um festzustellen, welche Hosts anvisiert werden sollen
- Es analysiert das Playbook, um eine geordnete Liste von Tasks zu erstellen
- Fuer jeden Task kopiert Ansible ein kleines Python-Modul via SSH auf den Remote-Host, fuehrt es aus, erfasst das Ergebnis und entfernt das Modul
- Die Ergebnisse (ok / changed / failed / skipped) werden aggregiert und im Terminal angezeigt
Inventarverwaltung
INI- vs. YAML-Format
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_port=2222
[dbservers]
db1.example.com
db2.example.com
[production:children]
webservers
dbservers
[all:vars]
ansible_user=deployer
ansible_ssh_private_key_file=~/.ssh/id_ed25519
# inventory.yml
all:
vars:
ansible_user: deployer
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
ansible_port: 2222
dbservers:
hosts:
db1.example.com:
db2.example.com:
group_vars und host_vars
projekt/
inventory.ini
group_vars/
all.yml
webservers.yml
dbservers.yml
host_vars/
web1.example.com.yml
site.yml
# group_vars/webservers.yml
http_port: 80
https_port: 443
document_root: /var/www/html
worker_processes: 4
Dynamisches Inventar mit dem aws_ec2-Plugin
# aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
instance-state-name: running
"tag:Environment": production
keyed_groups:
- key: tags.Role
prefix: role
Playbook-Struktur
- name: Webserver konfigurieren
hosts: webservers
become: true
vars:
app_name: myapp
app_port: 8080
tasks:
- name: nginx installieren
apt:
name: nginx
state: present
update_cache: true
- name: nginx-Konfiguration aus Template deployen
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
notify: nginx neu starten
- name: Sicherstellen, dass nginx laeuft und aktiviert ist
service:
name: nginx
state: started
enabled: true
handlers:
- name: nginx neu starten
service:
name: nginx
state: restarted
Bedingungen mit when
- name: apache2 installieren (nur Debian)
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: httpd installieren (nur Red Hat)
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
Schleifen mit loop
- name: Benoetierte Pakete installieren
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- curl
- ufw
- fail2ban
- name: Anwendungsverzeichnisse erstellen
file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
mode: "{{ item.mode }}"
loop:
- { path: /var/www/myapp, owner: www-data, mode: "0755" }
- { path: /var/log/myapp, owner: www-data, mode: "0750" }
Jinja2-Templates
{# templates/nginx.conf.j2 #}
user www-data;
worker_processes {{ worker_processes | default('auto') }};
http {
server {
listen {{ http_port }};
server_name {{ server_name }};
root {{ document_root }};
{% if enable_ssl | default(false) %}
listen {{ https_port }} ssl;
ssl_certificate {{ ssl_cert_path }};
{% endif %}
}
}
Tags fuer selektive Ausfuehrung
# Nur Tasks mit dem Tag 'config' ausfuehren
ansible-playbook site.yml --tags config
# Tasks mit dem Tag 'install' ueberspringen
ansible-playbook site.yml --skip-tags install
Wesentliche Module
| Modul | Zweck | Hauptparameter |
|---|---|---|
apt / yum | Paketverwaltung | name, state, update_cache |
copy | Statische Dateien kopieren | src, dest, owner, mode |
template | Jinja2-Templates deployen | src, dest, owner, mode |
service | Systemdienste verwalten | name, state, enabled |
user | Benutzerkonten verwalten | name, groups, shell, state |
file | Verz., Symlinks erstellen | path, state, owner, mode |
lineinfile | Zeilen in Dateien bearbeiten | path, regexp, line, state |
command | Befehl ausfuehren | argv, creates |
shell | Shell-Befehle mit Pipes | cmd, chdir |
git | Repository klonen/aktualisieren | repo, dest, version |
docker_container | Docker-Container verwalten | name, image, state, ports |
debug | Variablenwerte ausgeben | msg, var |
Role-Struktur
ansible-galaxy init roles/webserver
roles/webserver/
tasks/
main.yml
handlers/
main.yml
templates/
nginx.conf.j2
files/
index.html
vars/
main.yml
defaults/
main.yml
meta/
main.yml
defaults vs. vars
defaults/main.yml: Niedrige Prioritaet. Aufrufer koennen diese Werte leicht ueberschreiben.vars/main.yml: Hohe Prioritaet. Diese ueberschreiben die meisten anderen Variablenquellen.
# roles/webserver/defaults/main.yml
http_port: 80
https_port: 443
worker_processes: auto
document_root: /var/www/html
enable_ssl: false
# roles/webserver/tasks/main.yml
- name: nginx installieren
apt:
name: nginx
state: present
update_cache: true
- name: Dokumentstammverzeichnis erstellen
file:
path: "{{ document_root }}"
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: nginx-Konfiguration deployen
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: nginx neu starten
Roles in einem Playbook verwenden
- name: Web-Tier konfigurieren
hosts: webservers
become: true
roles:
- role: common
- role: security
- role: webserver
vars:
http_port: 80
enable_ssl: true
document_root: /var/www/myapp
Ansible Galaxy fuer Community-Roles
ansible-galaxy install geerlingguy.mysql
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
- name: geerlingguy.nginx
version: "3.2.0"
collections:
- name: community.docker
version: ">=3.4.0"
Ansible Vault fuer Geheimnisverwaltung
ansible-vault create group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml
ansible-vault encrypt secrets.yml
ansible-vault encrypt_string 'MeinGeheimnis123' --name 'db_password'
# group_vars/all/vars.yml (Klartext, im Git)
db_host: db1.example.com
db_password: "{{ vault_db_password }}"
# group_vars/all/vault.yml (verschluesselt, im Git)
vault_db_password: "SuperGeheim123!"
vault_api_key: "abcdef1234567890"
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Fehlerbehandlung
ignore_errors und failed_when
- name: Pruefen ob Anwendung installiert ist
command: which myapp
register: myapp_check
ignore_errors: true
changed_when: false
- name: Anwendung nur installieren wenn fehlend
apt:
name: myapp
state: present
when: myapp_check.rc != 0
- name: Migrationsskript ausfuehren
command: /opt/myapp/migrate.sh
register: migration_result
failed_when: "'ERROR' in migration_result.stdout"
changed_when: "'Changes applied' in migration_result.stdout"
block / rescue / always
- name: Anwendung mit Rollback deployen
block:
- name: Anwendungsdienst stoppen
service:
name: myapp
state: stopped
- name: Neue Version deployen
git:
repo: https://github.com/org/myapp.git
dest: /opt/myapp
version: "{{ app_version }}"
- name: Anwendungsdienst starten
service:
name: myapp
state: started
rescue:
- name: Auf vorherige Version zuruecksetzen
git:
repo: https://github.com/org/myapp.git
dest: /opt/myapp
version: "{{ previous_version }}"
- name: Team ueber Fehler benachrichtigen
debug:
msg: "Deployment von {{ app_version }} fehlgeschlagen. Zurueckgesetzt auf {{ previous_version }}."
always:
- name: Deployment-Versuch protokollieren
shell: echo "{{ app_version }} versucht am $(date)" >> /var/log/deployments.log
changed_when: false
Best Practices fuer Idempotenz
- Module bevorzugen statt
shell/command: Module pruefen den Zustand vor dem Handeln createsmitcommandverwenden: ueberspringt den Befehl, wenn die Datei bereits existiertchanged_when: falsesetzen bei Befehlen, die nur Zustand lesenlineinfilestattshell echo >>zum Aendern von Konfigurationsdateien verwendenapt: update_cache: truenicht bei jedem Task:cache_valid_time: 3600nutzen
ansible.cfg-Konfiguration
[defaults]
inventory = ./inventory.ini
remote_user = deployer
private_key_file = ~/.ssh/id_ed25519
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
roles_path = ./roles:~/.ansible/roles
forks = 10
[privilege_escalation]
become = True
become_method = sudo
become_user = root
Debugging und Tests
ansible-playbook site.yml --check --diff
ansible-playbook site.yml -vvv
ansible-playbook site.yml --syntax-check
- name: Aufgeloeste Variablen anzeigen
debug:
msg: "App: {{ app_name }}, Port: {{ app_port }}"
- name: Befehlsausgabe erfassen und pruefen
command: systemctl status nginx
register: nginx_status
ignore_errors: true
changed_when: false
- name: nginx-Status ausgeben
debug:
var: nginx_status.stdout_lines
Werkzeugvergleich
| Merkmal | Ansible | Terraform | Puppet | Chef | SaltStack |
|---|---|---|---|---|---|
| Hauptanwendung | Konfig-Mgmt + Orchestrierung | Infrastruktur-Provisionierung | Konfig-Mgmt | Konfig-Mgmt | Konfig + Remote-Exec |
| Architektur | Agentenlos (SSH) | Agentenlos (API) | Mit Agent | Mit Agent | Mit oder ohne Agent |
| Sprache | YAML | HCL | Puppet DSL | Ruby | YAML / Python |
| Zustandsverfolgung | Zustandslos | tfstate-Datei | PuppetDB | Chef-Server | Salt mine |
| Lernkurve | Niedrig | Mittel | Hoch | Hoch | Mittel |
| Idempotent | Ja | Ja | Ja | Ja | Ja |
Ansible und Terraform sind komplementaer: Verwenden Sie Terraform zum Erstellen von Cloud-Ressourcen und Ansible zur Konfiguration.
Praxisbeispiel: LAMP-Stack-Deployment
# lamp.yml
- name: LAMP-Stack deployen
hosts: webservers
become: true
vars:
php_version: "8.3"
mysql_root_password: "{{ vault_mysql_root_password }}"
mysql_db_name: myapp
mysql_db_user: appuser
mysql_db_password: "{{ vault_mysql_db_password }}"
tasks:
- name: Apache und PHP-Pakete installieren
apt:
name:
- apache2
- "libapache2-mod-php{{ php_version }}"
- "php{{ php_version }}"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-curl"
- "php{{ php_version }}-mbstring"
state: present
update_cache: true
- name: MySQL-Server installieren
apt:
name:
- mysql-server
- python3-pymysql
state: present
- name: MySQL starten und aktivieren
service:
name: mysql
state: started
enabled: true
- name: Anwendungsdatenbank erstellen
mysql_db:
login_user: root
login_password: "{{ mysql_root_password }}"
name: "{{ mysql_db_name }}"
state: present
- name: Apache mod_rewrite aktivieren
apache2_module:
name: rewrite
state: present
notify: Apache neu starten
- name: Anwendung aus Git klonen
git:
repo: "{{ app_repo }}"
dest: /var/www/html
version: "{{ app_version }}"
force: true
- name: Eigentuemer des Web-Verzeichnisses setzen
file:
path: /var/www/html
owner: www-data
group: www-data
recurse: true
- name: Sicherstellen, dass Apache laeuft und aktiviert ist
service:
name: apache2
state: started
enabled: true
handlers:
- name: Apache neu starten
service:
name: apache2
state: restarted
ansible-playbook -i inventory.ini lamp.yml --vault-password-file ~/.vault_pass
Zusammenfassung
- Die agentenlose Push-Architektur von Ansible bedeutet, dass Sie nur SSH-Zugriff benoetigen, um mit der Automatisierung zu beginnen
- Das Inventar (INI, YAML oder dynamisch) steuert das gesamte Host-Targeting;
group_varsundhost_varshalten Variablen organisiert - Playbooks verketten Plays, Tasks, Handlers und Jinja2-Templates in einem lesbaren, deklarativen Arbeitsablauf
- Roles verpacken Tasks, Handlers, Templates und Defaults in wiederverwendbare, teilbare Einheiten
- ansible-vault verschluesselt Geheimnisse im Ruhezustand; kombinieren Sie Vault-Dateien mit Klartextvariablen-Dateien fuer eine saubere Git-Geschichte
block/rescue/alwaysbietet strukturierte Fehlerbehandlung und Rollback-Logik fuer Produktions-Deployments- Validieren Sie immer mit
--check --diffund-vvvbevor Sie in der Produktion ausfuehren
Verwandte Artikel
- Ansible fuer Einsteiger: Serverkonfiguration ohne Agent automatisieren
- Ansible Vault: Geheimnisse sicher verschluesseln und verwalten
- Fehlerbehebung bei Ansible-Playbooks: Haeufige Fehler und Debugging
- Semaphore UI: Ansible-Weboberflaeche fuer Playbook-Verwaltung
- Einfuehrung in Terraform: Infrastruktur als Code