Die Grundlagen von Ansible, ohne die Ihre Playbooks ein Klumpen zusammengeklebter Spaghetti sind

Ich mache viele Reviews von fremdem Code in Ansible und schreibe viel selbst. Während ich Fehler analysiere (sowohl eigene als auch fremde) und eine gewisse Anzahl von Vorstellungsgesprächen führe, habe ich den Hauptfehler erkannt, den Ansible-Nutzer machen – sie tauchen in komplexe Themen ein, ohne die Grundlagen beherrscht zu haben.

Um diese universelle Ungerechtigkeit zu beheben, habe ich beschlossen, eine Einführung in Ansible für diejenigen zu schreiben, die bereits ein gewisses Wissen haben. Ich warne vor, dass dies keine Zusammenfassung der Handbücher ist, sondern ein Longread mit vielen Buchstaben und ohne Bilder.

Das erwartete Niveau des Lesers ist, dass bereits mehrere tausend Zeilen YAML geschrieben wurden und sich bereits etwas in der Produktion befindet, aber "irgendwie ist alles schief".

Bezeichnungen

Der größte Fehler eines Ansible-Nutzers ist, nicht zu wissen, wie die Dinge heißen. Wenn Sie die Bezeichnungen nicht kennen, können Sie das, was in der Dokumentation steht, nicht verstehen. Ein anschauliches Beispiel: Bei einem Vorstellungsgespräch konnte eine Person, die anscheinend viel mit Ansible gearbeitet hat, die Frage "Aus welchen Elementen besteht ein Playbook?" nicht beantworten. Als ich darauf hinwies, dass "die Antwort sein sollte, dass ein Playbook aus Plays besteht", folgte der vernichtende Kommentar "das nutzen wir nicht". Menschen arbeiten mit Ansible gegen Bezahlung und verwenden keine Plays. Tatsächlich verwenden viele es, wissen aber nicht, was es ist.

Lassen Sie uns damit anfangen, wie es genannt wird. Vielleicht wissen Sie es, vielleicht auch nicht, weil Sie nicht darauf geachtet haben, als Sie die Dokumentation gelesen haben.

Der ansible-playbook führt das Playbook aus. Ein Playbook ist eine Datei mit der Endung yml/yaml, in der etwa Folgendes steht:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Wir haben bereits verstanden, dass die gesamte Datei das Playbook ist. Wir können zeigen, wo die Rollen (roles) und die Aufgaben (tasks) sind. Aber wo ist das Play? Und was unterscheidet ein Play von einer Rolle oder einem Playbook?

Das steht alles in der Dokumentation. Aber das wird oft übersehen. Anfänger, weil es zu viel auf einmal ist und man sich nicht alles merken kann. Erfahrene, weil sie es für "triviale Dinge" halten. Wenn Sie erfahren sind, lesen Sie diese Seiten mindestens einmal alle sechs Monate, und Ihr Code wird deutlich besser.

Also, merken Sie sich: Ein Playbook ist eine Liste, die aus einem Play und import_playbook.
Das hier ist ein Play:

- hosts: group1
  roles:
    - role1

Und das hier ist auch ein weiteres Play:

- hosts: group2,group3
  tasks:
    - debug:

Was ist also ein Play? Warum ist es wichtig?

Play ist ein zentrales Element für das Playbook, denn nur Play verbindet die Liste der Rollen und/oder Aufgaben mit der Liste der Hosts, auf denen diese ausgeführt werden müssen. In den Tiefen der Dokumentation findet man Hinweise auf delegate_to, lokale Lookup-Plugins, netzwerk-spezifische Einstellungen, Jump-Hosts usw. Diese erlauben, den Ausführungsort der Aufgaben leicht zu ändern. Aber vergessen Sie das. Jede dieser cleveren Optionen hat sehr spezielle Anwendungen und ist definitiv nicht universell. Wir sprechen hier über grundlegende Dinge, die jeder wissen und nutzen sollte.

Wenn Sie "etwas" "irgendwo" ausführen möchten, schreiben Sie ein Play. Keine Rolle. Keine Rolle mit Modulen und Delegierungen. Sie nehmen einfach und schreiben Play. In der Feld hosts geben Sie an, wo die Ausführung erfolgt, und in roles/tasks, was ausgeführt werden soll.

Ganz einfach, oder? Wie könnte es anders sein?

Einer der typischen Punkte, an denen Menschen den Wunsch verspüren, dies nicht über Play zu tun, ist "eine Rolle, die alles einrichtet". Man möchte eine Rolle haben, die sowohl Server Typ-1- als auch Typ-2-Server konfiguriert.

Ein archetypisches Beispiel ist das Monitoring. Man möchte eine Rolle für das Monitoring haben, die das Monitoring einrichtet. Die Monitoring-Rolle wird auf die Monitoring-Hosts zugewiesen (im entsprechenden Play). Es stellt sich jedoch heraus, dass wir Pakete auf den Hosts installieren müssen, die wir überwachen. Warum also nicht Delegate verwenden? Außerdem müssen die iptables konfiguriert werden. Delegate? Und wir müssen auch die Konfiguration für die DB anpassen, damit das Monitoring funktioniert. Delegate! Sollte die Kreativität sprießen, kann man auch Delegation einführen. include_role in einer inneren Schleife mit einem cleveren Filter für die Gruppenliste, und innen include_role kann man noch machen delegate_to wieder. Und los geht's…

Der gute Wunsch, eine einzige Monitoring-Rolle zu haben, die "alles erledigt" – führt uns in die reinste Hölle, aus der es meistens nur einen Ausweg gibt: alles von Grund auf neu zu schreiben.

Wo ist der Fehler aufgetreten? In dem Moment, als Sie festgestellt haben, dass Sie zur Ausführung der Aufgabe "x" auf Host X zu Host Y gehen müssen, um dort "y" zu machen, hätten Sie eine einfache Übung ausführen sollen: Gehen Sie und schreiben Sie ein Play, das auf Host Y 'y' macht. Nicht etwas in 'x' hinzufügen, sondern von Grund auf neu schreiben. Auch wenn es mit fest codierten Variablen ist.

Es scheint, dass in den obigen Absätzen alles richtig gesagt wurde. Aber das ist nicht Ihr Fall! Denn Sie möchten wiederverwendbaren Code schreiben, der DRY ist und wie eine Bibliothek aussieht, und es muss ein Weg gefunden werden, dies zu tun.

Hier verbirgt sich ein weiterer schwerwiegender Fehler. Ein Fehler, der viele Projekte von akzeptabel geschrieben (man könnte es besser machen, aber es funktioniert und lässt sich leicht erweitern) in einen völligen Albtraum verwandelt hat, in dem selbst der Autor nicht mehr klarkommt. Es funktioniert, aber Gott bewahre, wenn man etwas ändern möchte.

Dieser Fehler klingt so: Eine Rolle ist eine Bibliotheksfunktion. Diese Analogie hat so viele gute Ansätze ruiniert, dass es einfach traurig ist, zuzusehen. Eine Rolle ist keine Bibliotheksfunktion. Sie kann keine Berechnungen durchführen und sie kann keine Entscheidungen auf der Ebene des Spiels treffen. Erinnern Sie mich daran, welche Entscheidungen das Spiel trifft?

Danke, Sie haben recht. Das Spiel trifft Entscheidungen (genauer gesagt, enthält es Informationen) darüber, welche Aufgaben und Rollen auf welchen Hosts ausgeführt werden sollen.

Wenn Sie diese Entscheidung einer Rolle delegieren, noch dazu mit Berechnungen, verurteilen Sie sich selbst (und denjenigen, der Ihren Code zu verstehen versucht) zu einem erbärmlichen Dasein. Die Rolle entscheidet nicht, wo sie ausgeführt wird. Diese Entscheidung trifft das play. Die Rolle tut, was ihr gesagt wurde, dort, wo es gesagt wurde.

Warum das Programmieren mit Ansible gefährlich ist und warum COBOL besser ist als Ansible, werden wir im Kapitel über Variablen und Jinja besprechen. Vorab sei gesagt – jede Ihrer Berechnungen hinterlässt eine unauslöschliche Spur von Änderungen an globalen Variablen, und Sie können nichts dagegen unternehmen. Sobald zwei "Spuren" sich kreuzen – ist alles verloren.

Hinweis für die Penibel: Eine Rolle kann zweifellos den Kontrollfluss beeinflussen. Es gibt delegate_to und sie hat sinnvolle Anwendungen. Es gibt meta: end host/play. Aber! Denken Sie daran, dass wir die Grundlagen lernen? Haben wir vergessen, dass es delegate_togeht? Wir sprechen über den einfachsten und schönsten Code in Ansible. Der leicht zu lesen, leicht zu schreiben, leicht zu debuggen, leicht zu testen und leicht zu erweitern ist. Also, noch einmal:

Das play und nur das play entscheidet, auf welchen Hosts was ausgeführt wird.

In diesem Abschnitt haben wir uns mit dem Gegensatz zwischen play und Rolle auseinandergesetzt. Nun sprechen wir über die Beziehungen zwischen Tasks und Rolle.

Tasks und Rollen

Betrachten wir play:

- hosts: somegroup
  pre_tasks:
    - some_tasks1:
  roles:
     - role1
     - role2
  post_tasks:
     - some_task2:
     - some_task3:

Angenommen, Sie müssen foo erstellen. Es sieht so aus: foo: name=foobar state=present. Wo soll das geschrieben werden? In pre? Post? Eine Rolle erstellen?

… Und wo sind die Aufgaben geblieben?

Wir beginnen wieder mit den Grundlagen – dem Aufbau von play. Wenn Sie in dieser Frage unsicher sind, können Sie play nicht als Grundlage für alles andere verwenden, und Ihr Ergebnis wird "instabil".

Der Aufbau von play: die Direktive hosts, die Einstellungen von play selbst sowie die Abschnitte pre_tasks, tasks, roles, post_tasks. Die anderen Parameter für play sind für uns jetzt nicht wichtig.

Die Reihenfolge ihrer Abschnitte mit Aufgaben und Rollen: pre_tasks, roles, tasks, post_tasks. Da die semantische Reihenfolge der Ausführung zwischen tasks und roles nicht klar ist, besagen die besten Praktiken, dass wir den Abschnitt taskshinzufügen, nur wenn es kein gibt roles. Wenn es eine roles, dann werden alle zugehörigen Aufgaben im Abschnitt pre_tasks/post_tasks.

platziert. Es bleibt nur das, was semantisch klar ist: zuerst pre_tasks, dann roles, dann post_tasks.

Aber wir haben die Frage immer noch nicht beantwortet: Wo sollen wir den Modulaufruf foo schreiben? Müssen wir für jedes Modul eine eigene Rolle schreiben? Oder ist es besser, eine umfangreiche Rolle für alles zu haben? Und wenn es keine Rolle ist, wo soll es geschrieben werden – in pre oder in post?

Wenn es auf diese Fragen keine fundierte Antwort gibt, ist das ein Zeichen für fehlende Intuition, also diese "wackligen Grundlagen". Lassen Sie uns das klären. Zuerst eine Kontrollfrage: Wenn bei play nichts vorhanden ist, pre_tasks und post_tasks (und es weder Tasks noch Roles gibt), kann dann etwas kaputtgehen, wenn ich die erste Task aus post_tasks ans Ende verschiebe? pre_tasks?

Natürlich deutet die Formulierung der Frage darauf hin, dass etwas kaputtgehen wird. Aber was genau?

… Handler. Das Studium der Grundlagen eröffnet eine wichtige Tatsache: Alle Handler werden automatisch nach jeder Sektion ausgeführt. Das heißt, alle Tasks aus pre_tasks, dann alle Handler, die notified wurden. Danach werden alle Roles und alle Handler, die in den Roles notified wurden, ausgeführt. Danach post_tasks und ihre Handler.

Somit, wenn Sie eine Task aus post_tasks in pre_tasksverschieben, werden Sie sie potenziell vor der Ausführung des Handlers ausführen. Beispielsweise, wenn in pre_tasks etwas installiert und konfiguriert wird, Webserver, sondern in post_tasks wird etwas hineingesendet. Wenn Sie diese Task in die Sektion pre_tasks verschieben, führt dies dazu, dass der Server zum Zeitpunkt des "Sendens" noch nicht gestartet ist und alles kaputtgeht.

Nun lassen Sie uns noch einmal überlegen, warum wir pre_tasks und post_tasks? Например, для того, чтобы выполнить всё нужное (включая хэндлеры) до выполнения роли. А post_tasks benötigen, um mit den Ergebnissen der Ausführung von Roles (einschließlich Handler) zu arbeiten.

Ein aufmerksamer Kenner von Ansible wird uns sagen, dass es meta: flush_handlers, aber warum benötigen wir flush_handlers, wenn wir uns auf die Ausführungsreihenfolge der Abschnitte im Play verlassen können? Darüber hinaus kann die Verwendung von meta: flush_handlers unerwartete Probleme mit sich bringen, wenn es um sich wiederholende Handler geht und uns merkwürdige Warnungen beschert, wenn wir sie verwenden. when u Block und so weiter. Je besser Sie Ansible kennen, desto mehr Nuancen können Sie für die "trickreiche" Lösung benennen. Eine einfache Lösung ist die natürliche Trennung zwischen pre/roles/post, die keine weiteren Nuancen hervorruft.

Und zurück zu unserem ‘foo’. Wohin soll es kommen? In pre, post oder roles? Offensichtlich hängt das davon ab, ob wir die Ergebnisse der Handler-Arbeit für foo benötigen. Wenn nicht, dann braucht foo weder in pre noch in post platziert zu werden – diese Abschnitte haben eine spezielle Bedeutung – die Ausführung von Tasks vor und nach dem Hauptcode-Array.

Jetzt lässt sich die Frage "Rolle oder Aufgabe" darauf reduzieren, was bereits im Play vorhanden ist – wenn dort Tasks sind, dann muss man sie zu den Tasks hinzufügen. Wenn es Roles gibt – dann muss eine Rolle erstellt werden (auch wenn es nur eine Aufgabe ist). Ich erinnere daran, dass Tasks und Roles nicht gleichzeitig verwendet werden.

Ein Verständnis der Grundlagen von Ansible liefert fundierte Antworten auf Fragen, die auf den ersten Blick nach Geschmacksfragen aussehen.

Tasks und Rollen (Teil zwei)

Lassen Sie uns nun die Situation besprechen, in der Sie gerade anfangen, ein Playbook zu schreiben. Sie müssen foo, bar und baz erledigen. Handelt es sich um eine Aufgabe, eine Rolle oder drei Rollen? Allgemeiner gesagt: Ab wann sollten Rollen geschrieben werden? Wozu ist es sinnvoll, Rollen zu schreiben, wenn man auch Aufgaben schreiben kann? … Was ist eigentlich eine Rolle?

Einer der gravierendsten Fehler (ich habe darüber bereits gesprochen) ist zu glauben, dass eine Rolle wie eine Funktion in einer Bibliothek eines Programms ist. Wie sieht die allgemeine Beschreibung einer Funktion aus? Sie nimmt Argumente entgegen, interagiert mit Nebeneffekten, erzeugt Nebenwirkungen und gibt einen Wert zurück.

Jetzt aufmerksam sein. Was davon kann in einer Rolle gemacht werden? Nebenwirkungen zu erzeugen – immer gerne, das ist schließlich das Wesen von Ansible – Nebenwirkungen zu erzeugen. Nebeneffekte haben? Kein Problem. Aber bei "einen Wert übergeben und zurückgeben" – da wird es schwierig. Erstens, Sie können keinen Wert in eine Rolle übergeben. Sie können eine globale Variable mit einer Lebensdauer, die der eines Plays entspricht, im Abschnitt vars für die Rolle festlegen. Sie können eine globale Variable mit einer Lebensdauer im Play innerhalb der Rolle festlegen. Oder sogar mit einer Lebensdauer des Playbooks (set_fact/register). Aber Sie können keine "lokalen Variablen" haben. Sie können keinen "Wert empfangen" und ihn "zurückgeben".

Daraus folgt das Wesentliche: Man kann in Ansible etwas schreiben, ohne dass es Nebenwirkungen gibt. Das Ändern von globalen Variablen ist immer eine Nebenwirkung für eine Funktion. In Rust beispielsweise ist die Änderung einer globalen Variablen unsafe. In Ansible ist es die einzige Methode, um Einfluss auf die Werte einer Rolle zu nehmen. Beachten Sie die verwendeten Begriffe: nicht "Wert an Rolle übergeben", sondern "Werte ändern, die von der Rolle verwendet werden". Zwischen den Rollen gibt es keine Isolation. Zwischen Aufgaben und Rollen gibt es keine Isolation.

Insgesamt: Eine Rolle ist keine Funktion..

Was ist also gut an einer Rolle? Erstens hat eine Rolle Standardwerte (/default/main.yaml), zweitens gibt es in einer Rolle zusätzliche Verzeichnisse, um Dateien zu speichern.

Was ist gut an Standardwerten? Aufgrund der etwas verzerrten Prioritätstabelle der Variablen in Ansible sind die Standardwerte der Rolle die am wenigsten priorisierten (mit Ausnahme der Befehlszeilenparameter von Ansible). Das bedeutet, wenn Sie Standardwerte bereitstellen möchten und sich keine Sorgen machen wollen, dass diese die Werte aus dem Inventory oder Gruppenvariablen überschreiben, sind die Standardeinstellungen der Rolle der einzig richtige Ort für Sie. (Ich lüge ein wenig – es gibt noch |d(your_default_here), aber wenn es um stationäre Plätze geht – dann nur die Standardrollen).

Was ist noch gut an Rollen? Sie haben eigene Kataloge. Diese Kataloge sind für Variablen gedacht, sowohl für feste (d.h. für die Rolle berechnete) als auch für dynamische (es gibt dieses Muster oder Anti-Muster – include_vars , indem es {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml). Diese Kataloge sind für files/, templates/. Außerdem ermöglichen sie Rollen, eigene Module und Plugins zu haben (library/). Im Vergleich zu den Aufgaben in Playbooks (die ebenfalls all dies beinhalten könnten), ist der Nutzen hier nur der, dass die Dateien nicht in einem Haufen sind, sondern in mehreren getrennten Haufen.

Ein weiterer Aspekt: Man kann versuchen, Rollen zu erstellen, die für wiederkehrende Nutzung (über Galaxy) verfügbar sind. Nach dem Erscheinen von Collections kann die Verbreitung von Rollen als fast vergessen betrachtet werden.

Somit haben Rollen zwei wichtige Merkmale: Sie haben Standards (eine einzigartige Eigenschaft) und sie ermöglichen eine Strukturierung des Codes.

Zurück zur ursprünglichen Frage: Wann sollte man Tasks und wann Roles verwenden? Tasks im Playbook dienen häufig entweder als "Kleber" vor/nach Rollen oder als eigenständige Bausteine (in diesem Fall sollten keine Rollen im Code sein). Eine Mischung aus normalen Tasks und Rollen ist unweigerlich unordentlich. Man sollte sich an einen klaren Stil halten – entweder Tasks oder Rollen. Rollen bieten eine Trennung der Entitäten und Standardwerte, während Tasks es ermöglichen, den Code schneller zu lesen. In der Regel enthält man in Rollen eher "statische" (wichtige und komplexe) Codes, während in der Style von Tasks Hilfsskripte geschrieben werden.

Es ist möglich, import_role als Task zu verwenden, aber wenn du das tust, sei bereit, dir selbst zu erklären, warum du das machen möchtest.

Ein aufmerksamer Leser könnte anmerken, dass Rollen weitere Rollen importieren können, dass Rollen eine Abhängigkeit über galaxy.yml haben können und dass es noch das erschreckende und furchtbare gibt. include_role Denkt daran, wir verbessern unsere Fähigkeiten in Ansible, nicht in rhythmischer Gymnastik.

Handler und Tasks

Lassen Sie uns eine weitere offensichtliche Sache diskutieren: Handler. Die korrekte Verwendung von Handlern ist fast schon eine Kunst. Wo liegt der Unterschied zwischen einem Handler und einer Task?

Da wir die Grundlagen auffrischen, hier ein Beispiel:

- hosts: group1
  tasks:
    - foo:
      notify: handler1
  handlers:
     - name: handler1
       bar:

Die Rolle der Handler befindet sich in rolename/handlers/main.yaml. Handler können zwischen allen Spielern geteilt werden: pre/post_tasks können die Handler einer Rolle aufrufen, und die Rolle kann Handler aus dem Play aufrufen. "Cross-role" Aufrufe von Handlern führen jedoch zu viel mehr Verwirrung als die Wiederholung eines trivialen Handlers. (Ein weiterer Aspekt der besten Praktiken ist, zu versuchen, Wiederholungen von Handlernamen zu vermeiden).

Der Hauptunterschied besteht darin, dass eine Task immer (idempotent) ausgeführt wird (plus/minus Tags und when), ein Handler – zur Statusänderung (notify wird nur ausgelöst, wenn etwas geändert wurde). Was bedeutet das? Zum Beispiel, dass bei einem erneuten Start, wenn es keine Änderungen gab, auch kein Handler ausgeführt wird. Warum kann es jedoch nötig sein, den Handler auszuführen, auch wenn bei der generierenden Aufgabe keine Änderungen festgestellt wurden? Zum Beispiel, weil etwas kaputt gegangen ist und Änderungen vorgenommen wurden, aber der Ausführung des Handlers nicht nachgekommen wurde. Zum Beispiel, weil das Netzwerk vorübergehend ausgefallen war. Die Konfiguration wurde geändert, der Dienst wurde nicht neu gestartet. Bei der nächsten Ausführung wird die Konfiguration nicht mehr geändert, und der Dienst bleibt mit der alten Konfiguration.

Die Situation mit der Konfiguration ist nicht lösbar (genauer gesagt, man könnte sich selbst ein spezielles Protokoll für den Neustart mit Datei-Flags etc. einfallen lassen, aber das ist nicht mehr ‘basic ansible’ in irgendeiner Form). Es gibt jedoch eine andere häufige Geschichte: Wir haben eine Anwendung installiert, ihre .service-Datei erstellt und wollen sie jetzt daemon_reload und state=started. Der natürliche Ort dafür scheint ein Handler zu sein. Aber wenn man ihn nicht als Handler, sondern als Aufgabe am Ende der Taskliste oder Rolle einfügt, wird er jedes Mal idempotent ausgeführt. Selbst wenn das Playbook mitten drin kaputtgeht. Das löst das Problem mit dem Attribut restarted nicht (man kann keine Aufgabe mit dem Attribut restarted ausführen, da die Idempotenz verloren geht), aber es ist definitiv sinnvoll, state=started zu setzen. Die allgemeine Stabilität der Playbooks erhöht sich, da die Anzahl der Abhängigkeiten und dynamischen Zustände verringert wird.

Ein weiteres positives Merkmal von Handlern ist, dass sie die Ausgabe nicht überfluten. Keine Änderungen – keine überflüssigen skipped oder ok in der Ausgabe – leichter lesbar. Das ist jedoch auch ein negatives Merkmal: Wenn Sie einen Tippfehler in einer linear ausgeführten Aufgabe beim ersten Durchlauf finden, werden die Handler nur bei Änderungen ausgeführt, d.h. unter bestimmten Bedingungen – sehr selten. Zum Beispiel das erste Mal nach fünf Jahren. Und natürlich wird dort ein Tippfehler im Namen sein, und alles wird kaputtgehen. Ein zweites Mal können sie nicht gestartet werden – es gibt ja keine Änderungen.

Es ist wichtig, über die Verfügbarkeit von Variablen zu sprechen. Wenn Sie beispielsweise notify für eine Aufgabe mit einer Schleife verwenden, was wird dann in den Variablen gespeichert? Es kann analytisch verstanden werden, aber das ist nicht immer trivial, besonders wenn die Variablen aus verschiedenen Quellen stammen.

… Daher sind Handler wesentlich weniger nützlich und weitaus problematischer, als es scheint. Wenn es möglich ist, etwas elegant (ohne komplizierte Tricks) ohne Handler zu schreiben, ist es besser, das so zu tun. Wenn es nicht schön funktioniert, ist es besser, mit ihnen zu arbeiten.

Ein aufmerksamer Leser weist vollkommen zu Recht darauf hin, dass wir nicht diskutiert haben listen, dass ein Handler notify für einen anderen Handler aufrufen kann, dass ein Handler import_tasks enthalten kann (der include_role mit with_items ausführen kann), dass das Handler-System in Ansible turing-vollständig ist, und dass Handler aus include_role auf interessante Weise mit den Handlern aus dem Play und so weiter überlappen – all das gehört offensichtlich nicht zu den "Grundlagen".

Obwohl es eine bestimmte Überraschung gibt, die tatsächlich eine Funktion ist und die man im Hinterkopf behalten sollte. Wenn Sie eine Aufgabe haben, die mit delegate_to ausgeführt wird und die einen notify aufweist, wird der entsprechende Handler ohne delegate_toausgeführt, d.h. auf dem Host, auf dem das Play zugewiesen ist. (Obwohl der Handler natürlich auch einen delegate_to haben kann).

Ich möchte ein paar Worte zu wiederverwendbaren Rollen sagen. Vor der Einführung von Kollektionen gab es die Idee, universelle Rollen zu schaffen, die man ansible-galaxy install , und los geht's. Es funktioniert auf allen Betriebssystemen in allen Varianten und in allen Situationen. Ich denke jedoch, dass es nicht funktioniert. Jede Rolle, die auf unzählige Fälle abzielt, include_vars, ist dazu verdammt, in die Fallstricke von Corner-Case-Bugs zu fallen. Man kann diese Bugs durch intensives Testen beheben, aber wie bei jedem Testen gilt: Entweder hat man das kartesische Produkt der Eingabewerte und eine totale Funktion oder man hat "einzelne Szenarien abgedeckt." Meiner Meinung nach ist es viel besser, wenn die Rolle linear ist (zyklomatische Komplexität 1).

Je weniger If-Abfragen (ob explizit oder deklarativ — in Form von when oder in Form von include_vars nach einer Variablenliste) vorhanden sind, desto besser ist die Rolle. Manchmal müssen Abzweigungen gemacht werden, aber ich wiederhole, je weniger, desto besser. Eine scheinbar gute Rolle von Galaxy (es funktioniert ja!) mit einer Fülle von when kann weniger bevorzugt werden als die "eigenen" Rolle aus fünf Aufgaben. Der Moment, wenn die Rolle mit galaxy besser ist – wenn Sie anfangen, etwas zu schreiben. Der Moment, wenn sie schlechter wird – wenn etwas kaputtgeht und Sie den Verdacht haben, dass es an der "Rolle mit galaxy" liegt. Sie öffnen sie und sehen fünf Includes, acht Task-Listen und einen Stapel whenund darin müssen Sie sich zurechtfinden. Statt 5 Aufgaben in einer linearen Liste, in der es nichts gibt, was kaputtgehen könnte.

In den folgenden Teilen

  • Ein wenig über Inventar, gruppierte Variablen, den host_group_vars Plugin, hostvars. Wie man aus Spaghetti einen gordischen Knoten bindet. Scope und Priorität von Variablen, das Ansible-Speichermodell. "Wo sollte ich also den Benutzernamen für die Datenbank speichern?"
  • jinja: {{ jinja }} — nosql notype nosense weicher Knetgummi. Es ist überall, sogar da, wo man es nicht erwartet. Ein bisschen über !!unsafe und köstliches yaml.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster