Die Grundlagen von Ansible, ohne die Ihre Playbooks ein Klumpen aus zusammengeklebten Nudeln sind

Ich mache viele Code-Reviews für fremden Ansible-Code und schreibe auch viel selbst. Bei der Analyse von Fehlern (sowohl von anderen als auch von mir selbst) sowie während einiger Interviews habe ich den grundlegenden Fehler erkannt, den Ansible-Nutzer machen — sie stürzen sich auf die Komplexität, ohne die Grundlagen zu beherrschen.

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

Das erwartete Niveau des Lesers – schon mehrere tausend Zeilen YAML geschrieben, etwas ist schon in der Produktion, aber „irgendwie läuft alles schief“.

Bezeichnungen

Der Hauptfehler des Ansible-Nutzers ist, nicht zu wissen, wie die Dinge heißen. Wenn Sie die Bezeichnungen nicht kennen, können Sie nicht verstehen, was in der Dokumentation geschrieben steht. Ein lebendiges Beispiel: Bei einem Interview konnte eine Person, die angeblich viel mit Ansible gearbeitet hat, die Frage „Aus welchen Elementen besteht ein Playbook?“ nicht beantworten. Und als ich hinwies, dass „die Antwort sein sollte, dass ein Playbook aus Play besteht“, folgte der verheerende Kommentar „das verwenden wir nicht“. Menschen schreiben Ansible für Geld und verwenden kein Play. In Wirklichkeit verwenden sie es, wissen aber nicht, was das ist.

Lassen Sie uns also mit den Grundlagen beginnen: wie die Dinge heißen. Vielleicht wissen Sie das schon, vielleicht aber auch nicht, weil Sie nicht darauf geachtet haben, als Sie die Dokumentation gelesen haben.

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

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

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

Das steht alles in der Dokumentation. Und das wird ignoriert. Anfänger — weil es zu viel ist und man sich nicht alles auf einmal merken kann. Erfahrene Benutzer — weil es „triviale Dinge“ sind. Wenn Sie erfahren sind, lesen Sie diese Seiten mindestens einmal alle sechs Monate, und Ihr Code wird um Klassen besser.

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

- hosts: group1
  roles:
    - role1

Und das hier ist ebenfalls ein Play:

- hosts: group2,group3
  tasks:
    - debug:

Was ist also ein Play? Warum existiert es?

Play ist ein Schlüsselelement für das Playbook, denn nur Play verbindet die Liste der Rollen und/oder Aufgaben mit der Liste von Hosts, auf denen sie ausgeführt werden müssen. In den tiefen Untiefen der Dokumentation finden sich Hinweise auf delegate_to, lokale Lookup-Plugins, netzwerkspezifische Einstellungen, Jump Hosts usw. Sie ermöglichen es, den Ausführungsort der Aufgaben leicht zu ändern. Aber vergessen Sie das. Jede dieser ausgeklügelten Optionen hat sehr spezielle Anwendungen und ist definitiv nicht universell. Wir sprechen ü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 Delegaten. Sie nehmen und schreiben ein Play. In dem Sie im Feld Hosts auflisten, wo es ausgeführt werden soll, und in Roles/Tasks, was ausgeführt werden soll.

Einfach, oder? Und wie könnte es anders sein?

Eines der typischen Merkmale, wenn Menschen den Wunsch haben, dies nicht über Play zu tun, ist „eine Rolle, die alles konfiguriert“. Man möchte eine Rolle haben, die konfiguriert und Server erster Art, sowie Server zweiter Art.

Ein archetypisches Beispiel ist das Monitoring. Man möchte eine Rolle Monitoring haben, die das Monitoring einrichtet. Die Rolle Monitoring wird auf die Monitoring-Hosts zugewiesen (im entsprechenden Play). Aber es stellt sich heraus, dass wir für das Monitoring Pakete auf den Hosts installieren müssen, die wir überwachen. Warum also nicht delegate? Und wir müssen auch iptables konfigurieren. delegate? Und wir müssen die Konfiguration für die DBMS anpassen, damit das Monitoring startet. delegate! Und wenn die Kreativität Überhand nimmt, kann man die Delegation include_role in einer verschachtelten Schleife mit einem ausgeklügelten Filter auf die Liste der Gruppen machen, und darin include_role kann man noch wieder machen delegate_to und los geht's…

Der fromme Wunsch, eine einzige Rolle Monitoring zu haben, die "alles macht", führt uns in die Hölle, aus der es meist nur einen Ausweg gibt: alles von Grund auf neu zu schreiben.

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

Es scheint, als wäre in den obigen Absätzen alles richtig gesagt. Aber das ist doch nicht Ihr Fall! Denn Sie möchten wiederverwendbaren Code schreiben, der DRY ist und wie eine Bibliothek aussieht, und Sie müssen einen Weg finden, wie das geht.

Hier versteckt sich ein weiterer grober Fehler. Ein Fehler, der viele Projekte von akzeptabler Qualität (man könnte es besser machen, aber alles funktioniert und es lässt sich leicht erweitern) in einen vollkommenen Albtraum verwandelt hat, in dem selbst der Autor nicht mehr zurechtkommt. Es funktioniert, aber Gott bewahre, wenn man etwas ändert.

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

Danke, Sie haben recht. Play trifft die Entscheidung (genauer gesagt, enthält sie die Informationen), welche Aufgaben und Rollen auf welchen Hosts ausgeführt werden.

Wenn Sie diese Entscheidung an eine Rolle delegieren und auch noch Berechnungen hineinpacken, verurteilen Sie sich selbst (und die Person, die versucht, Ihren Code zu verstehen) zu einer elenden Existenz. Eine Rolle entscheidet nicht, wo sie ausgeführt wird. Diese Entscheidung trifft Play. Eine Rolle tut das, was ihr gesagt wird, dort, wo ihr gesagt wird.

Warum das Programmieren mit Ansible gefährlich ist und warum COBOL besser ist als Ansible, werden wir im Kapitel über Variablen und Jinja besprechen. Bis dahin sagen wir eines: Jede Ihrer Berechnungen hinterlässt eine unauslöschliche Spur über die Änderung globaler Variablen, und Sie können dagegen nichts tun. Sobald zwei "Spuren" sich kreuzen – ist alles verloren.

Anmerkung für die Aufmerksamen: Eine Rolle kann natürlich den Steuerfluss beeinflussen. Es gibt delegate_to und es hat vernünftige Anwendungen. Es gibt meta: end host/play. Aber! Denken Sie daran, wir lernen die Grundlagen? Haben Sie vergessen, was delegate_toist? 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:

Play und nur Play entscheidet, auf welchen Hosts was ausgeführt wird.

In diesem Abschnitt haben wir uns mit dem Konflikt zwischen Play und Rolle beschäftigt. Nun sprechen wir über das Verhältnis zwischen Aufgaben und Rolle.

Aufgaben 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 machen. Und es sieht so aus wie foo: name=foobar state=present. Wo soll das geschrieben werden? In Pre? Post? Eine Rolle erstellen?

… Und wo sind die Aufgaben geblieben?

Wir beginnen erneut bei den Grundlagen – der Aufbau von Play. Wenn Sie in dieser Frage schwimmen, können Sie Play nicht als Grundlage für alles andere verwenden, und Ihr Ergebnis wird "schwammig".

Die Play-Anweisung: Hosts, Einstellungen für das Play selbst und die Abschnitte pre_tasks, tasks, roles, post_tasks. Die anderen Parameter für das Play sind momentan nicht wichtig für uns.

Die Reihenfolge ihrer Abschnitte mit Tasks und Rollen: pre_tasks, roles, tasks, post_tasks. Da die semantische Reihenfolge der Ausführung zwischen tasks und roles nicht klar ist, besagt die Best Practice, dass wir einen Abschnitt hinzufügen, tasks, nur wenn es kein roles. Wenn es eine gibt roles, dann werden alle zugehörigen Tasks in die Abschnitte gelegt. pre_tasks/post_tasks.

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

Aber wir haben die Frage noch nicht beantwortet: Wo soll der Aufruf des Moduls foo geschrieben werden? Müssen wir für jedes Modul eine ganze Rolle schreiben? Oder ist es besser, eine große Rolle für alles zu haben? Und wenn keine Rolle, wo soll es dann 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 die sogenannten "wackeligen Grundlagen". Lassen Sie uns das klären. Zuerst eine Kontrollfrage: Wenn das Play pre_tasks und post_tasks hat (und keine Tasks oder Rollen), 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 es kaputtgehen wird. Aber was genau?

… Handler. Das Lesen der Grundlagen offenbart eine wichtige Tatsache: Alle Handler werden automatisch nach jedem Abschnitt flush’d. D.h. alle Tasks aus pre_tasks, dann alle Handler, die notify waren. Danach werden alle Rollen und alle Handler, die in den Rollen notify waren, ausgeführt. Danach post_tasks und deren Handler.

Somit, wenn Sie eine Task aus post_tasks in pre_tasksverschieben, führen Sie sie potenziell vor dem Handler aus. Zum Beispiel, wenn in pre_tasks etwas installiert und konfiguriert wird, wird das Verschieben dieser Task in den Abschnitt einen Webserver, sondern in post_tasks dazu führen, dass der Server im Moment des "Schickens" noch nicht gestartet ist und alles kaputtgeht. pre_tasks Jetzt lassen Sie uns noch einmal überlegen, warum wir

arbeiten können, um mit den Ergebnissen der Ausführungen der Rollen (einschließlich Handler) zu arbeiten. pre_tasks und post_tasks? Например, для того, чтобы выполнить всё нужное (включая хэндлеры) до выполнения роли. А post_tasks 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 Reihenfolge der Ausführung der Abschnitte im Play verlassen können? Darüber hinaus kann die Verwendung von meta: flush_handlers uns mit unerwarteten wiederholten Handlers überraschen und uns seltsame Warnungen im Falle der Verwendung vonblock when arbeiten usw. Je besser Sie Ansible kennen, desto mehr Nuancen können Sie für eine "clevere" Lösung benennen. Und die einfache Lösung – die Verwendung einer natürlichen Trennung zwischen pre/roles/post – verursacht keine Nuancen. und so weiter. Je besser Sie Ansible kennen, desto mehr Details können Sie für eine "clevere" Lösung angeben. Eine einfache Lösung besteht darin, eine natürliche Trennung zwischen pre/roles/post zu verwenden, was keine besonderen Aspekte aufwirft.

Und zurück zu unserem ‘foo’. Wo sollen wir es platzieren? In pre, post oder in roles? Offensichtlich hängt es davon ab, ob wir die Ergebnisse der Verarbeitung des Handlers für foo benötigen. Wenn nicht, dann muss foo weder in pre noch in post gelegt werden — diese Abschnitte haben eine spezielle Bedeutung — das Ausführen von Tasks vor und nach dem Hauptcode.

Jetzt besteht die Antwort auf die Frage „Rolle oder Task“ darin, was bereits im Play vorhanden ist — wenn dort Tasks vorhanden sind, müssen sie in Tasks ergänzt werden. Wenn es Rollen gibt — muss eine Rolle erstellt werden (auch wenn sie aus nur einer Task besteht). Ich erinnere daran, dass Tasks und Rollen nicht gleichzeitig verwendet werden.

Das Verständnis der Grundlagen von Ansible gibt fundierte Antworten auf, scheinbar, Fragen des Geschmacks.

Tasks und Rollen (Teil zwei)

Jetzt besprechen wir die Situation, wenn Sie gerade erst anfangen, ein Playbook zu schreiben. Sie müssen foo, bar und baz machen. Sind das drei Tasks, eine Rolle oder drei Rollen? Zusammenfassend die Frage: Wann sollte man mit dem Schreiben von Rollen beginnen? Was ist der Sinn, Rollen zu schreiben, wenn man Tasks schreiben kann? … Aber was ist eine Rolle?

Einer der gravierendsten Fehler (darüber habe ich bereits gesprochen) — zu glauben, dass eine Rolle wie eine Funktion in der Programmbibliothek ist. Wie sieht eine allgemeine Beschreibung einer Funktion aus? Sie nimmt Argumente als Eingabe, interagiert mit Nebeneffekten, erzeugt Side Effects und gibt einen Wert zurück.

Jetzt, aufgepasst. Was davon kann in einer Rolle gemacht werden? Side Effects auslösen — immer gerne, das ist die Essenz von Ansible — Side Effects zu erzeugen. Side Causes haben? Elementar. Aber beim „Wert übergeben und zurückgeben“ — da wird es schwierig. Erstens, Sie können keinen Wert an eine Rolle übergeben. Sie können eine globale Variable mit einer Lebensdauer, die der von Play entspricht, im Abschnitt vars für die Rolle einstellen. Sie können eine globale Variable mit der Lebensdauer innerhalb von Play in der Rolle setzen. Oder sogar mit der 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 Hauptprinzip: Man kann in Ansible nichts schreiben und keine Side Effects auslösen. Die Änderung globaler Variablen ist immer ein Side Effect für eine Funktion. In Rust ist beispielsweise die Änderung einer globalen Variablen... unsafe. Aber in Ansible ist es die einzige Möglichkeit, Werte für die Rolle zu beeinflussen. Beachten Sie die verwendeten Ausdrücke: nicht „Wert in Rolle übergeben“, sondern „Werte ändern, die die Rolle verwendet“. Zwischen Rollen gibt es keine Isolation. Zwischen Tasks und Rollen gibt es keine Isolation.

Insgesamt: Eine Rolle ist keine Funktion..

Was ist gut an Rollen? Erstens gibt es in Rollen Standardwerte (/default/main.yaml), zweitens haben Rollen zusätzliche Verzeichnisse zum Speichern von Dateien.

Was sind die Vorteile von Standardwerten? In der etwas abwegigen Prioritätstabelle von Maslow spielt das, was in Ansible als Role Defaults bezeichnet wird, die niedrigste Rolle ( von den Befehlszeilenparametern von Ansible abgesehen). Das bedeutet, dass, wenn Sie Standardwerte bereitstellen möchten, ohne sich Sorgen zu machen, dass sie die Werte aus dem Inventar oder den Gruppenvariablen überschreiben, die Standardwerte der Rolle der einzig richtige Ort für Sie sind. (Ich lüge ein wenig – es gibt noch |d(your_default_here), aber wenn wir über feste Plätze sprechen – dann nur die Standardwerte der Rollen).

Was ist noch gut an Rollen? Sie haben ihre eigenen Verzeichnisse. Dies sind Verzeichnisse für Variablen, sowohl für feste (d.h. berechnete für die Rolle) als auch für dynamische (es gibt ein Muster oder ein Anti-Muster – include_vars zusammen mit {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Dies sind Verzeichnisse für files/, templates/. Außerdem ermöglichen sie es, dass Rollen ihre eigenen Module und Plugins haben (library/). Aber im Vergleich zu den Aufgaben in Playbooks (wo auch all dies sein kann) besteht der Vorteil hier nur darin, dass die Dateien nicht in einem einzigen Haufen liegen, sondern in mehreren getrennten Haufen.Ein weiterer Punkt: Man kann versuchen, Rollen zu machen, die für die Wiederverwendung verfügbar sind (über Galaxy). Nach dem Aufkommen von Sammlungen kann man die Verbreitung von Rollen als fast vergessen betrachten.Somit haben Rollen zwei wichtige Merkmale: Sie haben Standardwerte (eine einzigartige Eigenschaft) und sie ermöglichen die Strukturierung des Codes.

Zurück zur ursprünglichen Frage: Wann sollte man Aufgaben und wann Rollen verwenden? Aufgaben werden in Playbooks meist entweder als "Kleber" vor/nach Rollen verwendet oder als eigenständiges Bauelement (dann sollten im Code keine Rollen vorhanden sein). Ein Haufen normaler Aufgaben gemischt mit Rollen ist eindeutig unordentlich. Man sollte sich an einen bestimmten Stil halten – entweder Aufgaben oder Rollen. Rollen bieten eine Trennung von Entitäten und Standardwerten, während Aufgaben das Lesen des Codes schneller ermöglichen. In der Regel wird in Rollen der eher "statische" (wichtige und komplexe) Code ausgegliedert, während unterstützende Skripte im Aufgabenstil geschrieben werden.

}

}

Es besteht die Möglichkeit, import_role als Aufgabe zu erstellen, aber wenn Sie so etwas tun, seien Sie bereit, es für Ihr eigenes Gefühl von Ästhetik zu erklären, warum Sie das tun möchten.

Ein genauer Leser könnte sagen, dass Rollen Rollen importieren können, dass Rollen Abhängigkeiten durch galaxy.yml haben können und dass es außerdem das furchtbare und schreckliche gibt. include_role — Ich erinnere daran, dass wir unsere Fähigkeiten in grundlegender Ansible verbessern und nicht in Rhythmischer Gymnastik.

Handler und Aufgaben

Lassen Sie uns noch eine offensichtliche Sache besprechen: Handler. Sie richtig zu nutzen, ist fast eine Kunst. Was ist der Unterschied zwischen einem Handler und einer Aufgabe?

Da wir die Grundlagen wieder aufgreifen, hier ein Beispiel:

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

In einer Rolle befinden sich die Handler in rolename/handlers/main.yaml. Handler werden zwischen allen Teilnehmern geteilt: pre/post_tasks können die Handler der Rolle auslösen, und die Rolle kann Handler aus dem Spiel heraus aufrufen. Allerdings verursachen "cross-role" Aufrufe von Handlern viel mehr Verwirrung als die Wiederholung eines triviale Handlers. (Ein weiteres Element der Best Practices — versuchen Sie, keine Namen von Handlers zu wiederholen).

Der Hauptunterschied besteht darin, dass eine Aufgabe (idempotent) immer ausgeführt wird (plus/minus Tags und when), während ein Handler nur bei einer Änderung des Zustands (notify wird nur ausgelöst, wenn es eine Veränderung gab) aufgerufen wird. Was kann das zur Folge haben? Zum Beispiel, dass bei einem erneuten Ausführen, wenn keine Veränderung stattfand, kein Handler ausgeführt wird. Aber warum könnte es sein, dass wir einen Handler ausführen müssen, wenn es bei der dazugehörigen Aufgabe keine Änderungen gab? Zum Beispiel, weil etwas kaputt gegangen ist und es eine Änderung gab, aber der Handler nicht erreicht wurde. Zum Beispiel, weil das Netzwerk temporär ausgefallen ist. Die Konfiguration hat sich 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 Version der 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 Dateiflaggen erfinden usw., aber das ist schon nicht mehr ‚grundlegendes Ansible‘ in irgendeiner Form). Dafür gibt es eine andere häufige Geschichte: Wir haben eine Anwendung installiert, ihre .service-Datei erstellt und möchten nun, dass sie daemon_reload und state=started. Und ein natürlicher Platz dafür scheint der Handler zu sein. Aber wenn man ihn nicht als Handler, sondern als Task am Ende der Taskliste oder als Rolle ausführt, wird er bei jedem Durchlauf idempotent ausgeführt. Selbst wenn das Playbook mitten im Durchlauf abstürzt. Das löst das Problem mit restarted (man kann keine Task mit dem Attribut restarted durchführen, da die Idempotenz verloren geht) nicht, aber es ist definitiv sinnvoll, state=started zu setzen, die allgemeine Stabilität der Playbooks steigt, da die Anzahl der Abhängigkeiten und des dynamischen Zustands verringert wird.

Ein weiteres positives Merkmal des Handlers ist, dass er die Ausgabe nicht verstopft. Es gab keine Änderungen – keine überflüssigen skipped oder ok in der Ausgabe – leichter zu lesen. Es ist auch ein negatives Merkmal – wenn man einen Schreibfehler in einer linear ausführbaren Task beim ersten Durchlauf sieht, werden die Handler nur bei changed ausgeführt, also unter bestimmten Bedingungen – sehr selten. Zum Beispiel, das erste Mal in fünf Jahren. Und natürlich wird es einen Schreibfehler im Namen geben, und alles wird kaputtgehen. Und beim zweiten Mal kann man ihn nicht mehr starten – es gibt kein changed.

Man muss gesondert über die Zugänglichkeit von Variablen sprechen. Zum Beispiel, wenn Sie notify für eine Task mit einer Schleife haben, was wird in den Variablen sein? Man kann es analytisch herausfinden, aber das ist nicht immer trivial, insbesondere wenn die Variablen aus verschiedenen Quellen kommen.

… Also sind Handler viel weniger nützlich und viel problematischer, als es scheint. Wenn man etwas ohne Tricks schön schreiben kann, ist es besser, es ohne Handler zu tun. Wenn es nicht schön wird – besser mit ihnen.

Ein aufmerksamer Leser weist zu Recht darauf hin, dass wir nicht besprochen haben, listen, dass ein Handler notify für einen anderen Handler auslösen kann, dass ein Handler import_tasks enthalten kann (das include_role mit with_items durchführen kann), dass das Handler-System in Ansible turing-vollständig ist, dass die Handler aus include_role auf höchst interessante Weise mit den Handlern aus dem Play zusammentreffen usw. – all dies sind offensichtlich nicht die "Grundlagen".

Obwohl es einen bestimmten WTF gibt, der tatsächlich ein Feature ist, und an den man sich erinnern muss. Wenn Ihre Task mit delegate_to ausgeführt wird und sie notify hat, wird der entsprechende Handler ohne delegate_to, das heißt, auf dem Host, auf dem das Play zugewiesen ist, ausgeführt. (Obwohl der Handler natürlich auch sein kann, delegate_to ).

Außerdem möchte ich ein paar Worte zu wiederverwendbaren Rollen sagen. Vor der Einführung von Collections gab es die Idee, dass man universelle Rollen schaffen kann, die man ansible-galaxy install und fuhr. Funktioniert auf allen Betriebssystemen aller Varianten in allen Situationen. Nun, meine Meinung: das funktioniert nicht. Jede Rolle mit Unterstützung für 100500 Fälle ist zu einem Abgrund von Corner-Case-Fehlern verurteilt. Diese können durch massives Testen abgedeckt werden, aber wie bei jedem Testen hat man entweder das kartesische Produkt von Eingabewerten und eine totale Funktion, oder man hat "einzelne Szenarien abgedeckt". Meine Meinung ist — es ist viel besser, wenn die Rolle linear ist (cyclomatische Komplexität 1). include_varsJe weniger ifs (explizit oder deklarativ — in Form von

oder Form when nach Variablen), desto besser ist die Rolle. Manchmal muss man Verzweigungen machen, aber ich wiederhole, je weniger es davon gibt, desto besser. Also ist eine scheinbar gute Rolle mit Galaxy (es funktioniert ja!) mit einer Menge include_vars vielleicht weniger vorteilhaft als eine "eigene" Rolle aus fünf Tasks. Der Moment, in dem die Rolle mit Galaxy besser ist — wenn man anfängt, etwas zu schreiben. Der Moment, in dem sie schlechter wird — wenn etwas kaputtgeht und man den Verdacht hat, dass es an der "Rolle mit Galaxy" liegt. Man öffnet sie, und da sind fünf Includes, acht Task-Listen und ein Stapel when von ... Und das muss man durchdringen. Anstatt 5 Tasks in einer linearen Liste, in der es nichts gibt, was kaputtgehen kann. whenIn den nächsten Teilen

Ein wenig über Inventar, Gruppenvariablen, host_group_vars Plugin, hostvars. Wie man aus Spaghetti den Gordischen Knoten verbindet. Scope und Präzedenz von Variablen, Ansible-Speichermodell. "Wo zur Hölle soll der Benutzername für die Datenbank gespeichert werden?"

  • jinja: {{ jinja }}
  • — nosql notype nosense weiche Knetmasse. Es ist überall, sogar dort, wo man es nicht erwartet. Ein wenig über !!unsafe und schmackhaften YAML. Die Veröffentlichung der OpenSUSE Leap 15.2 Distribution

Quelle: habr.com

60GB SSD 8Gb DDR4