Ansible Templates und Handler: Beispiel mit nginx
Erstellen Sie eine nginx-Konfiguration aus einer Jinja2-Vorlage, lösen Sie den Handler nur bei Änderungen aus und prüfen Sie Idempotenz durch zweimaliges Ausführen.
Ansible-Templates und Handler für Ihr erstes Playbook
Ansible-Templates und Handler sind die beiden Bausteine, die aus einem statischen Playbook ein nützliches Playbook machen. Ein Template erzeugt aus Ihren Variablen eine Konfigurationsdatei, sodass eine Datei für jeden Host ausreicht. Ein Handler wird nur ausgeführt, wenn eine Task tatsächlich etwas geändert hat. Dadurch wird der Dienst nur bei einer echten Änderung der Konfiguration neu geladen und bleibt ansonsten unverändert.
Dieser Leitfaden setzt genau dort an, wo Ihr erstes Ansible-Playbook auf einem VPS endet. Sie verfügen bereits über ein Play, das ein Paket installiert und einen Dienst startet. Alle folgenden Schritte werden auf einem Rechner ausgeführt, weil das Play localhost über eine lokale Verbindung als Ziel verwendet. Sie benötigen keinen zweiten Server. Dasselbe Play läuft ohne Änderungen an den Tasks gegen reale Inventory-Hosts. Im letzten Abschnitt wird erläutert, was sich dabei ändert.
Arbeitsverzeichnis einrichten
sudo apt update
sudo apt install -y ansible nginx
ansible --version
mkdir -p ~/ansible-templates/templates
cd ~/ansible-templatesnginx wird hier nur verwendet, weil es ein echter Dienst mit einer Konfigurationsdatei und einem Reload-Befehl ist. Damit sind alle Voraussetzungen für das Beispiel erfüllt. ansible --version gibt die Version von ansible-core und den verwendeten Python-Interpreter aus. Notieren Sie sich beide Angaben. Das folgende Playbook verwendet vollständig qualifizierte Modulnamen wie ansible.builtin.template. Dafür benötigen Sie Ansible 2.10 oder neuer. Jedes aktuelle Distributionspaket ist deutlich neuer.
Erstellen Sie inventory.ini:
[local]
localhost ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}"ansible_connection=local weist Ansible an, jede Aufgabe als lokalen Prozess auszuführen, statt eine SSH-Sitzung zum eigenen System zu öffnen. Die zweite Einstellung ist nicht überflüssig. Wenn Sie localhost in eine Inventory-Datei schreiben, wird der Eintrag zu einem gewöhnlichen Host. Dabei geht der Interpreter verloren, den Ansible dem impliziten localhost automatisch zur Verfügung stellt. Ansible verwendet dann die Interpreter-Erkennung und kann einen anderen Python-Interpreter auswählen als den, mit dem das Play ausgeführt wird. ansible_playbook_python ist der Interpreter, mit dem ansible-playbook derzeit ausgeführt wird. Dadurch bleiben beide synchron.
Erstellen Sie ansible.cfg:
[defaults]
inventory = inventory.iniOhne diese Datei müssen Sie bei jedem Befehl -i inventory.ini angeben. Ohne Inventory gibt Ansible [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' aus. Ein Play mit hosts: all findet dann keine passenden Hosts. Beachten Sie außerdem ansible.cfg: Ansible ignoriert diese Datei, wenn sie sich in einem für alle Benutzer beschreibbaren Verzeichnis befindet. Bewahren Sie das Projekt daher unter Ihrem Home-Verzeichnis auf. Eine Inventory-Datei enthält mehr als nur eine Liste von Hosts, und dies ist die kleinste Datei, die ihren Zweck erfüllt.
template oder copy: Wann welches Modul verwendet wird
ansible.builtin.copy überträgt eine Datei unverändert. ansible.builtin.template verarbeitet die Datei zunächst mit Jinja2 und überträgt anschließend das Ergebnis. Die Modulbeschreibung bezeichnet template als „virtuelles Modul, das vollständig als Action-Plugin implementiert ist und auf dem Controller ausgeführt wird“. Daraus folgt ein wichtiger Punkt: Das Rendering erfolgt auf dem Rechner, auf dem Sie ansible-playbook eingegeben haben. Der Zielhost sieht Ihre Variablen nie und benötigt keine installierte Jinja2-Umgebung.
Verwenden Sie copy, wenn die Datei auf allen Hosts identisch ist. Verwenden Sie template, sobald sich ein Wert je Host unterscheidet oder Sie eine {% for %}-Schleife beziehungsweise einen {% if %}-Block benötigen. copy verfügt zwar über einen content:-Parameter, und Variablen darin werden wie alle anderen Task-Argumente ersetzt. Dort gibt es jedoch keine Schleifen und keine Bedingungen. Alles mit einer solchen Struktur gehört in ein Template. Beide Module akzeptieren dieselben Dateioptionen, weil beide dieselben Dokumentationsfragmente einbinden. Daher verhalten sich owner, group, mode, backup und validate in beiden Modulen gleich.
Vorlage schreiben: eine Variable, eine Schleife
Speichern Sie dies als templates/app.conf.j2:
# {{ ansible_managed }}
upstream {{ app_name }}_backend {
{% for backend in app_backends %}
server {{ backend.host }}:{{ backend.port }} weight={{ backend.weight }};
{% endfor %}
}
server {
listen {{ app_listen_port }};
server_name {{ app_server_name }};
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
}
}Hier übernehmen zwei Arten von Jinja2-Tags die Aufgabe. {{ ... }} ist ein Ausdruck und gibt seinen Wert aus. {% ... %} ist eine Anweisung und gibt selbst nichts aus. app_backends ist eine Liste aus Dictionaries. Daher liest backend.host aus jedem Eintrag einen Schlüssel aus, und die Schleife schreibt für jeden Eintrag eine server-Zeile, unabhängig davon, wie viele Sie definieren.
Ein Detail zum Leerraum sorgt häufig für Überraschungen, wenn Sie Jinja2 aus anderen Umgebungen kennen. Ansible setzt trim_blocks standardmäßig auf yes. Jinja2 selbst tut das nicht. Dadurch wird der Zeilenumbruch direkt nach einem {% ... %}-Tag entfernt, und die Schleife hinterlässt keine Leerzeile. Ansible lässt lstrip_blocks auf no, sodass Leerzeichen vor einem {%-Tag erhalten bleiben und in der gerenderten Datei erscheinen. Wenn Ihre Ausgabe unerwünschte Einrückungen enthält, setzen Sie lstrip_blocks: true in der Template-Task.
{{ ansible_managed }} wird standardmäßig als der wörtliche Text Ansible managed gerendert. Belassen Sie diese Einstellung. Häufig wird ansible_managed in ansible.cfg so angepasst, dass ein Datum enthalten ist. Dann unterscheidet sich die gerenderte Datei bei jedem Lauf, die Task meldet bei jedem Lauf eine Änderung, und der Dienst wird bei jedem Lauf neu geladen. Diese eine Einstellung zerstört die Eigenschaft, um die es im restlichen Leitfaden geht. Die Endung .j2 ist eine Konvention, und Ansible prüft sie nicht.
Das Playbook
Speichern Sie dies als site.yml:
- name: Render an nginx site from a template
hosts: local
become: true
vars:
app_name: learn
app_listen_port: 8080
app_server_name: learn.example.com
app_backends:
- host: 127.0.0.1
port: 9001
weight: 3
- host: 127.0.0.1
port: 9002
weight: 1
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Render the site configuration
ansible.builtin.template:
src: templates/app.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}.conf"
owner: root
group: root
mode: '0644'
backup: true
notify: nginx config changed
- name: Make sure nginx is enabled and running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test the nginx configuration
ansible.builtin.command:
cmd: /usr/sbin/nginx -t
changed_when: false
listen: nginx config changed
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: nginx config changedmode: '0644' ist absichtlich in Anführungszeichen gesetzt. Die Dokumentation zu den Dateioptionen weist darauf hin, dass Oktalzahlen in Anführungszeichen gesetzt werden sollen, „damit Ansible einen String erhält und die Umwandlung vom String in eine Zahl selbst durchführen kann“. Ohne Anführungszeichen liest der YAML-Parser 0644 als einfache Zahl. Dadurch können Berechtigungen entstehen, die Sie nicht angefordert haben.
notify: nginx config changed bezeichnet ein Thema, keinen Handler. Beide Handler enthalten listen: nginx config changed, daher erreicht eine Benachrichtigung beide Handler. Wenn Sie später einen dritten Handler mit derselben Zeile listen hinzufügen, muss die Template-Aufgabe nicht geändert werden. cache_valid_time: 3600 verhindert, dass ein zweiter Lauf innerhalb derselben Stunde erneut auf die Paketspiegel zugreift.
Einmal ausführen und anschließend die Ausgabe lesen
ansible-playbook site.ymlWenn sudo nach einem Passwort fragt, fügen Sie -K hinzu. Ansible fordert das Passwort dann interaktiv an.
Lesen Sie zuerst die Zeilen der einzelnen Aufgaben und anschließend den PLAY RECAP am Ende. Jede Aufgabe gibt changed: aus, wenn Ansible eine Änderung vornehmen musste, oder ok:, wenn sich der Host bereits im gewünschten Zustand befand. Die Zusammenfassung zählt diese Werte für jeden Host. Erst nachdem alle Aufgaben des Plays abgeschlossen sind, erhalten Sie RUNNING HANDLER [Test the nginx configuration], gefolgt von RUNNING HANDLER [Reload nginx].
Prüfen Sie nun den Rechner selbst, statt der Ausgabe zu vertrauen:
sudo cat /etc/nginx/conf.d/learn.conf
sudo /usr/sbin/nginx -t
curl -sI http://127.0.0.1:8080/nginx -t gibt nginx: configuration file /etc/nginx/nginx.conf test is successful aus, wenn die zusammengeführte Konfiguration korrekt geparst wird. curl gibt eine Statuszeile von nginx zurück. 502 Bad Gateway ist hier die richtige Antwort, weil der Server-Block aktiv ist und an den Ports 9001 und 9002 nichts lauscht. sudo tail /var/log/nginx/error.log nennt den Grund in einfachen Worten: connect() failed (111: Connection refused) while connecting to upstream.
Führen Sie es ein zweites Mal aus, um die Idempotenz zu beweisen
ansible-playbook site.ymlDies ist der entscheidende Lauf. Vergleichen Sie seine Ausgabe daher Zeile für Zeile mit der ersten Ausgabe. Die Template-Aufgabe sollte jetzt ok: ausgeben, wo zuvor changed: ausgegeben wurde. Außerdem darf keiner der beiden Handler an irgendeiner Stelle in der Ausgabe erscheinen.
Der Mechanismus ist einfach und wichtig, weil Sie ihn als Grundlage für die Fehlersuche benötigen. template rendert die Datei auf dem Controller und vergleicht die Prüfsumme des Ergebnisses mit der Prüfsumme der Datei, die sich bereits unter dest befindet. Stimmen Inhalt, Eigentümer und Modus überein, gibt es nichts zu tun. Die Aufgabe meldet daher ok, notify wird daher nie ausgelöst und der Handler wird nie ausgeführt. Handler werden bei changed und bei nichts anderem ausgeführt.
Beweisen Sie auch die umgekehrte Richtung. Ändern Sie in vars weight: 3 in weight: 1, führen Sie das Play erneut aus, und die Template-Aufgabe meldet changed. Beide Handler werden ausgeführt, und sudo cat /etc/nginx/conf.d/learn.conf zeigt den neuen Wert.
Wenn ein zweiter identischer Lauf weiterhin eine Änderung meldet, ist das Rendering nicht stabil. Suchen Sie zunächst nach einem zeitabhängigen Wert in der Ausgabe. Das ist die häufigste Ursache, und ein angepasstes ansible_managed ist dafür der übliche Auslöser. Prüfen Sie anschließend, ob mode und owner in der Aufgabe mit dem tatsächlichen Zustand auf dem Datenträger übereinstimmen. Eine Abweichung dort gilt auch dann als Änderung, wenn die Bytes identisch sind.
Änderungen vor der Ausführung prüfen
ansible-playbook site.yml --check --diff--check führt das Play aus, ohne den Host zu ändern. --diff gibt aus, welche Änderungen jede Task vornehmen würde. Bei template ist dies ein zeilenweiser Unterschied zwischen der generierten Ausgabe und der Datei auf dem Datenträger. Zusammen beantworten sie die Frage „Was würde dieser Lauf tun?“, ohne Änderungen vorzunehmen. Der Check-Modus hat eigene Fallstricke, insbesondere bei Tasks, deren Ergebnis von einer vorherigen Task abhängt, die der Check-Modus tatsächlich nicht ausgeführt hat.
Warum Handler bis zum Ende des Plays warten
Die Dokumentation zu Handlern ist eindeutig: „Standardmäßig werden Handler ausgeführt, nachdem alle Tasks eines bestimmten Plays abgeschlossen wurden. Benachrichtigte Handler werden automatisch nach jedem der folgenden Abschnitte in der angegebenen Reihenfolge ausgeführt: pre_tasks, roles/tasks und post_tasks.“
Der Grund ist die Bündelung. Ein Play, das vier Konfigurationsdateien für einen Dienst erzeugt, sollte diesen Dienst am Ende einmal neu starten, wenn alle vier Dateien vorhanden sind. Ein Neustart nach jeder Datei würde den Dienst viermal neu starten. Bei drei dieser Neustarts würde er eine unvollständige Konfiguration laden. Auf derselben Seite wird die Garantie eindeutig formuliert: „Wird derselbe Handler mehrfach benachrichtigt, wird er unabhängig von der Anzahl der benachrichtigenden Tasks nur einmal ausgeführt.“
Auch die Reihenfolge ist festgelegt: „Handler werden in der Reihenfolge ausgeführt, in der sie im Abschnitt handlers definiert sind, nicht in der Reihenfolge, in der sie in der Anweisung notify aufgeführt sind.“ Deshalb steht Test the nginx configuration im Playbook über Reload nginx. Der Test wird zuerst ausgeführt, weil er zuerst geschrieben wurde. Die Zeile notify hat darauf keinen Einfluss.
Handler früh ausführen und nach einem Fehler ausführen
Manchmal benötigt eine spätere Aufgabe im selben Play den Dienst bereits mit der neuen Konfiguration. Leeren Sie die Liste der benachrichtigten Handler an dieser Stelle mit dem Modul meta. In der Dokumentation wird dies als Ausführung „aller Handler-Aufgaben beschrieben, die bis dahin benachrichtigt wurden“.
- name: Run the notified handlers now instead of at the end of the play
ansible.builtin.meta: flush_handlers
- name: Wait for the new listener to accept connections
ansible.builtin.wait_for:
host: 127.0.0.1
port: 8080
timeout: 10Entfernen Sie die Zeile meta, wird die Aufgabe wait_for ausgeführt, während nginx weiterhin die alte Konfiguration verwendet. Beim ersten Durchlauf lauscht noch kein Dienst auf Port 8080. Daher wartet die Aufgabe die gesamten zehn Sekunden und schlägt anschließend fehl.
Der zweite Fall betrifft Fehler. „Wenn eine Aufgabe einen Handler benachrichtigt, aber später im Play eine andere Aufgabe fehlschlägt, wird der Handler standardmäßig auf diesem Host nicht ausgeführt. Dadurch kann der Host in einem unerwarteten Zustand bleiben.“ Ein Play, das eine Konfiguration erstellt und anschließend an einer unabhängigen Aufgabe scheitert, hinterlässt daher die neue Datei auf dem Datenträger, während der laufende Dienst weiterhin die alte Konfiguration verwendet. Überschreiben Sie dieses Verhalten mit --force-handlers in der Befehlszeile oder mit force_handlers: true im Play. Derselbe Schalter ist unter [defaults] in ansible.cfg als force_handlers = True sowie als Umgebungsvariable ANSIBLE_FORCE_HANDLERS verfügbar. Der Standardwert ist False.
Handler-Namen kollidieren, und der Verlierer bleibt still
Die Dokumentation formuliert die Regel so: „Jeder Handler sollte einen global eindeutigen Namen haben. Wenn mehrere Handler mit demselben Namen definiert sind, kann nur der zuletzt in das Play geladene Handler benachrichtigt und ausgeführt werden.“ Handler, die innerhalb einer Rolle definiert sind, sind ebenfalls nicht auf diese Rolle beschränkt. Sie werden in eine globale Handler-Liste für das gesamte Play eingefügt. Wenn zwei Rollen jeweils Restart nginx definieren, bleibt ein Name übrig, der genau einen dieser Handler auflöst. Welche Rolle gewinnt, hängt von der Ladereihenfolge ab, nicht von der Rolle, aus der Sie den Handler benachrichtigt haben.
Testen Sie diese Regel, bevor Sie sich darauf verlassen. Speichern Sie Folgendes als handlers-dup.yml:
- name: Two handlers, one name
hosts: local
gather_facts: false
tasks:
- name: Notify the duplicated name
ansible.builtin.command:
cmd: /bin/true
changed_when: true
notify: Duplicated handler
handlers:
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-first
state: touch
mode: '0644'
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-second
state: touch
mode: '0644'rm -f /tmp/dup-first /tmp/dup-second
ansible-playbook handlers-dup.yml
ls -l /tmp/dup-first /tmp/dup-secondDas Play wird erfolgreich ausgeführt, RUNNING HANDLER [Duplicated handler] erscheint einmal, und ls gibt für den einen Handler eine Zeile mit /tmp/dup-first und für den anderen eine Zeile mit ls: cannot access '/tmp/dup-second': No such file or directory aus. Ausgeführt wird der zuerst geschriebene Handler, nicht der zuletzt geladene. Das widerspricht der Aussage in der Dokumentation.
Der Unterschied ist wichtig, weil sich die dokumentierte Regel auf Handler-Blöcke und nicht auf einzelne Zeilen in einer Datei bezieht. Handler aus getrennten Quellen, also zunächst aus einer Rolle und anschließend aus einer anderen, gehören zu getrennten Blöcken. Ein späterer Block überschreibt einen früheren. Eine normale handlers:-Liste in einem Play ist dagegen ein einzelner Block. Die Suche innerhalb eines Blocks läuft von oben nach unten und endet beim ersten passenden Namen. Innerhalb einer Datei wird daher die erste Definition verwendet, während die zweite unerreichbar bleibt. Zwischen Rollen funktioniert die Überschreibung dagegen wie in der Dokumentation beschrieben. In beiden Fällen können Sie niemals beide Handler erreichen. Auf keine der beiden Verhaltensweisen sollten Sie sich stützen.
Es gibt zwei saubere Lösungen. Geben Sie jedem Handler-Namen ein rollenspezifisches Präfix. Alternativ können Sie die qualifizierte Form role_name : handler_name benachrichtigen. Die Dokumentation nennt dies als Möglichkeit, „sicherzustellen, dass ein Handler aus einer Rolle benachrichtigt wird und nicht ein Handler außerhalb der Rolle mit demselben Namen“. Die Leerzeichen um den Doppelpunkt gehören zu dieser Syntax. Das wird zu einem praktischen Problem, sobald Sie Rollen einbinden, die Sie nicht selbst geschrieben haben.
Auf derselben Seite wird außerdem folgende Regel genannt: „Vermeiden Sie Variablen im Namen des Handlers. Da Handler-Namen frühzeitig als Templates verarbeitet werden, verfügt Ansible möglicherweise noch nicht über einen Wert für einen Handler-Namen wie diesen.“ Ein Handler namens Restart {{ service_name }} schlägt das gesamte Play fehl, wenn diese Variable beim Verarbeiten des Namens nicht definiert ist. Wenn Sie Handler-Namen als feste Zeichenfolgen definieren und sie mit listen gruppieren, vermeiden Sie dieses Problem.
Validierung: Die Installation einer fehlerhaften gerenderten Datei verhindern
validate führt einen Befehl für die gerenderte Datei aus, bevor Ansible sie an den Zielort verschiebt. In der Dokumentation heißt es: „Der Validierungsbefehl, der vor dem Kopieren der aktualisierten Datei an den endgültigen Zielort ausgeführt wird. Für die Validierung wird ein temporärer Dateipfad verwendet. Dieser wird über %s übergeben, das wie in den folgenden Beispielen enthalten sein muss. Der Befehl wird außerdem sicher übergeben. Daher funktionieren Shell-Funktionen wie Expansionen und Pipes nicht.“
Daraus ergeben sich zwei direkte Regeln. %s ist obligatorisch. Eine Validate-Zeichenfolge ohne diesen Platzhalter schlägt mit validate must contain %s fehl. Außerdem wird keine Shell verwendet. Pipes, Umleitungen, Globbing und && funktionieren daher nicht. Es ist genau ein Befehl mit genau einem Dateiargument zulässig.
Die offiziellen Modulbeispiele zeigen die beiden Fälle, in denen dies problemlos funktioniert:
- name: Copy a new sudoers file into place, after passing validation with visudo
ansible.builtin.template:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -cf %s
- name: Update sshd configuration safely, avoid locking yourself out
ansible.builtin.template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
backup: yesBeide funktionieren, weil jeder Prüfer eine einzelne Datei als Eingabe erwartet und sie eigenständig bewertet. visudo -cf liest eine sudoers-Datei. sshd -t -f liest eine vollständige sshd_config.
Warum validate die nginx-Datei in diesem Leitfaden nicht prüfen kann
Fügen Sie validate: /usr/sbin/nginx -t -c %s zur obigen Template-Aufgabe hinzu. Die Aufgabe schlägt fehl. Die Meldung nennt die Ursache:
nginx: [emerg] "upstream" directive is not allowed here in <ansible temporary path>:2nginx -t -c erwartet eine vollständige Konfiguration, die auf der obersten Ebene mit events- und http-Blöcken beginnt. Die Datei, die dieses Play rendert, ist ein Fragment. Sie wird durch include /etc/nginx/conf.d/*.conf; innerhalb von /etc/nginx/nginx.conf in den http-Block eingefügt. Für sich allein und außerhalb dieses Kontexts ist upstream tatsächlich eine Direktive an der falschen Stelle. Deshalb weist nginx eine Datei zurück, die an ihrem tatsächlichen Einbindungsort vollständig korrekt ist. Dem Prüfer wurde ein Fragment übergeben, das er wie eine vollständige Konfiguration behandeln soll.
Die praktikable Lösung ist bereits im Playbook enthalten. Installieren Sie das Fragment und prüfen Sie anschließend die zusammengesetzte Konfiguration in einem Handler, der vor dem Reload-Handler definiert ist. Da Handler in der Reihenfolge ihrer Definition ausgeführt werden, sieht nginx -t die echte /etc/nginx/nginx.conf mit Ihrem eingebundenen Fragment. Ein Fehler an dieser Stelle schlägt das Play fehl, bevor systemctl reload überhaupt aufgerufen wird. Beachten Sie die Konsequenz: Wenn diese Prüfung fehlschlägt, liegt die fehlerhafte Datei bereits auf der Festplatte. nginx stellt weiterhin die zuletzt geladene Konfiguration bereit, bis der Dienst neu gestartet wird.
Dafür ist backup: true erforderlich. Vor dem Überschreiben erstellt es neben der ursprünglichen Datei eine Kopie der vorherigen Datei. Diese erhält den Namen basename.PID.YYYY-MM-DD@HH:MM:SS~. Im Verzeichnis befinden sich dann beispielsweise Einträge wie learn.conf.4127.2026-08-20@11:42:09~. Führen Sie sudo ls -l /etc/nginx/conf.d/ nach einer Änderung aus. Dann finden Sie eine solche Datei vor.
Dieses Detail bei der Benennung ist wichtiger, als es zunächst scheint. In /etc/nginx/conf.d/ ist die Sicherung unproblematisch, weil die Hauptkonfiguration nur conf.d/*.conf einbindet und der Name der Sicherung mit einer Tilde endet. In einem Verzeichnis, das mit einem einfachen * eingebunden wird, ist sie jedoch nicht unproblematisch. Unter Debian und Ubuntu bindet /etc/nginx/nginx.conf /etc/nginx/sites-enabled/* genau auf diese Weise ein. Wenn Sie mit backup: true nach sites-enabled templaten, lädt nginx die Sicherung als zweiten aktiven Serverblock. Deshalb schreibt dieses Play stattdessen nach conf.d.
Ausführung desselben Plays gegen Hosts aus dem echten Inventar
Ändern Sie hosts: local in den von Ihnen verwendeten Gruppennamen. Ansonsten bleibt im Play alles unverändert. Das Template wird einmal pro Host gerendert. Daher können app_listen_port und app_backends aus group_vars und host_vars stammen, während die Template-Datei selbst nur einmal vorhanden ist. Genau darin liegt der Vorteil, Werte in Variablen statt in der Datei abzulegen.
Zwei Dinge ändern sich. become: true benötigt jetzt auf jedem Zielsystem ein sudo-Passwort, sofern dort kein passwortloses sudo eingerichtet ist. Fügen Sie daher -K hinzu. Ein Secret im Template, beispielsweise ein Datenbankpasswort oder ein API-Token, darf außerdem nicht als Klartext in vars: in einer Datei stehen, die Sie in das Repository übernehmen. Verschlüsseln Sie diese Werte mit Ansible Vault und referenzieren Sie sie weiterhin genau wie bisher über ihren Namen. Für das Template spielt es keine Rolle, woher eine Variable stammt.
Wenn das Play mehr als einen Dienst umfasst, gibt es für vars:, templates/ und handlers: bereits jeweils einen standardmäßigen Ablageort. Sie dorthin zu verschieben, ist der eigentliche Zweck von der Aufteilung in ein Playbook und eine Rolle.
FAQ
Warum wurde mein Ansible-Handler nicht ausgeführt?
Fast immer, weil die Aufgabe, die ihn benachrichtigt, ok statt changed gemeldet hat. Handler werden bei einer Änderung und sonst nicht ausgelöst. Eine Template-Aufgabe, deren gerenderte Ausgabe bereits der Datei auf dem Datenträger entspricht, benachrichtigt daher keinen Handler. Prüfen Sie anschließend vier Punkte. Die Zeichenfolge in notify muss exakt mit dem Handler name oder einem listen-Topic übereinstimmen, einschließlich Groß- und Kleinschreibung sowie Leerzeichen. Eine spätere Aufgabe, die auf diesem Host fehlschlägt, unterdrückt benachrichtigte Handler, sofern Sie nicht --force-handlers übergeben. Ein Handler, der in einem anderen Play definiert ist, ist in diesem Play nicht sichtbar. Eine benachrichtigende Aufgabe, die aufgrund einer when-Bedingung übersprungen wird, benachrichtigt überhaupt keinen Handler.
Warum meldet mein Playbook bei jedem Lauf eine Änderung?
Der gerenderte Text ist zwischen den Läufen nicht stabil. Die häufigste Ursache ist ein Zeitstempel in der Ausgabe. Eine angepasste ansible_managed-Zeichenfolge, die ein Datum enthält, bewirkt genau das. Prüfen Sie als Nächstes mode und owner in der Aufgabe. Wenn diese Werte nicht mit der bereits auf dem Datenträger vorhandenen Datei übereinstimmen, korrigiert Ansible sie und meldet eine Änderung, obwohl der Inhalt identisch ist. Führen Sie ansible-playbook site.yml --check --diff aus, um festzustellen, welcher der beiden Werte die Ursache ist. --diff zeigt die Änderung an, die die Aufgabe vornehmen soll.
Was ist in Ansible der Unterschied zwischen template und copy?
ansible.builtin.copy überträgt eine Datei unverändert. ansible.builtin.template rendert die Datei zuerst auf dem Controller mit Jinja2 und überträgt anschließend das Ergebnis. Variablen und Schleifen werden dadurch aufgelöst, bevor die Datei den Zielhost erreicht. Verwenden Sie copy für eine Datei, die überall byteidentisch sein soll. Verwenden Sie template für Dateien, deren Inhalt vom Host abhängt. Beide Module verwenden dieselben Dateioptionen. Daher funktionieren mode, owner, backup und validate in beiden Modulen gleich.
Wie kann ich einen Handler mitten in einem Play ausführen?
Fügen Sie an der gewünschten Stelle ansible.builtin.meta: flush_handlers als Aufgabe ein. Dadurch werden alle bis dahin benachrichtigten Handler ausgelöst. Anschließend wird das Play normal fortgesetzt. Verwenden Sie dies, wenn eine spätere Aufgabe im selben Play voraussetzt, dass der Dienst bereits mit der neuen Konfiguration läuft, beispielsweise ein wait_for auf einem Port, der erst nach dem Reload verfügbar ist. Dies ist die vorgesehene Methode, um einen Handler vor dem Ende des Plays auszuführen.
Kann ich validate mit einem nginx-Konfigurationsfragment verwenden?
Nicht mit nginx -t -c %s. Dieser Befehl erwartet eine vollständige Konfiguration, die mit den übergeordneten Blöcken events und http beginnt. Daher weist er ein conf.d-Fragment mit einer Meldung wie "upstream" directive is not allowed here zurück. Das Fragment ist innerhalb des http-Blocks gültig, aber nicht als eigenständige Konfiguration. Installieren Sie die Datei und führen Sie anschließend nginx -t in einem Handler gegen die zusammengesetzte Konfiguration aus. Dieser Handler muss vor dem Reload-Handler definiert sein. Handler werden in der Reihenfolge ihrer Definition ausgeführt. Eine fehlerhafte Konfiguration lässt das Play daher fehlschlagen, bevor der Reload versucht wird. Setzen Sie backup: true in der Template-Aufgabe, damit die vorherige Datei weiterhin zum Zurücksetzen verfügbar ist.