SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-07

Ansible-Playbook oder Rolle: Wann Sie was nutzen

Erfahren Sie, wann ein flaches Ansible-Playbook genügt und ab wann eine Rolle sinnvoll ist, inklusive Verzeichnisstruktur, ansible-galaxy init, Aufruf und Variablenrangfolge.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Ansible-Playbook vs. Rolle: Was ist der Unterschied?

Ein Ansible-Playbook ist die Datei, die Sie mit ansible-playbook ausführen. Es ordnet eine Gruppe von Hosts den erforderlichen Aufgaben zu. Eine Ansible-Rolle ist ein Verzeichnis mit einer festen Struktur. Es enthält Tasks, Templates, Handler und Standardvariablen. Ein Playbook ruft die Rolle anhand ihres Namens auf. Die Task-Syntax ist in beiden Fällen identisch. Es geht daher nicht darum, was Sie ausdrücken können. Es geht um Wiederverwendung.

Beginnen Sie mit einem flachen Playbook. Eine einzelne site.yml mit einer tasks:-Liste ist die richtige Struktur für Ihre erste Automatisierung. Sie bleibt länger sinnvoll, als die meisten erwarten. Wandeln Sie das Playbook in eine Rolle um, wenn derselbe Task-Block für eine zweite Hostgruppe ausgeführt werden muss oder wenn die Datei ungefähr 100 Zeilen überschreitet und Sie einen Task nicht mehr durch einfaches Scrollen finden.

Wenn Sie noch kein Playbook geschrieben haben, beginnen Sie mit einem ersten Playbook für einen einzelnen VPS und kehren Sie zurück, sobald es größer wird.

Wenn ein flaches Playbook die richtige Lösung ist

Ein flaches Playbook ist die richtige Wahl, wenn die Arbeiten einmalig auf einem Host ausgeführt werden oder niemand anderes das Playbook lesen wird. Einen einzelnen Anwendungsserver bereitzustellen oder einen Server vor einem Wartungsfenster zu patchen, rechtfertigt keine Verzeichnisstruktur. Eine Role fügt sieben Verzeichnisse und eine zusätzliche Abstraktionsebene hinzu. Wenn das Playbook daneben der einzige Aufrufer ist, bringt diese Abstraktion keinen Vorteil. Bei jedem Lesen der tatsächlich ausgeführten Aufgaben ist jedoch ein zusätzlicher Sprung erforderlich.

Das flache Playbook ist ab einem bestimmten Zeitpunkt nicht mehr die richtige Wahl. Dieser Zeitpunkt ist leicht zu erkennen. Sie kopieren einen Aufgabenblock in ein zweites Playbook. Diese Kopie ist das Signal. Ab diesem Zeitpunkt muss jede Korrektur zweimal vorgenommen werden. Eines Tages wird sie jedoch nur einmal vorgenommen.

Was ein Rollenverzeichnis tatsächlich enthält

roles/common/
  defaults/main.yml
  vars/main.yml
  tasks/main.yml
  handlers/main.yml
  templates/99-hardening.conf.j2
  files/
  meta/main.yml
  • tasks/main.yml ist der Einstiegspunkt. Ansible führt diese Datei aus, wenn die Rolle aufgerufen wird. Alle anderen Verzeichnisse sind optional.
  • defaults/main.yml enthält die Variablen, die ein Aufrufer voraussichtlich überschreibt. Diese Quelle hat in Ansible die niedrigste Priorität. Fast alles andere hat Vorrang.
  • vars/main.yml enthält Variablen, die ein Aufrufer voraussichtlich nicht überschreibt. Diese Variablen haben eine höhere Priorität als das Inventory. Das ist eine weitreichende Festlegung. Verwenden Sie diesen Bereich sparsam.
  • handlers/main.yml enthält Tasks, die von notify ausgelöst werden. Ein Handler wird am Ende des Plays genau einmal ausgeführt, unabhängig davon, wie viele Tasks ihn benachrichtigt haben.
  • files/ enthält Dateien, die das Modul copy unverändert kopiert. templates/ enthält Jinja2-Templates, die das Modul template rendert. Innerhalb einer Rolle referenzieren Sie beide über den reinen Dateinamen ohne Pfad, weil Ansible zuerst in den Verzeichnissen der Rolle sucht.
  • meta/main.yml definiert Rollenabhängigkeiten und die Metadaten, die Ansible Galaxy liest.

Die Verzeichnisstruktur ist keine Stilfrage. Ansible sucht in diesen exakt festgelegten Pfaden. Ein Template, das Sie in roles/common/template/ (Singular) ablegen, wird daher einfach nicht gefunden.

Die allgemeine Rolle mit ansible-galaxy init erstellen

mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles common

Damit wird das vollständige Grundgerüst unter roles/common erstellt. Es enthält auch Verzeichnisse, die Sie nicht verwenden, sowie main.yml-Stubdateien, die nur --- enthalten. Löschen Sie Verzeichnisse und Dateien, die leer bleiben. Ein leeres vars/main.yml ist für Ansible unproblematisch. Es macht jedoch schwerer erkennbar, welche Dateien in der Rolle tatsächlich relevant sind.

Füllen Sie nun die Dateien aus, die die eigentliche Arbeit erledigen. Beginnen Sie mit den Defaults. Sie bilden die öffentliche Schnittstelle der Rolle.

# roles/common/defaults/main.yml
---
common_packages:
  - ufw
  - fail2ban
  - unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"

Setzen Sie "no" und "yes" in Anführungszeichen. Ansible analysiert YAML mit PyYAML. Dabei wird ein nicht in Anführungszeichen gesetztes no als boolescher Wert false interpretiert. Dadurch wird die Konfigurationszeile als PermitRootLogin False gerendert, und sshd lehnt sie ab. Die Anführungszeichen sorgen dafür, dass der Wert als Zeichenfolge behandelt wird.

# roles/common/tasks/main.yml
---
- name: Install the base packages
  ansible.builtin.apt:
    name: "{{ common_packages }}"
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Create the admin group
  ansible.builtin.group:
    name: "{{ common_admin_group }}"
    state: present

- name: Install the sshd hardening drop-in
  ansible.builtin.template:
    src: 99-hardening.conf.j2
    dest: /etc/ssh/sshd_config.d/99-hardening.conf
    owner: root
    group: root
    mode: "0644"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd
# roles/common/handlers/main.yml
---
- name: Restart sshd
  ansible.builtin.service:
    name: ssh
    state: restarted
# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}

Unter Debian und Ubuntu heißt die systemd-Unit ssh. Auf Systemen der RHEL-Familie heißt sie sshd. Ein Handler mit dem falschen Namen schlägt erst fehl, wenn sich tatsächlich etwas an der Vorlage ändert. Deshalb wird der Fehler häufig erst Wochen später sichtbar.

Die Zeile validate ist der wichtigste Teil dieser Task. Ansible rendert die Vorlage in eine temporäre Datei, ersetzt %s durch den Pfad dieser Datei und führt den Befehl aus. Das Ziel wird nur ersetzt, wenn der Befehl mit 0 beendet wird. Fügen Sie eine ungültige Direktive in die Vorlage ein und führen Sie die Task erneut aus: Die Task schlägt mit failed to validate fehl, die echte /etc/ssh/sshd_config.d/99-hardening.conf bleibt unverändert, und Sie können sich weiterhin am Server anmelden. Beachten Sie, dass die Prüfung mehr als nur die Syntax testet. Wenn sshd -t die Hostschlüssel nicht lesen kann, wird der Prozess mit sshd: no hostkeys available -- exiting. beendet, und Ansible meldet denselben failed to validate. Lesen Sie daher zuerst msg des Moduls, bevor Sie die Vorlage als Ursache ansehen.

Wie ein Playbook eine Rolle aufruft

# site.yml
---
- name: Base configuration for every server
  hosts: all
  become: true
  roles:
    - common
# inventory.ini
[local]
localhost ansible_connection=local
ansible-playbook -i inventory.ini site.yml

Das Play sollte in der Zusammenfassung mit failed=0 enden. Übergeben Sie Parameter an der Aufrufstelle in der erweiterten Form. So kann eine Rolle für zwei Hostgruppen verwendet werden:

  roles:
    - role: common
      common_admin_group: ops
      common_permit_root_login: prohibit-password

Es gibt eine Reihenfolgeregel, die fast alle überrascht. Ein Play kann pre_tasks, roles, tasks und post_tasks enthalten. Ansible führt sie unabhängig von der Reihenfolge, in der sie in der Datei stehen, in dieser Reihenfolge aus. Setzen Sie tasks: oberhalb von roles:, werden die Rollen trotzdem zuerst ausgeführt. Muss etwas vor einer Rolle geschehen, gehört es daher in pre_tasks: und nicht an den Anfang von tasks:.

- name: Ordering demonstration
  hosts: local
  gather_facts: false
  pre_tasks:
    - name: Runs first
      ansible.builtin.debug:
        msg: pre
  roles:
    - common
  tasks:
    - name: Runs after the role
      ansible.builtin.debug:
        msg: task
  post_tasks:
    - name: Runs last
      ansible.builtin.debug:
        msg: post

Um eine Rolle innerhalb einer Aufgabenliste statt über den Schlüssel roles: aufzurufen, verwenden Sie import_role oder include_role.

  tasks:
    - name: Static, read when the playbook is parsed
      ansible.builtin.import_role:
        name: common

    - name: Dynamic, resolved when the task runs
      ansible.builtin.include_role:
        name: postgres
      when: "'db' in group_names"

import_role ist statisch. Ansible liest die Rolle zur Parse-Zeit ein und ihre Aufgaben werden Teil des Plays. Daher listet ansible-playbook --list-tasks site.yml diese Aufgaben auf, und ein Tag beim Import gilt für jede darin enthaltene Aufgabe. include_role ist dynamisch. Die Rolle wird erst gelesen, wenn die Aufgabe ausgeführt wird. Dadurch können Sie den Rollennamen aus einer Variable oder einer Schleife beziehen. Der Nachteil ist, dass diese Aufgaben für --list-tasks und --start-at-task nicht sichtbar sind.

Hier gibt es eine typische Fehlerquelle. Ein when: bei einer include_role-Aufgabe wird ausgewertet, bevor defaults/main.yml der eingebundenen Rolle im Gültigkeitsbereich liegt. Schreiben Sie when: common_packages | length > 0 auf den Include, bricht die Ausführung mit 'common_packages' is undefined ab, obwohl diese Variable in der gerade eingebundenen Rolle definiert ist. Verschieben Sie die Umschaltvariable stattdessen aus der Rolle heraus. Legen Sie sie in group_vars/all.yml ab, wo sie überall im Gültigkeitsbereich liegt. Belassen Sie die Standardwerte der Rolle bei den Werten, die die Rolle selbst verwendet.

Welche Variable setzt sich durch: Defaults, group_vars, Vars, Extra Vars

Ansible dokumentiert mehr als zwanzig Ebenen für die Variablenpräzedenz. Vier davon entscheiden fast jede praktische Diskussion. Hier sind sie von der niedrigsten bis zur höchsten Priorität aufgeführt.

  • roles/<name>/defaults/main.yml liegt weit unten in der Hierarchie. Fast alles, was Sie an anderer Stelle setzen, hat Vorrang. Genau deshalb ist es der richtige Ort für die anpassbaren Parameter einer Rolle.
  • group_vars/ und host_vars/ liegen in der Mitte. Hier gehören die Einstellungen Ihrer Umgebung hin. Sie überschreiben die Role Defaults sauber.
  • roles/<name>/vars/main.yml hat Vorrang vor host_vars. Ein hier gesetzter Wert kann nicht aus dem Inventory überschrieben werden. Reservieren Sie diese Ebene für Dinge, die innerhalb der Rolle konsistent bleiben müssen, beispielsweise einen Paketnamen, der mit einem Servicenamen übereinstimmen muss.
  • Ein Rollenparameter, der am Aufruf übergeben wird, hat Vorrang vor vars/main.yml. -e auf der Kommandozeile hat Vorrang vor allem anderen, einschließlich Rollenparametern.

Sie können diese Auflösung in etwa einer Minute nachvollziehen. Geben Sie einer kleinen Rolle einen Default und eine Role Var. Setzen Sie anschließend dieselben Namen in host_vars.

# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults
# roles/prec/vars/main.yml
---
prec_internal: from-rolevars
# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars
# roles/prec/tasks/main.yml
---
- name: Show which value survived
  ansible.builtin.debug:
    msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"
ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cli

Der erste Lauf gibt tunable=from-hostvars internal=from-rolevars aus. Das Inventory hatte Vorrang vor dem Role Default, aber nicht vor der Role Var. Der zweite Lauf gibt internal=from-cli aus, weil Extra Vars ganz oben stehen und nichts darunter sie überschreiben kann. Deshalb ist -e für einen einmaligen Lauf geeignet, aber in einem dauerhaft verwendeten Script falsch: Es hat stillschweigend Vorrang vor jeder berücksichtigten Entscheidung in Ihrem Repository.

Die praktische Regel lautet: Wenn ein Wert gesetzt werden können soll, gehört er in defaults/. Wenn Sie ihn in vars/ setzen, teilen Sie jedem künftigen Benutzer der Rolle mit, dass das Inventory ihn nicht ändern darf. Das ist gelegentlich beabsichtigt, meistens jedoch ein Fehler.

Die Idempotenz der Rolle nachweisen: zweimal ausführen

Ein vertrauenswürdiger Ansible-Lauf erzeugt beim zweiten Durchlauf dasselbe Ergebnis und meldet, dass nichts geändert wurde. Führen Sie das Playbook zweimal aus und lesen Sie die Zusammenfassung.

ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml

Die zweite Zusammenfassung sollte so aussehen:

PLAY RECAP *********************************************************************
localhost   : ok=4  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0 bedeutet, dass jedes Modul den aktuellen Zustand geprüft und festgestellt hat, dass die Arbeit bereits erledigt war. changed=2 bei einem zweiten Lauf bedeutet, dass zwei Tasks den Unterschied nicht erkennen können. Sie schreiben daher dauerhaft Dateien neu und starten Dienste immer wieder. Die übliche Ursache ist command oder shell, weil Ansible nicht feststellen kann, was ein beliebiger Befehl bewirkt hat.

# traps.yml
---
- name: Command modules do not know what they changed
  hosts: local
  gather_facts: false
  tasks:
    - name: This appends a line on every run
      ansible.builtin.shell: "echo run >> /tmp/grow.txt"

    - name: This appends a line only once
      ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
      args:
        creates: /tmp/guarded.txt

Führen Sie dieses Playbook zweimal aus und zählen Sie die Zeilen mit wc -l /tmp/grow.txt /tmp/guarded.txt. /tmp/grow.txt enthält zwei Zeilen und /tmp/guarded.txt enthält eine Zeile. Beim zweiten Lauf wurde der geschützte Task überhaupt nicht ausgeführt. Sein Ergebnis enthält die Meldung skipped, since /tmp/guarded.txt exists, weil creates dem Modul zunächst ein sichtbares Ergebnis zur Suche vorgibt. Wenn ein Befehl kein solches Ergebnis hinterlässt, registrieren Sie seine Ausgabe und entscheiden Sie selbst mit changed_when.

ansible-playbook --check --diff site.yml prognostiziert Änderungen, ohne sie auszuführen, und --diff gibt die exakten Zeilen aus, die ein Template neu schreiben würde. Berücksichtigen Sie bei der Auswertung eine Einschränkung: shell- und command-Tasks werden im Check-Modus übersprungen. Ein sauber aussehender Plan kann daher dennoch ausstehende Änderungen verbergen.

Warum meldet Ansible, dass die Rolle nicht gefunden wurde

Ansible sucht nach einem roles/-Verzeichnis neben der Playbook-Datei und anschließend in roles_path. Die Suche richtet sich nach dem Playbook, nicht nach Ihrer Shell.

ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely

Diese Meldung bedeutet, dass site.yml und roles/ nicht mehr zusammenpassen. Ansible gibt dabei hilfreich die Pfade aus, die es versucht hat. Legen Sie beide im selben Verzeichnis ab. Der Aufruf aus einem übergeordneten Verzeichnis ist unproblematisch, weil der Pfad des Playbooks maßgeblich ist:

ansible-playbook -i infra/inventory.ini infra/site.yml

Dasselbe Problem kann unauffälliger auftreten. Ansible ignoriert ein ansible.cfg im aktuellen Verzeichnis, wenn dieses Verzeichnis für alle Benutzer beschreibbar ist. Jeder Benutzer auf dem System könnte dort eine Konfiguration ablegen und dadurch das Verhalten Ihres Laufs ändern.

[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.

Ihre Einstellungen für roles_path und inventory fehlen dann stillschweigend. Die Rollensuche schlägt aus einem Grund fehl, der nichts mit Rollen zu tun hat. ansible --version gibt das config file aus, das tatsächlich geladen wurde. ansible-config dump --only-changed gibt jede Einstellung aus, die von den integrierten Standardwerten abweicht. Prüfen Sie beide Ausgaben, wenn sich ein Lauf so verhält, als wäre Ihre Konfiguration nicht vorhanden.

Rollen gemeinsam nutzen: requirements.yml und eine festgelegte Version

Eine von jemand anderem geschriebene Rolle wird installiert, nicht kopiert. Deklarieren Sie sie einmal:

# requirements.yml
---
roles:
  - name: postgres
    src: https://github.com/example/ansible-role-postgres
    scm: git
    version: v1.4.0
ansible-galaxy install -r requirements.yml -p galaxy_roles

Setzen Sie version immer. Ohne diese Angabe erhalten Sie den Inhalt des Standard-Branches an dem Tag, an dem Sie den Befehl ausführen. Dadurch kann eine Deployment-Konfiguration, die im vergangenen Monat funktioniert hat, ohne jede Änderung in Ihrem eigenen Repository fehlschlagen. Verweisen Sie roles_path auf das Download-Verzeichnis und nehmen Sie dieses Verzeichnis aus git aus:

# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_roles

Rollen in roles/ neben dem Playbook werden weiterhin gefunden, weil dieser Pfad zusätzlich zu roles_path immer durchsucht wird. Ihre eigenen Rollen bleiben dadurch versioniert und geprüft, während Rollen von Drittanbietern reproduzierbar aus Downloads stammen und auf einen Tag festgelegt sind.

Wo Rollen nicht mehr die richtige Lösung sind

Eine Rolle ist eine Einheit zur Wiederverwendung innerhalb eines Ansible-Laufs. Sie erstellt bei Ihrem Provider weder Server noch DNS-Einträge. Wenn Sie sie dafür verwenden, werden Playbooks schnell zu etwas, das niemand mehr warten möchte. Die Aufteilung der Aufgaben zwischen Ansible und Terraform sollten Sie lesen, bevor Sie beginnen. Eine Rolle ersetzt außerdem kein Inventardesign: Sobald Sie mehr als eine Handvoll Rechner verwalten, ist wie Sie diese Server gruppieren und erreichen wichtiger als die Ablage der Tasks.

Die Härtung, die diese common-Rolle installiert, erfordert ebenfalls eigene Entscheidungen. Der obige Drop-in setzt genau zwei Direktiven und keine weiteren. Lesen Sie daher welche SSH-Einstellungen sich tatsächlich zu ändern lohnen und wie Ubuntu Sicherheitsupdates automatisch installiert, bevor Sie entscheiden, was für jeden von Ihnen verwalteten Host in die Rolle gehört.

FAQ

Wann sollte ich ein Ansible-Playbook in eine Rolle umwandeln?

Wenn derselbe Aufgabenblock in einem zweiten Play oder für eine zweite Hostgruppe ausgeführt werden muss. Das Kopieren von Aufgaben zwischen Playbooks ist das entscheidende Signal. Ab diesem Zeitpunkt muss jede Korrektur doppelt angewendet werden. Eines Tages wird sie dann nur an einer Stelle umgesetzt. Ein einzelnes Playbook mit ungefähr 100 Zeilen, das immer nur auf eine Hostgruppe zielt, profitiert nicht von einer Rolle. Die zusätzlichen Verzeichnisse erschweren dann die Lesbarkeit.

Werden Rollen vor den Aufgaben im selben Play ausgeführt?

Ja. Ansible führt pre_tasks aus, danach alles unter roles:, anschließend tasks: und dann post_tasks:. Die Reihenfolge dieser Schlüssel in Ihrer Datei wird dabei ignoriert. Wenn tasks: über roles: steht, werden diese Aufgaben deshalb nicht zuerst ausgeführt. Muss etwas vor einer Rolle geschehen, tragen Sie es unter pre_tasks: ein.

Warum überschreibt mein Wert in group_vars nicht die Rolle?

Prüfen Sie, ob die Variable in vars/main.yml der Rolle statt in defaults/main.yml gesetzt ist. vars/ hat in der Prioritätsreihenfolge von Ansible Vorrang vor group_vars und host_vars. Deshalb kann das Inventory den Wert nicht überschreiben. Verschieben Sie die Variable nach defaults/main.yml. Dieser Bereich liegt weit unten in der Prioritätsreihenfolge und ist der richtige Ort für Werte, die ein Aufrufer ändern können soll. Um zu bestätigen, dass die Priorität und nicht ein Tippfehler die Ursache ist, führen Sie den Vorgang einmal mit -e name=value aus. Diese Quelle hat Vorrang vor allen anderen.

Warum meldet Ansible, dass die Rolle nicht gefunden wurde?

Die Suche beginnt neben der Playbook-Datei. Daher müssen site.yml und roles/ im selben Verzeichnis liegen. Der Fehler zeigt die geprüften Pfade an, beispielsweise the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Sie können das Playbook aus einem übergeordneten Verzeichnis ausführen. Die Suche folgt dem Pfad des Playbooks und nicht dem aktuellen Arbeitsverzeichnis Ihrer Shell. Wenn Sie roles_path aus ansible.cfg verwenden, prüfen Sie mit ansible --version, ob diese Datei geladen wurde. Ansible ignoriert sie, wenn das Arbeitsverzeichnis für alle Benutzer beschreibbar ist.

Benötige ich ansible-galaxy init, um eine Rolle zu erstellen?

Nein. Eine Rolle besteht lediglich aus Verzeichnissen mit den erwarteten Namen. Daher sind mkdir -p roles/common/tasks und ein tasks/main.yml bereits eine funktionierende Rolle. ansible-galaxy init --init-path roles common erspart Tipparbeit und erstellt das vollständige Grundgerüst einschließlich meta/main.yml und eines README-Platzhalters. Löschen Sie Verzeichnisse, die leer bleiben. Ein leeres vars/main.yml verschleiert, welche Dateien in der Rolle tatsächlich etwas bewirken.