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

Ansible Check-Modus und --diff richtig nutzen

Erfahren Sie, was --check und --diff tatsächlich prüfen, welche Module nichts melden und warum der Probelauf vor dem echten Lauf falsch liegen kann.

Was der Ansible-Check-Modus macht

Der Ansible-Check-Modus ist ein Probelauf: ansible-playbook --check verbindet sich mit jedem Host im Play, fragt jedes Modul, ob der aktuelle Zustand bereits dem angeforderten Zustand entspricht, und meldet, was sich ändern würde, ohne etwas zu schreiben. Mit --diff wird zusätzlich der Inhalt der Dateien vor und nach der Änderung ausgegeben, die das Modul bearbeiten würde. Zusammen beantworten diese Optionen die Frage, die Sie sich vor jedem echten Lauf stellen sollten: Was wird auf diesen Servern gleich geändert?

Der Check-Modus simuliert Ihr Playbook nicht. Es gibt kein Modell des Servers. Jedes Modul wird lediglich angewiesen, zu prüfen statt zu schreiben. Ein Modul, das eine schreibgeschützte Prüfung unterstützt, meldet changed und fährt fort. Ein Modul, das diese Prüfung nicht unterstützt, führt nichts aus und meldet nichts. Die Ansible-Dokumentation fasst es in einem Satz zusammen: „Module, die den Check-Modus nicht unterstützen, melden nichts und führen nichts aus.“ Genau daraus entsteht die Lücke, durch die ein Probelauf eine falsche Antwort liefern kann. Deshalb behandelt der größte Teil dieses Leitfadens diese Lücke.

Testlauf ausführen: --check und --diff

ansible-playbook -i inventory.ini site.yml --check --diff --limit web1

-C und -D sind die Kurzformen der beiden Flags. Das --limit ist beabsichtigt. Den Diff eines Hosts können Sie lesen. Bei 20 Hosts scrollen Sie über den Diff hinweg.

Vier Ergebniswörter decken den gesamten Bericht ab.

  • ok: [web1] bedeutet, dass das Modul den Zustand geprüft hat und dieser bereits übereinstimmt. Es würde nichts geändert.
  • changed: [web1] bedeutet, dass das Modul etwas geschrieben hätte. Mit --diff zeigen die darüberstehenden Zeilen, was.
  • skipping: [web1] bedeutet, dass die Aufgabe nicht ausgewertet wurde. Entweder war ein when false, oder das Modul kann nicht im Check-Modus ausgeführt werden.
  • fatal: [web1] bedeutet, dass die Aufgabe während der Prüfung fehlgeschlagen ist. Lesen Sie die Meldung, bevor Sie annehmen, dass das Playbook fehlerhaft ist.

--diff gibt für Dateimodule einen Unified Diff aus. Entfernte Zeilen sind mit - und hinzugefügte Zeilen mit + gekennzeichnet. Die Kopfzeilen beginnen mit --- before und +++ after und nennen den Zielpfad. Module, die keine Dateien schreiben, geben ihre eigene Darstellung von vorher und nachher aus. Daher zeigt ansible.builtin.user die Attribute, die geändert würden, und nicht den Dateiinhalt.

Aktivieren Sie den Diff dauerhaft in ansible.cfg, damit Sie das Flag nicht vergessen:

[diff]
always = true
context = 5

Zwei schnellere Prüfungen sollten Sie vor dem Check-Modus ausführen. ansible-playbook site.yml --syntax-check analysiert das YAML und die Play-Struktur, ohne einen einzigen Host zu kontaktieren. ansible-playbook site.yml --list-tasks gibt die Aufgaben aus, die ausgeführt würden. So erkennen Sie, dass eine Rolle nicht mit dem erwarteten Tag versehen ist. Keine der beiden Prüfungen stellt eine Verbindung her. Beide werden daher sofort abgeschlossen.

Der Check-Modus selbst stellt eine Verbindung her. Er öffnet eine SSH-Verbindung zu jedem Host im Muster und sammelt Facts. Daher schlägt der Testlauf fehl, wenn ein Host nicht erreichbar ist. Das ist bereits ein nützliches Signal. Deshalb ist es wichtig, vorher zu klären, wie ein Playbook mit nicht erreichbaren Hosts umgehen soll, bevor Sie einen Testlauf in CI integrieren.

Warum der Prüfmodus auf einem neuen Server fehlschlägt

Dieses Play ist korrekt. Führen Sie es mit --check auf einem Server aus, auf dem nginx noch nicht vorhanden ist. Dann schlägt ein großer Teil des Plays fehl.

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Write the site config
  ansible.builtin.template:
    src: site.conf.j2
    dest: /etc/nginx/conf.d/site.conf

- name: Start and enable nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Die Aufgabe apt meldet changed. Das ist korrekt: Das Paket fehlt, und bei einer normalen Ausführung würde es installiert. Der Prüfmodus hat es nicht installiert. Anschließend schlägt die Aufgabe template fehl, weil /etc/nginx/conf.d/ auf diesem Host nicht vorhanden ist und zuvor nichts angelegt wurde. Auch die Aufgabe service schlägt fehl, weil es keine nginx-Unit gibt, deren Status abgefragt werden könnte. Keine dieser Fehlermeldungen ist ein Fehler im Playbook. Der Probelauf verfügte nicht über den erforderlichen Ausgangszustand. Genau das bedeutet der Hinweis in der Dokumentation, dass der Prüfmodus für eine Aufgabe keine brauchbare Ausgabe liefern kann, wenn deren Eingabe von einer Änderung einer vorherigen Aufgabe abhängt.

Die genaue Formulierung der Regel lautet daher: Der Prüfmodus ist gegenüber einem Host zuverlässig, auf dem das Playbook bereits den gewünschten Zustand hergestellt hat. Auf einem neuen Host erzeugt er dagegen viele Fehlermeldungen. Ein --check-Lauf, bei dem jede Aufgabe ok meldet, ist eine zutreffende Aussage über einen Host im gewünschten Zustand. Er bedeutet, dass keine Änderung erforderlich wäre. Auf einem vollständig neuen Host zeigt --check dagegen meistens nur, dass der Host neu ist. Wenn Sie Ihr erstes Ansible-Playbook für einen VPS schreiben, müssen Sie damit rechnen, dass der erste Probelauf zahlreiche Fehlermeldungen liefert. Beurteilen Sie das Playbook anhand des zweiten Laufs.

Warum Command- und Shell-Aufgaben im Check-Modus übersprungen werden

ansible.builtin.command und ansible.builtin.shell wissen nicht, was Ihr Command ausführt. Es gibt keine schreibgeschützte Möglichkeit, eine beliebige Binärdatei auszuführen. Deshalb verweigert das Modul die Ausführung im Check-Modus. Das Aufgabenergebnis enthält skipped: true und die Meldung Command would have run if not in check mode. Ihre Ausgabe zeigt skipping: [web1].

Die Moduldokumentation bezeichnet die Unterstützung des Check-Modus als „partiell“. Als Workaround nennt sie creates und removes. Geben Sie der Aufgabe einen Pfad in creates. Dann kann der Check-Modus zumindest den Dateitest auswerten:

- name: Extract the release bundle
  ansible.builtin.command: /usr/bin/tar xf /tmp/app.tar.gz -C /opt/app
  args:
    creates: /opt/app/bin/app

Wenn /opt/app/bin/app bereits vorhanden ist, meldet der Check-Modus Would not run command since '/opt/app/bin/app' exists. Das ist eine tatsächliche Antwort. Fehlt der Pfad, erhalten Sie Command would have run if not in check mode. Auch das ist eine tatsächliche Antwort. Ohne creates bleibt diese Aufgabe in der Probelauf-Ausgabe ohne Ergebnis.

Die Folgewirkung ist problematischer als das fehlende Ergebnis. Eine übersprungene Aufgabe erzeugt weiterhin ein Ergebnis. Dieses Ergebnis ist jedoch ein Skip-Ergebnis und enthält keinen Schlüssel stdout. Die Bedingung der nächsten Aufgabe schlägt deshalb bei der Auswertung fehl. Die Fehlermeldung ähnelt 'dict object' has no attribute 'stdout'. Ihr Playbook funktioniert bei einer echten Ausführung, schlägt aber im Probelauf fehl. Das ist der verwirrendste Fehler dieser gesamten Funktion.

check_mode: false und die eine Stelle, an die es gehört

check_mode: false an einer Task bedeutet: „Diese Task tatsächlich ausführen, auch unter --check.“ Das ist die Lösung für das Problem übersprungener Befehle und nur bei einer Task sicher, die ausschließlich liest.

- name: Read the installed app version
  ansible.builtin.command: /usr/local/bin/app --version
  register: app_version
  check_mode: false
  changed_when: false

Diese Task verhält sich in beiden Modi korrekt. Sie liest eine Version und schreibt nie, changed_when: false verhindert, dass sie eine Änderung meldet, die sie nicht vorgenommen hat, und check_mode: false sorgt dafür, dass app_version.stdout während eines Probelaufs vorhanden ist. Dadurch werden die darauf aufbauenden Bedingungen weiterhin ausgewertet.

Lesen Sie das Schlüsselwort wörtlich, bevor Sie es an anderer Stelle einfügen. Eine Task mit check_mode: false schreibt während ansible-playbook --check auf Ihre Server. Verwenden Sie es bei einer apt-Task oder einer template-Task, damit ein Probelauf übersichtlicher wirkt, ist es kein Probelauf mehr. Wenn sich eine schreibende Task nicht sicher ausführen lässt, schützen Sie sie stattdessen mit einer Bedingung:

- name: Apply the database migration
  ansible.builtin.command: /usr/local/bin/app migrate --apply
  when: not ansible_check_mode

ansible_check_mode ist eine spezielle Variable, die Ansible während eines Prüflaufs auf true setzt. Das Gegenstück zu diesem Schlüsselwort gibt es ebenfalls. check_mode: true bindet eine Task immer an den Prüfmodus, auch während einer echten Ausführung. Dadurch wird sie zu einer Abfrage auf Abweichungen: Registrieren Sie das Ergebnis. Ein changed-Bericht bedeutet, dass der Host nicht mehr dem von der Task geforderten Zustand entspricht.

Warum eine Aufgabe bei jedem Lauf Änderungen meldet

Führen Sie das Playbook zweimal direkt hintereinander aus. Dazwischen darf nichts geschehen. Bei der zweiten Ausführung sollte jede Aufgabe ok melden. Meldet eine Aufgabe weiterhin changed, weist das auf eine von zwei Ursachen hin: Das Modul kann den von ihm verwalteten Zustand nicht erkennen, oder die Eingabe ist nicht stabil. Beides lässt sich beheben. Es handelt sich nicht um überflüssige Meldungen, die unterdrückt werden sollten.

  • command und shell ohne creates, removes oder changed_when melden jedes Mal changed, weil das Modul nicht feststellen kann, ob etwas geschehen ist. Fügen Sie creates hinzu oder setzen Sie changed_when anhand einer Zeichenfolge in der Ausgabe.
  • ansible.builtin.file mit state: touch meldet absichtlich bei jedem Lauf changed, weil das Berühren einer Datei ihre Zeitstempel aktualisiert. Verwenden Sie state: file, wenn Sie lediglich Eigentümer oder Modus setzen wollten.
  • Ein template, dessen gerenderte Ausgabe sich ändert, schreibt die Datei bei jedem Lauf neu. Ein Zeitstempel aus ansible_date_time, ein Aufruf von now() oder ein bei jedem Lauf neu erzeugtes Passwort erzeugen jeweils andere Bytes. Daher meldet das Modul korrekt eine Änderung. Entfernen Sie den veränderlichen Wert aus dem Template.
  • ansible.builtin.user mit password: "{{ pw | password_hash('sha512') }}" ändert sich bei jedem Lauf, weil password_hash bei jedem Aufruf einen zufälligen Salt-Wert auswählt. Dadurch stimmt der resultierende Hash nie mit dem Wert überein, der bereits in /etc/shadow steht. Übergeben Sie einen expliziten Salt-Wert, der aus etwas Stabilen abgeleitet wird.
  • state: latest in einem Paketmodul meldet changed, sobald ein Upgrade verfügbar ist. Diese Meldung ist korrekt. Deshalb liefert state: latest ein Playbook, dessen Ergebnis Sie nicht vorhersagen können. Verwenden Sie state: present und führen Sie Upgrades gezielt durch.
  • ansible.builtin.unarchive mit einer URL ohne creates lädt die Daten erneut herunter und extrahiert sie erneut. Geben Sie einen creates-Pfad an.

--diff ist der schnellste Weg, diese Fälle zu unterscheiden. Wenn eine Aufgabe changed meldet und der Diff unterschiedliche Bytes zeigt, ist Ihre Eingabe nicht stabil. Wenn sie changed meldet und der Diff überhaupt nichts zeigt, kann das Modul die vorgenommene Änderung nicht ausdrücken. Das bedeutet meist, dass es sich um eine command-Aufgabe oder um einen Metadaten-Schreibvorgang wie das Setzen eines Zeitstempels handelt.

Verwenden Sie nicht changed_when: false, um eine Aufgabe mit überflüssigen Meldungen ruhigzustellen. Dadurch wird die Meldung unterdrückt, sodass notify nie ausgelöst wird und der Handler zum Neustart des Dienstes nie ausgeführt wird. Beheben Sie stattdessen die Aufgabe.

Angriffsfläche begrenzen: --limit, --tags und --step

Der Prüfmodus zeigt, was geändert würde. Mit diesen Optionen legen Sie fest, wie viele Rechner die Änderung gleichzeitig ausführen.

--limit begrenzt den Play auf eine Teilmenge des Inventars. Die Option verwendet dieselben Muster wie hosts:. Daher funktionieren sowohl --limit web1 als auch --limit 'webservers:!web3'. Setzen Sie das Muster in Anführungszeichen. Ein nicht in Anführungszeichen gesetztes ! in einer interaktiven bash-Sitzung löst eine History-Erweiterung für das Ausrufezeichen aus. Ihre Shell ändert den Befehl, bevor Ansible ihn überhaupt sieht.

Prüfen Sie das Muster, bevor Sie ihm vertrauen. ansible-playbook site.yml --limit 'webservers:!web3' --list-hosts gibt die passenden Hosts aus und beendet sich, ohne eine Verbindung zu einem dieser Hosts herzustellen. Ein Muster ohne Treffer ist sicher, weil Ansible nicht auf das gesamte Inventar zurückfällt. Ansible gibt eine Warnung aus, dass das Hostmuster keinem Host zugeordnet werden konnte. Anschließend beendet es sich mit einem Fehler, dass die Hosts und --limit keinem Host entsprechen. Nur wenn Sie wissen, wie die Inventardatei diese Gruppen definiert, ist ein Muster von vornherein vorhersehbar.

--tags deploy führt nur die markierten Tasks aus, und --skip-tags packages führt alle anderen Tasks aus. --list-tags zeigt die verfügbaren Tags an. Tags sind besonders nützlich, sobald ein Play so groß wird, dass Sie nicht mehr alles ausführen möchten. Das ist auch einer der Gründe für die Aufteilung eines langen Playbooks in Rollen.

--start-at-task "Write the site config" setzt einen fehlgeschlagenen Lauf ab einem benannten Task fort. Verwenden Sie die Option zur Wiederherstellung, und beachten Sie die Auswirkungen: Alles vor diesem Task wird übersprungen. Dazu gehören auch Tasks, die Facts setzen oder Variablen registrieren, die spätere Tasks lesen.

--step fragt vor jedem Task nach und wartet auf Ihre Antwort: yes, no oder continue. Die Ausführung ist langsam. Für den ersten Lauf mit destruktiven Änderungen ist die Option dennoch geeignet, weil Sie zwischen zwei Tasks abbrechen können und nicht erst nach zwanzig.

Die Änderung seriell ausrollen

Standardmäßig führt Ansible eine Aufgabe auf jedem Host im Play aus, bevor es mit der nächsten Aufgabe beginnt. Das ist schnell, führt aber dazu, dass eine fehlerhafte Aufgabe innerhalb derselben Sekunde die gesamte Serverflotte erreicht. Wenn Sie den Fehler gelesen und Ctrl-C gedrückt haben, ist die Änderung bereits überall ausgerollt.

serial teilt das Play in Batches auf. Das gesamte Play wird zuerst auf dem ersten Batch und anschließend auf dem nächsten Batch ausgeführt.

- name: Roll out the web tier
  hosts: webservers
  serial: [1, 5, "30%"]
  max_fail_percentage: 0
  tasks:
    - name: Deploy the release
      ansible.builtin.include_role:
        name: webapp

Der erste Batch enthält einen Host. Wenn dieser erfolgreich bleibt, umfasst der zweite Batch fünf Hosts. Jeder darauffolgende Batch enthält 30 Prozent der Hosts des Plays. max_fail_percentage: 0 beendet das Play, sobald ein Host in einem Batch fehlschlägt. Eine fehlerhafte Version stoppt dadurch auf einem einzigen Rechner. any_errors_fatal: true ist die weniger differenzierte Variante und beendet das Play für alle Hosts, sobald der erste Host fehlschlägt.

Die Ausführung auf einem Host zuerst ist keine übertriebene Vorsicht. Der Grund dafür ist konkret: Inventory-Gruppen verändern sich im Laufe der Zeit. Ein Server, der sechs Monate nach den anderen hinzugefügt wurde, verwendet möglicherweise eine andere Distributionsversion, enthält einen von jemandem manuell installierten Dienst oder hat eine andere Festplattenaufteilung. Das Playbook ist für die Gruppe korrekt, aber für genau diesen Host ungeeignet. Ein Probelauf auf einem bereits konvergierten Host würde diesen Fehler nicht zeigen. Eine Flotte von Linux-Servern verwalten bedeutet zu einem großen Teil, den abweichenden Host zu finden, bevor die Änderung ihn erreicht.

Reihenfolge der Ausführung

  1. ansible-playbook site.yml --syntax-check erkennt YAML- und Strukturfehler vollständig ohne Netzwerkzugriff.
  2. ansible-playbook site.yml --limit web1 --list-hosts bestätigt, dass Ihr Muster wie erwartet übereinstimmt.
  3. ansible-playbook site.yml --limit web1 --check --diff ist der Probelauf. Lesen Sie den Diff.
  4. ansible-playbook site.yml --limit web1 --diff wendet die Änderung auf diesem einen Host an.
  5. Führen Sie Schritt 4 erneut aus. Alles sollte ok melden. Alles, was weiterhin changed ist, muss behoben werden, bevor es auf den restlichen Bestand angewendet wird.
  6. ansible-playbook site.yml --check --diff über das gesamte Inventar liefert nun eine aussagekräftige Antwort, weil die konvergierten Hosts keine Änderungen mehr melden und die verbleibende Abweichung der tatsächlichen Differenz entspricht.

Ein Hinweis zu Schritt 3: --diff gibt Dateiinhalte auf Ihrem Terminal und im Log Ihres CI-Jobs aus. Wenn ein Template ein Datenbankpasswort rendert, wird dieses Passwort daher in das Log geschrieben. Setzen Sie diff: false für diese Aufgabe, um die Ausgabe zu unterdrücken, oder verwenden Sie no_log: true, um das gesamte Ergebnis auszublenden. Speichern Sie den Wert selbst in einer verschlüsselten Ansible-Vault-Datei und nicht im Repository.

FAQ

Ändert ansible-playbook --check etwas auf dem Server?

Nein, mit einer Ausnahme, die Sie steuern können. Im Check-Modus wird jedes Modul aufgefordert, Änderungen nur zu melden, statt sie zu schreiben. Module, die dies nicht unterstützen, melden nichts und führen nichts aus. Die Ausnahme ist das Aufgaben-Schlüsselwort check_mode: false. Es erzwingt, dass diese einzelne Aufgabe auch während eines --check-Laufs tatsächlich ausgeführt wird. Suchen Sie in Ihren Playbooks und Rollen nach check_mode: false, bevor Sie einem Probelauf vertrauen. Prüfen Sie dabei, ob jeder Treffer zu einer Aufgabe gehört, die ausschließlich den Zustand liest.

Was ist der Unterschied zwischen --check und --diff?

--check legt fest, ob etwas tatsächlich ausgeführt wird. --diff legt fest, wie viele Details Sie sehen. --check allein zeigt, dass sich eine Datei ändern würde. --diff allein wendet die Änderung an und zeigt die geänderten Zeilen. Verwenden Sie beide Optionen zusammen für einen lesbaren Probelauf. Aktivieren Sie --diff auch bei echten Läufen, indem Sie unter [diff] in ansible.cfg den Wert always = true setzen.

Warum meldet meine Ansible-Aufgabe bei jedem Lauf changed?

Weil das Modul den verwalteten Zustand nicht erkennen kann oder weil der übergebene Wert bei jedem Lauf anders ist. command und shell melden immer changed, sofern Sie nicht creates oder changed_when hinzufügen. file in Verbindung mit state: touch ändert sich absichtlich. Eine Vorlage, die einen Zeitstempel oder ein neu generiertes Kennwort rendert, erzeugt bei jedem Lauf andere Bytes. Daher wird die Datei tatsächlich neu geschrieben. Führen Sie das Playbook zweimal direkt hintereinander aus. Alles, was beim zweiten Durchlauf weiterhin changed ist, gehört zu der Aufgabe, die Sie korrigieren müssen.

Warum werden meine command- und shell-Aufgaben während eines Probelaufs übersprungen?

Weil es keine schreibgeschützte Möglichkeit gibt, einen beliebigen Befehl auszuführen. Im Check-Modus setzt das Modul command den Wert skipped: true mit der Meldung Command would have run if not in check mode. Fügen Sie creates oder removes hinzu, damit der Check-Modus stattdessen die Dateiprüfung auswerten kann. Setzen Sie bei einer Aufgabe, die ausschließlich den Zustand liest, check_mode: false zusammen mit changed_when: false. Dadurch bleibt das registrierte Ergebnis auch während des Probelaufs vorhanden, und die darauf basierenden Bedingungen funktionieren weiterhin.

Warum schlägt der Check-Modus auf einem neuen Server fehl, funktioniert aber auf einem vorhandenen?

Weil der Check-Modus den Zustand nicht erstellt, von dem spätere Aufgaben abhängen. Ein Probelauf auf einem Host ohne nginx meldet die Installation als changed. Anschließend schlägt die Aufgabe fehl, die in /etc/nginx/conf.d/ schreibt, weil dieses Verzeichnis nie erstellt wurde. Das ist erwartetes Verhalten. Der Check-Modus erkennt Abweichungen auf Hosts, die das Playbook bereits in den gewünschten Zustand versetzt hat. Einen ersten Lauf kann er nicht validieren. Wenden Sie das Playbook auf einem neuen Host zunächst auf einer Maschine an. Lesen Sie anschließend den zweiten Lauf aus.

#ansible#check-mode#idempotency#automation#safety