SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-24

Ansible-Playbook oder Rolle: Wann welche Lösung passt

Erfahren Sie, wann ein flaches Ansible-Playbook genügt und ab wann eine Rolle mit fester Struktur sinnvoll wird, inklusive ansible-galaxy init, Einbindung und Variablenpriorität.

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 bindet die Rolle über ihren Namen ein. 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 passende Struktur für Ihre erste Automatisierung. Sie bleibt länger sinnvoll, als viele 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 etwa 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 hierher zurück, sobald es wächst.

Wenn ein flaches Playbook die richtige Lösung ist

Ein flaches Playbook ist die richtige Wahl, wenn die Arbeit einmalig auf einem Host ausgeführt wird oder niemand anderes es 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 nichts. Sie kostet Sie jedoch jedes Mal einen Sprung, wenn Sie nachlesen möchten, was tatsächlich ausgeführt wird.

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. Von da an muss jede Korrektur zweimal vorgenommen werden. Eines Tages wird sie jedoch nur noch an einer Stelle vorgenommen.

Was ein Role-Verzeichnis 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 Role aufgerufen wird. Alle anderen Verzeichnisse sind optional.
  • defaults/main.yml enthält Variablen, die ein Aufrufer voraussichtlich überschreibt. Dies ist die Variablenquelle mit der niedrigsten Priorität in Ansible. 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 nur selten.
  • handlers/main.yml enthält Tasks, die von notify ausgelöst werden. Ein Handler läuft am Ende des Plays genau einmal, unabhängig davon, wie viele Tasks ihn benachrichtigt haben.
  • files/ enthält Dateien, die vom Modul copy unverändert kopiert werden. templates/ enthält Jinja2-Templates, die vom Modul template gerendert werden. Innerhalb einer Role referenzieren Sie beide über den einfachen Dateinamen ohne Pfad, weil Ansible zuerst in den Verzeichnissen der Role sucht.
  • meta/main.yml definiert Role-Abhängigkeiten und die Metadaten, die Ansible Galaxy einliest.

Das Layout ist keine Stilfrage. Ansible sucht in genau diesen Pfaden. Ein Template, das Sie in roles/common/template/ (Singular) ablegen, wird daher schlicht nicht gefunden.

Die gemeinsame 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 angelegt. Es enthält auch Verzeichnisse, die Sie nicht verwenden werden, sowie main.yml-Stub-Dateien, die nur --- enthalten. Löschen Sie die Verzeichnisse und Dateien, die leer bleiben. Ein leeres vars/main.yml ist für Ansible unproblematisch. Es verschleiert jedoch, 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, da sie die öffentliche Schnittstelle der Rolle bilden.

# 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. PyYAML interpretiert ein nicht in Anführungszeichen gesetztes no als booleschen Wert false. 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 eine Änderung tatsächlich die Vorlage ändert. Deshalb tritt dieser Fehler meist erst Wochen später auf.

Die Zeile validate ist die wichtigste Zeile dieser Aufgabe. 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 dem Exit-Code 0 endet. Fügen Sie eine ungültige Direktive in die Vorlage ein und führen Sie den Vorgang erneut aus. Die Aufgabe 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 Ihre Syntax testet. Wenn sshd -t die Hostschlüssel nicht lesen kann, beendet sich der Prozess mit sshd: no hostkeys available -- exiting., und Ansible meldet denselben failed to validate. Lesen Sie daher zuerst die 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 im Rückblick mit failed=0 enden. Übergeben Sie Parameter an der Aufrufstelle in der erweiterten Form. Dadurch 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 ihrer Reihenfolge in der Datei in dieser Reihenfolge aus. Setzen Sie tasks: über 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 beim Parsen 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. Es 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 Stolperfalle. 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 beim Include, wird die Ausführung mit 'common_packages' is undefined beendet, 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 Defaults der Rolle für Werte, die die Rolle selbst verwendet.

Welche Variable gewinnt: Defaults, group_vars, vars, extra vars

Ansible dokumentiert mehr als zwanzig Ebenen für die Variablenpriorität. Vier davon entscheiden fast jede praktische Diskussion. Hier sind sie von schwach nach stark geordnet.

  • roles/<name>/defaults/main.yml steht weit unten in der Priorität. Fast alles, was Sie an anderer Stelle setzen, hat Vorrang. Genau deshalb sind sie der richtige Ort für die anpassbaren Parameter einer Rolle.
  • group_vars/ und host_vars/ liegen in der Mitte. Hier gehören die Vorgaben Ihrer Umgebung hin. Sie überschreiben die Rollen-Defaults sauber.
  • roles/<name>/vars/main.yml steht über host_vars. Ein Wert, den Sie hier festlegen, kann nicht aus dem Inventory überschrieben werden. Verwenden Sie diese Ebene für Werte, die innerhalb der Rolle konsistent bleiben müssen, etwa einen Paketnamen, der mit einem Dienstnamen übereinstimmen muss.
  • Ein Rollenparameter, der beim Aufruf übergeben wird, hat Vorrang vor vars/main.yml. -e in der Befehlszeile hat wiederum Vorrang vor allem anderen, auch vor Rollenparametern.

Sie können dieses Verhalten in etwa einer Minute nachvollziehen. Geben Sie einer kleinen Rolle einen Default und eine Rollenvariable. 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 Rollen-Default, unterlag aber der Rollenvariable. Der zweite Lauf gibt internal=from-cli aus, weil extra vars ganz oben stehen und keine darunterliegende Ebene sie überschreiben kann. Deshalb ist -e für einen einmaligen Lauf geeignet, aber falsch in einem dauerhaft verwendeten Skript: Die Variable steht dann stillschweigend über jeder berücksichtigten Entscheidung in Ihrem Repository.

Die praktische Regel lautet: Wenn ein Wert überschreibbar sein 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 aber ein Versehen.

Die Idempotenz der Rolle nachweisen: Führen Sie sie zweimal aus

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 erforderliche Arbeit bereits erledigt ist. changed=2 bei einem zweiten Durchlauf bedeutet, dass zwei Tasks keinen Unterschied feststellen 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. Beim zweiten Durchlauf 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 zuerst ein sichtbares Ergebnis gibt, nach dem es suchen kann. 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. --diff gibt die exakten Zeilen aus, die ein Template neu schreiben würde. Beachten Sie dabei eine Einschränkung: shell- und command-Tasks werden im Check-Modus übersprungen. Ein sauber wirkender Plan kann daher dennoch ausstehende Arbeiten verbergen.

Eine weitere Spalte in dieser Zusammenfassung erfordert dieselbe Aufmerksamkeit: Ein Host, mit dem Ansible keine Verbindung herstellen konnte, wird unter unreachable statt unter failed gezählt. Auf diesem Host wurde überhaupt kein Task ausgeführt. Legen Sie daher im Voraus fest, ob ein nicht erreichbarer Host den gesamten Lauf stoppen soll, bevor Sie diese Rolle auf mehr als einige wenige Rechner anwenden.

Warum meldet Ansible, dass die Rolle nicht gefunden wurde

Ansible sucht zuerst 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 hilfreicherweise die Pfade aus, die es ausprobiert hat. Legen Sie beide im selben Verzeichnis ab. Sie können den Befehl aus einem übergeordneten Verzeichnis ausführen, weil der Pfad zum Playbook maßgeblich ist:

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

Es gibt eine unauffälligere Variante desselben Problems. 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 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 beides, wenn sich ein Lauf so verhält, als wäre Ihre Konfiguration nicht vorhanden.

Rollen teilen: 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 wird der Stand des Standard-Branches verwendet, der an dem Tag vorliegt, an dem Sie den Befehl ausführen. Dadurch kann ein Deployment, das im letzten Monat funktioniert hat, ohne jede Änderung in Ihrem eigenen Repository fehlschlagen. Verweisen Sie mit 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 damit versioniert und werden geprüft, während Rollen von Drittanbietern reproduzierbar heruntergeladen und auf ein Tag festgelegt werden.

Wo Rollen keine passende Lösung mehr sind

Eine Rolle ist eine Einheit zur Wiederverwendung innerhalb eines Ansible-Laufs. Sie erstellt weder Server noch DNS-Einträge bei Ihrem Anbieter. Wenn Sie sie dafür einsetzen, 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 die Gruppierung und Erreichbarkeit dieser Server wichtiger als die Ablage der Tasks.

Auch die Härtung, die diese common-Rolle installiert, erfordert eigene Entscheidungen. Das obige Drop-in setzt genau zwei Direktiven und keine weiteren. Lesen Sie daher welche SSH-Einstellungen tatsächlich geändert werden sollten 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 Role 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 an zwei Stellen vorgenommen werden. Früher oder später wird sie nur an einer Stelle geändert. Ein einzelnes Playbook mit ungefähr 100 Zeilen, das immer nur auf eine Hostgruppe zielt, profitiert nicht von einer Role. Die zusätzlichen Verzeichnisse erschweren dann die Lesbarkeit.

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

Ja. Ansible führt zuerst pre_tasks aus, danach alles unter roles:, dann tasks: und anschließend post_tasks:. Die Reihenfolge dieser Schlüssel in der Datei spielt dabei keine Rolle. Wenn tasks: vor roles: steht, werden die zugehörigen Aufgaben deshalb nicht zuerst ausgeführt. Muss etwas vor einer Role geschehen, fügen Sie es in pre_tasks: ein.

Warum überschreibt mein group_vars-Wert die Role nicht?

Prüfen Sie, ob die Variable in vars/main.yml der Role statt in defaults/main.yml gesetzt ist. vars/ hat in Ansible eine höhere Priorität als 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 Lauf einmal mit -e name=value aus. Diese Quelle hat Vorrang vor allen anderen.

Warum meldet Ansible, dass die Role nicht gefunden wurde?

Die Suche beginnt neben der Playbook-Datei. Deshalb müssen site.yml und roles/ im selben Verzeichnis liegen. Die Fehlermeldung gibt die geprüften Pfade aus, 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 von ansible.cfg aus auf roles_path angewiesen sind, prüfen Sie mit ansible --version, ob diese Datei geladen wurde. In einem für alle Benutzer beschreibbaren Arbeitsverzeichnis ignoriert Ansible sie.

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

Nein. Eine Role besteht lediglich aus Verzeichnissen mit den erwarteten Namen. mkdir -p roles/common/tasks zusammen mit einem tasks/main.yml ergibt bereits eine funktionierende Role. ansible-galaxy init --init-path roles common erspart Tipparbeit und erstellt das vollständige Grundgerüst einschließlich meta/main.yml sowie einer README-Vorlage. Löschen Sie Verzeichnisse, die leer bleiben. Ein leeres vars/main.yml verschleiert, welche Dateien in der Role tatsächlich etwas ausführen.