Einführung in Puppet

Puppet ist ein Konfigurationsmanagementsystem. Es wird verwendet, um Hosts in den gewünschten Zustand zu versetzen und diesen Zustand aufrechtzuerhalten.

Ich arbeite seit über fünf Jahren mit Puppet. Dieser Text ist im Wesentlichen eine übersetzte und umstrukturierte Zusammenstellung der wichtigsten Punkte aus der offiziellen Dokumentation, die es neuen Benutzern ermöglicht, schnell in die Grundlagen von Puppet einzutauchen.

Einführung in Puppet

Grundinformationen

Das Arbeitschema von Puppet ist client-server-basiert, obwohl auch eine serverlose Betriebsart mit eingeschränkter Funktionalität unterstützt wird.

Es wird ein Pull-Modell verwendet: Standardmäßig fragen die Clients alle 30 Minuten den Server nach der Konfiguration und wenden sie an. Wenn Sie mit Ansible gearbeitet haben, hier wird ein anderes Push-Modell verwendet: Der Administrator initiiert den Prozess der Anwendung der Konfiguration, die Clients wenden von sich aus nichts an.

Bei der Netzwerkkommunikation wird bidirektionale TLS-Verschlüsselung verwendet: Server und Client haben ihre eigenen privaten Schlüssel und die entsprechenden Zertifikate. Normalerweise stellt der Server Zertifikate für die Clients aus, aber prinzipiell ist auch die Verwendung einer externen CA möglich.

Einführung in die Manifeste

In der Terminologie von Puppet werden die clients des Puppet-Servers verbunden Nodes (nodes). Die Konfiguration für die Nodes wird in Manifeste geschrieben in einer speziellen Programmiersprache – Puppet DSL.

Puppet DSL ist eine deklarative Sprache. Darin wird der gewünschte Zustand eines Nodes in Form von Erklärungen zu einzelnen Ressourcen beschrieben, zum Beispiel:

  • Eine Datei existiert und hat einen bestimmten Inhalt.
  • Ein Paket ist installiert.
  • Ein Dienst ist gestartet.

Ressourcen können miteinander verknüpft sein:

  • Es gibt Abhängigkeiten, die den Anwendungsreihenfolge von Ressourcen beeinflussen.
    Zum Beispiel: „Installiere zuerst das Paket, korrigiere die Konfigurationsdatei, starte dann den Dienst.“
  • Es gibt Benachrichtigungen – wenn sich eine Ressource ändert, sendet sie Benachrichtigungen an die darauf abonnierte Ressourcen.
    Wenn sich zum Beispiel die Konfigurationsdatei ändert, kann der Dienst automatisch neu gestartet werden.

Darüber hinaus gibt es in Puppet DSL Funktionen und Variablen sowie bedingte Operatoren und Selector. Verschiedene Templatesysteme wie EPP und ERB werden ebenfalls unterstützt.

Puppet ist in Ruby geschrieben, daher stammen viele Konstrukte und Begriffe von dort. Ruby ermöglicht es, Puppet zu erweitern – komplizierte Logik, neue Ressourcentypen und Funktionen zu integrieren.

Während der Arbeit von Puppet werden die Manifeste für jede einzelne Node auf dem Server im Katalog kompiliert. Verzeichnis — dies ist eine Liste von Ressourcen und ihren Beziehungen nach der Berechnung der Werte von Funktionen, Variablen und der Aufdeckung von bedingten Anweisungen.

Syntax und Code-Stil

Hier sind die Abschnitte der offiziellen Dokumentation, die Ihnen helfen, die Syntax zu verstehen, falls die bereitgestellten Beispiele nicht ausreichen:

Hier ist ein Beispiel dafür, wie ein Manifest aussieht:

# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
  # Конфигурация по сути является перечислением ресурсов и их параметров.
  #
  # У каждого ресурса есть тип и название.
  #
  # Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
  #
  # Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
  # Про разные типы ресурсов написано ниже.
  #
  # После типа в фигурных скобках пишется название ресурса, потом двоеточие,
  # дальше идёт опциональное перечисление параметров ресурса и их значений.
  # Значения параметров указываются через т.н. hash rocket (=>).
  resource { 'title':
    param1 => value1,
    param2 => value2,
    param3 => value3,
  }
}

Einrückungen und Zeilenumbrüche sind kein obligatorischer Teil des Manifests, es gibt jedoch einen empfohlenen Styleguide.Zusammenfassung:

  • Doppelter Rückzug, Tabs werden nicht verwendet.
  • Die geschweiften Klammern werden durch ein Leerzeichen getrennt, der Doppelpunkt wird nicht durch ein Leerzeichen getrennt.
  • Kommas nach jedem Parameter, inklusive dem letzten. Jeder Parameter steht in einer eigenen Zeile. Eine Ausnahme wird für den Fall ohne Parameter und mit einem Parameter gemacht: Kann in einer Zeile und ohne Komma geschrieben werden (d.h. resource { 'title': } und resource { 'title': param => value }).
  • Die Pfeile bei den Parametern müssen auf derselben Ebene stehen.
  • Die Pfeile der Ressourcenbeziehungen stehen davor.

Dateispeicherorte auf dem Puppetserver

Für weitere Erklärungen führe ich den Begriff "Stammverzeichnis" ein. Das Stammverzeichnis ist das Verzeichnis, das die Puppet-Konfiguration für eine bestimmte Node enthält.

Das Stammverzeichnis variiert je nach Puppet-Version und Verwendung von Umgebungen. Umgebungen sind unabhängige Konfigurationssätze, die in separaten Verzeichnissen gespeichert werden. Sie werden in der Regel in Kombination mit Git verwendet, in diesem Fall werden Umgebungen aus Git-Branches erstellt. Dementsprechend befindet sich jede Node in einer bestimmten Umgebung. Dies wird entweder auf der Node selbst oder in ENC konfiguriert, über das ich im nächsten Artikel sprechen werde.

  • In der dritten Version ("alter Puppet") war das Basisverzeichnis /etc/puppet. Die Verwendung von Umgebungen ist optional — wir verwenden sie beispielsweise nicht mit dem alten Puppet. Wenn Umgebungen verwendet werden, wird in der Regel in /etc/puppet/environments, das Stammverzeichnis dem Verzeichnis der Umgebung entsprechen. Wenn Umgebungen nicht verwendet werden, ist das Stammverzeichnis das Basisverzeichnis.
  • Seit der vierten Version ("neuer Puppet") ist die Verwendung von Umgebungen verpflichtend, und das Basisverzeichnis wurde verschoben. /etc/puppetlabs/code. Entsprechend werden die Umgebungen in /etc/puppetlabs/code/environments, das Wurzelverzeichnis – das Verzeichnis der Umgebung.

Im Wurzelverzeichnis muss ein Unterverzeichnis vorhanden sein manifests, in dem ein oder mehrere Manifestdateien mit der Beschreibung der Knoten liegen. Darüber hinaus sollte es ein Unterverzeichnis geben modules, in dem die Module liegen. Was Module sind, werde ich später erklären. Zudem kann im alten Puppet auch ein Unterverzeichnis vorhanden sein files, in dem verschiedene Dateien liegen, die wir auf die Knoten kopieren. Im neuen Puppet hingegen sind alle Dateien in Module ausgelagert.

Manifestdateien haben die Endung .pp.

Ein paar praxisnahe Beispiele

Die Beschreibung des Knotens und der Ressource darauf

Auf dem Knoten server1.testdomain muss eine Datei erstellt werden /etc/issue mit dem Inhalt Debian GNU/Linux n l. Die Datei muss dem Benutzer und der Gruppe gehören root, die Zugriffsrechte müssen sein 644.

Wir schreiben das Manifest:

node 'server1.testdomain' {   # Konfigurationsblock für den Knoten server1.testdomain
    file { '\/etc\/issue':   # wir beschreiben die Datei \/etc\/issue
        ensure  => present,   # diese Datei muss existieren
        content => 'Debian GNU\/Linux n l',   # sie sollte diesen Inhalt haben
        owner   => root,   # Benutzer-Eigentümer
        group   => root,   # Gruppen-Eigentümer
        mode    => '0644',   # die Datei-Rechte. Sie werden als Zeichenfolge angegeben (in Anführungszeichen), da eine Zahl mit 0 am Anfang sonst als octal interpretiert wird und alles nicht so funktioniert, wie es sollte
    }
}

Die Beziehungen der Ressourcen auf dem Knoten

Auf dem Knoten server2.testdomain muss nginx gestartet sein, das mit der заранее подготовленной Konfiguration arbeitet.

Wir zerlegen die Aufgabe:

  • Es muss das Paket installiert werden nginx.
  • Die Konfigurationsdateien müssen vom Server kopiert werden.
  • Der Dienst muss gestartet werden nginx.
  • Im Falle einer Aktualisierung der Konfiguration muss der Dienst neu gestartet werden.

Wir schreiben das Manifest:

node 'server2.testdomain' {   # Konfigurationsblock, der sich auf den Knoten server2.testdomain bezieht
    package { 'nginx':   # wir beschreiben das Paket nginx
        ensure => installed,   # es muss installiert sein
    }
  # Der direkte Pfeil (->) zeigt an, dass die Ressource darunter
  # nach der oben beschriebenen Ressource erstellt werden muss.
  # Solche Abhängigkeiten sind transitiv.
    -> file { '\/etc\/nginx':   # wir beschreiben die Datei \/etc\/nginx
        ensure  => directory,   # dies sollte ein Verzeichnis sein
        source  => 'puppet:\/\/modules\/example\/nginx-conf',   # sein Inhalt sollte vom Puppet-Server an der angegebenen Adresse kommen
        recurse => true,   # Dateien rekursiv kopieren
        purge   => true,   # überschüssige Dateien (die nicht in der Quelle sind) müssen gelöscht werden
        force   => true,   # überschüssige Verzeichnisse löschen
    }
  # Der ~>-Pfeil zeigt an, dass die Ressource darunter
  # auf Änderungen der oben beschriebenen Ressource reagieren soll.
  # Der ~>-Pfeil schließt den direkten (->) ein.
    ~> service { 'nginx':   # wir beschreiben den Dienst nginx
        ensure => running,   # er sollte laufen
        enable => true,   # er muss beim Systemstart automatisch gestartet werden
    }
  # Wenn eine Ressource vom Typ Dienst eine Benachrichtigung erhält,
  # wird der entsprechende Dienst neu gestartet.
}

Damit das funktioniert, sollte die Dateistruktur auf dem Puppet-Server ungefähr so aussehen:

/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│   └── site.pp
└── modules/
    └── example/
        └── files/
            └── nginx-conf/
                ├── nginx.conf
                ├── mime.types
                └── conf.d/
                    └── some.conf

Ressourcentypen

Eine vollständige Liste der unterstützten Ressourcentypen finden Sie in der Dokumentation, hier werde ich jedoch fünf grundlegende Typen beschreiben, die in meiner Praxis ausreichen, um die meisten Aufgaben zu lösen.

SETUP.EXE: MS-DOS-Executable, NE für MS Windows 3.x

Verwaltet Dateien, Verzeichnisse, symbolische Links, deren Inhalte und Zugriffsrechte.

Parameter:

  • Name der Ressource — Pfad zur Datei (optional)
  • path — Pfad zur Datei (wenn im Namen nicht angegeben)
  • ensure — Dateityp:
    • absent — Datei löschen
    • present — sollte eine Datei beliebigen Typs sein (wenn die Datei nicht vorhanden ist, wird eine gewöhnliche Datei erstellt)
    • SETUP.EXE: MS-DOS-Executable, NE für MS Windows 3.x — gewöhnliche Datei
    • directory — Verzeichnis
    • link — symbolischer Link
  • für Inhalte mit Bildern (alternativer Text — Dateiinhalte (nur für gewöhnliche Dateien geeignet, nicht kombinierbar mit source oder target)
  • source — Verweis auf den Pfad, von dem die Inhalte der Datei kopiert werden sollen (nicht kombinierbar mit für Inhalte mit Bildern (alternativer Text oder target). Kann sowohl in Form einer URI mit dem Schema puppet: verwendet werden (dann werden die Dateien vom Puppet-Server verwendet), als auch mit dem Schema http: (ich hoffe, es ist klar, was in diesem Fall passiert), und sogar mit dem Schema file: oder in Form eines absoluten Pfades ohne Schema (dann wird die Datei vom lokalen Dateisystem auf dem Knoten verwendet)
  • target — wohin soll der Symlink zeigen (kann nicht zusammen mit für Inhalte mit Bildern (alternativer Text oder source)
  • owner — Benutzer, dem die Datei gehören soll
  • Gruppe — Gruppe, der die Datei gehören soll
  • Modus — Berechtigungen für die Datei (in Form einer Zeichenfolge)
  • recurse — aktiviert die rekursive Verarbeitung von Verzeichnissen
  • purge — aktiviert das Löschen von Dateien, die in Puppet nicht beschrieben sind
  • force — aktiviert das Löschen von Verzeichnissen, die in Puppet nicht beschrieben sind

package

Installiert und entfernt Pakete. Kann Benachrichtigungen verarbeiten — installiert das Paket erneut, wenn der Parameter festgelegt ist reinstall_on_refresh.

Parameter:

  • Name der Ressource — Name des Pakets (optional)
  • name — Name des Pakets (wenn im Titel nicht angegeben)
  • provider — Paketmanager, der verwendet werden soll
  • ensure — gewünschter Zustand des Pakets:
    • present, installiert — jede Version installiert
    • latest — neueste Version installiert
    • absent — entfernt (apt-get remove)
    • purged — zusammen mit den Konfigurationsdateien entfernt (apt-get purge)
    • held — die Version des Pakets ist gesperrt (apt-mark hold)
    • jede andere Zeichenfolge — angegebene Version installiert
  • reinstall_on_refresh — wenn true, wird das Paket bei Erhalt der Benachrichtigung neu installiert. Nützlich für distributionsbasierte Systeme, bei denen ein neuer Build der Pakete erforderlich sein kann, wenn sich die Build-Parameter ändern. Standardmäßig false.

service

Verwaltet Dienste. Kann Benachrichtigungen verarbeiten — startet den Dienst neu.

Parameter:

  • Name der Ressource — Dienst, den verwaltet werden soll (optional)
  • name — Dienst, den verwaltet werden soll (wenn im Titel nicht angegeben)
  • ensure — gewünschter Zustand des Dienstes:
    • Laufen — läuft
    • stopped — gestoppt
  • enable — steuert die Möglichkeit, den Dienst zu starten:
    • true — automatische Start aktiviert (systemctl enable)
    • mask — maskiert (systemctl mask)
    • false — automatischer Start deaktiviert (systemctl disable)
  • restart — Befehl zum Neustarten des Dienstes
  • status — Befehl zur Überprüfung des Status des Dienstes
  • hasrestart — angeben, ob das Init-Skript des Dienstes einen Neustart unterstützt. Wenn false und der Parameter angegeben ist restart — wird der Wert dieses Parameters verwendet. Wenn false und der Parameter restart nicht angegeben ist — wird der Dienst gestoppt und zum Neustart gestartet (aber in systemd wird der Befehl verwendet systemctl restart).
  • hasstatus — angeben, ob das Init-Skript des Dienstes den Befehl unterstützt status. Wenn false, dann wird der Wert des Parameters verwendet status. Standardmäßig true.

exec

Startet externe Befehle. Wenn keine Parameter angegeben werden creates, onlyif, unless oder refreshonly, wird der Befehl bei jedem Lauf von Puppet ausgeführt. Kann Benachrichtigungen verarbeiten — führt den Befehl aus.

Parameter:

  • Name der Ressource — Befehl, der ausgeführt werden soll (optional)
  • command — Der Befehl, der ausgeführt werden muss (wenn er nicht im Titel angegeben ist)
  • path — Die Pfade, in denen die ausführbare Datei gesucht werden soll
  • onlyif — Wenn der in diesem Parameter angegebene Befehl mit einem Rückgabewert von null abgeschlossen wird, wird der Hauptbefehl ausgeführt
  • unless — Wenn der in diesem Parameter angegebene Befehl mit einem von null verschiedenen Rückgabewert abgeschlossen wird, wird der Hauptbefehl ausgeführt
  • creates — Wenn die in diesem Parameter angegebene Datei nicht existiert, wird der Hauptbefehl ausgeführt
  • refreshonly — wenn true, wird der Befehl nur dann ausgeführt, wenn dieses exec eine Benachrichtigung von anderen Ressourcen erhält
  • cwd — Das Verzeichnis, aus dem der Befehl ausgeführt wird
  • Benutzer — Der Benutzer, unter dem der Befehl ausgeführt wird
  • provider — Mithilfe von etwas wird der Befehl ausgeführt:
    • posix — Es wird einfach ein Kindprozess erstellt, muss angegeben werden path
    • shell — Der Befehl wird in der Shell ausgeführt /bin/sh, ist optional path, es kann Globbing, Pipes und andere Shell-Features verwendet werden. Normalerweise wird dies automatisch bestimmt, wenn es verschiedene Sonderzeichen gibt (|, ;, &&, || und so weiter).

Cron

Verwaltet Cron-Jobs.

Parameter:

  • Name der Ressource — Einfach eine eindeutige ID
  • ensure — Der Status des Cron-Jobs:
    • present — Erstellen, wenn nicht vorhanden
    • absent — Löschen, wenn vorhanden
  • command — Welchen Befehl ausführen
  • Umgebung — In welcher Umgebung der Befehl ausgeführt werden soll (Liste der Umgebungsvariablen und deren Werte durch =)
  • Benutzer — Von welchem Benutzer der Befehl ausgeführt wird
  • Minute, Stunde, Wochentag, Monat, Tag im Monat — Wann der Cron ausgeführt wird. Wenn eines dieser Attribute nicht angegeben ist, ist sein Wert in der Crontab *.

In Puppet 6.0 Cron sozusagen aus der Box entfernt in puppetserver, deshalb gibt es keine Dokumentation auf der allgemeinen Website. Aber es ist in der Box in puppet-agent, daher muss es nicht separat installiert werden. Die Dokumentation dazu kann man in der Dokumentation für die fünfte Version von Puppet, oder auf GitHub.

Über Ressourcen im Allgemeinen

Anforderungen an die Eindeutigkeit von Ressourcen

Der häufigste Fehler, mit dem wir konfrontiert sind — Doppelte Deklaration. Dieser Fehler tritt auf, wenn zwei oder mehr Ressourcen des gleichen Typs mit demselben Namen in das Verzeichnis gelangen.

Deshalb noch einmal: In Manifests für einen Knoten dürfen keine Ressourcen des gleichen Typs mit demselben Namen (title) vorhanden sein!

Manchmal ist es notwendig, Pakete mit demselben Namen, aber mit unterschiedlichen Paketmanagern zu installieren. In diesem Fall muss der Parameter name, um Fehler zu vermeiden:

package { 'ruby-mysql':
  ensure   => installiert,
  name     => 'mysql',
  provider => 'gem',
}
package { 'python-mysql':
  ensure   => installiert,
  name     => 'mysql',
  provider => 'pip',
}

Bei anderen Ressourcentypen gibt es ähnliche Parameter, die helfen, Duplikate zu vermeiden — name arbeiten service, command arbeiten exec, und so weiter.

Metaparameter

Jeder Ressourcentyp hat einige spezielle Parameter, unabhängig von seiner Entität.

Vollständige Liste der Metaparameter in der Puppet-Dokumentation.

Kurze Liste:

  • require — in diesem Parameter wird angegeben, von welchen Ressourcen diese Ressource abhängt.
  • before — in diesem Parameter wird angegeben, welche Ressourcen von dieser Ressource abhängen.
  • subscribe — in diesem Parameter wird angegeben, von welchen Ressourcen diese Ressource Benachrichtigungen erhält.
  • notify — in diesem Parameter wird angegeben, welche Ressourcen Benachrichtigungen von dieser Ressource erhalten.

Alle genannten Metaparameter akzeptieren entweder einen Verweis auf eine Ressource oder ein Array von Verweisen in eckigen Klammern.

Verweise auf Ressourcen

Ein Verweis auf eine Ressource ist einfach eine Erwähnung der Ressource. Sie werden hauptsächlich verwendet, um Abhängigkeiten anzugeben. Ein Verweis auf eine nicht existierende Ressource führt zu einem Kompilierungsfehler.

Die Syntax eines Verweises ist wie folgt: Ressourcentyp mit Großbuchstaben (wenn im Namen des Typs doppelte Doppelpunkt enthalten sind, wird jeder Teil des Namens zwischen den Doppelpunkten großgeschrieben), danach in eckigen Klammern der Name der Ressource (der Name bleibt unverändert!). Es dürfen keine Leerzeichen sein, die eckigen Klammern folgen sofort nach dem Namen des Typs.

Beispiel:

file { '/file1': ensure => present }
file { '/file2':
  ensure => directory,
  before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']

Abhängigkeiten und Benachrichtigungen

Dokumentation hier.

Wie bereits erwähnt, sind einfache Abhängigkeiten zwischen Ressourcen transitiv. Seien Sie vorsichtig beim Festlegen von Abhängigkeiten — es können zirkuläre Abhängigkeiten entstehen, was zu einem Kompilierungsfehler führt.

Im Gegensatz zu Abhängigkeiten sind Benachrichtigungen nicht transitiv. Für Benachrichtigungen gelten die folgenden Regeln:

  • Wenn eine Ressource eine Benachrichtigung erhält, wird sie aktualisiert. Die Maßnahmen bei der Aktualisierung hängen vom Ressourcentyp ab — exec führt einen Befehl aus, service startet einen Dienst neu, package stellt ein Paket neu bereit. Wenn für eine Ressource keine Aktion bei der Aktualisierung definiert ist, passiert nichts.
  • Bei einem Durchlauf wird die Ressource von Puppet nicht mehr als einmal aktualisiert. Das ist möglich, da die Benachrichtigungen Abhängigkeiten enthalten und der Abhängigkeitsgraph keine Zyklen aufweist.
  • Wenn Puppet den Zustand einer Ressource ändert, sendet die Ressource Benachrichtigungen an alle Ressourcensubscriber.
  • Wenn eine Ressource aktualisiert wird, sendet sie Benachrichtigungen an alle, die auf sie abonniert sind.

Behandlung nicht angegebener Parameter

Im Allgemeinen wird, wenn ein Parameter einer Ressource keinen Standardwert hat und dieser Parameter nicht im Manifest angegeben ist, Puppet dieses Attribut der entsprechenden Ressource auf dem Knoten nicht ändern. Beispielsweise, wenn bei einer Ressource des Typs SETUP.EXE: MS-DOS-Executable, NE für MS Windows 3.x der Parameter nicht angegeben ist owner, dann wird Puppet den Eigentümer der entsprechenden Datei nicht ändern.

Einführung in Klassen, Variablen und Definitionen

Angenommen, wir haben mehrere Knoten, auf denen der gleiche Teil der Konfiguration vorhanden ist, aber auch Unterschiede bestehen – andernfalls könnten wir alles in einem Block beschreiben. node {}. Natürlich kann man einfach die identischen Teile der Konfiguration kopieren, aber im Allgemeinen ist das eine schlechte Lösung – die Konfiguration wächst, und beim Ändern des gemeinsamen Teils der Konfiguration muss man dasselbe an vielen Orten anpassen. Dabei kann man leicht Fehler machen, und das Prinzip DRY (Don’t Repeat Yourself) wurde nicht ohne Grund entwickelt.

Um ein solches Problem zu lösen, gibt es eine Konstruktion wie Klasse.

Klassen

Klasse – das ist ein benannter Block von Puppet-Code. Klassen dienen der Wiederverwendbarkeit von Code.

Zunächst muss die Klasse beschrieben werden. Die Beschreibung selbst fügt keine Ressourcen hinzu. Die Klasse wird in Manifesten beschrieben:

# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
    ...
}

Danach kann die Klasse verwendet werden:

# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше

Ein Beispiel aus der vorherigen Aufgabe – wir lagern die Installation und Konfiguration von nginx in eine Klasse aus:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recurse => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    include nginx_example
}

Variablen

Die Klasse aus dem vorherigen Beispiel ist ganz und gar nicht flexibel, da sie immer dieselbe nginx-Konfiguration liefert. Lassen Sie uns dafür sorgen, dass der Pfad zur Konfiguration variabel wird, sodass diese Klasse zur Installation von nginx mit beliebiger Konfiguration verwendet werden kann.

Das kann man mit Variablen.

Hinweis: Variablen in Puppet sind unveränderlich!

Darüber hinaus kann auf eine Variable nur zugegriffen werden, nachdem sie deklariert wurde, sonst hat die Variable den Wert undef.

Beispiel zur Arbeit mit Variablen:

# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"

In Puppet gibt es Namensräume, und Variablen haben entsprechend Gültigkeitsbereiche: Eine Variable mit demselben Namen kann in verschiedenen Namensräumen definiert sein. Bei der Auflösung des Wertes einer Variablen wird die Variable im aktuellen Namensraum gesucht, dann im umgebenden und so weiter.

Beispiele für Namensräume:

  • global — dort landen Variablen außerhalb der Klassendefinition oder des Knotens;
  • Namensraum des Knotens in der Knotendefinition;
  • Namensraum der Klasse in der Klassendefinition.

Um Mehrdeutigkeit beim Zugriff auf eine Variable zu vermeiden, kann der Namensraum im Variablennamen angegeben werden:

# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var

Wir vereinbaren, dass der Pfad zur nginx-Konfiguration in der Variable $nginx_conf_sourceliegt. Dann sieht die Klasse wie folgt aus:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # hier verwenden wir die Variable anstelle eines festen Strings
        recurse => true,
        purge  => true,
        force  => true,
    }
    -> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    $nginx_conf_source = 'puppet:////modules/example/nginx-conf'
    include nginx_example
}

Das angegebene Beispiel ist schlecht, da es ein gewisses „geheimes Wissen“ darüber gibt, dass irgendwo innerhalb der Klasse eine Variable mit einem bestimmten Namen verwendet wird. Es ist viel richtiger, dieses Wissen allgemein zu machen — Klassen können Parameter haben.

Klasseneigenschaften sind Variablen im Namensraum der Klasse, die im Klassenkopf definiert sind und wie normale Variablen im Klassentext verwendet werden können. Die Werte der Parameter werden bei der Verwendung der Klasse im Manifest angegeben.

Einem Parameter kann ein Standardwert zugewiesen werden. Wenn ein Parameter keinen Standardwert hat und kein Wert bei der Verwendung angegeben wird, führt dies zu einem Kompilierungsfehler.

Lassen Sie uns die Klasse aus dem obigen Beispiel parametrisieren und zwei Parameter hinzufügen: den ersten, obligatorischen — den Pfad zur Konfiguration, und den zweiten, optionalen — den Namen des Pakets mit nginx (zum Beispiel gibt es in Debian die Pakete nginx, nginx-light, nginx-full).

# переменные описываются сразу после имени класса в круглых скобках
class nginx_example (
  $conf_source,
  $package_name = 'nginx-light', # параметр со значением по умолчанию
) {
  package { $package_name:
    ensure => installed,
  }
  -> file { '/etc/nginx':
    ensure  => directory,
    source  => $conf_source,
    recurse => true,
    purge   => true,
    force   => true,
  }
  ~> service { 'nginx':
    ensure => running,
    enable => true,
  }
}

node 'server2.testdomain' {
  # если мы хотим задать параметры класса, функция include не подойдёт* — нужно использовать resource-style declaration
  # *на самом деле подойдёт, но про это расскажу в следующей серии. Ключевое слово "Hiera".
  class { 'nginx_example':
    conf_source => 'puppet:///modules/example/nginx-conf',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

In Puppet sind Variablen typisiert. Es gibt viele Datentypen. Datentypen werden normalerweise zur Validierung von Werten verwendet, die an Klassen und Defines übergeben werden. Wenn der übergebene Parameter nicht dem angegebenen Typ entspricht, tritt ein Kompilierungsfehler auf.

Der Typ wird direkt vor dem Parametername geschrieben:

class example (
  String $param1,
  Integer $param2,
  Array $param3,
  Hash $param4,
  Hash[String, String] $param5,
) {
  ...
}

Klassen: include classname vs class{‘classname’:}

Jede Klasse ist ein Ressourcentyp class. Wie bei anderen Ressourcentypen kann nicht mehr als eine Instanz derselben Klasse auf einem Knoten vorhanden sein.

Wenn man versucht, dieselbe Klasse zweimal auf denselben Knoten hinzuzufügen, indem man class { 'classname':} verwendet (egal, ob mit unterschiedlichen oder identischen Parametern), tritt ein Kompilierungsfehler auf. Im Fall der Verwendung einer Klasse im Ressourcestil können alle Parameter jedoch sofort im Manifest explizit angegeben werden.

Wenn man jedoch include, kann die Klasse beliebig oft hinzugefügt werden. Der Grund dafür ist, dass include eine idempotente Funktion ist, die prüft, ob die Klasse im Katalog vorhanden ist. Wenn die Klasse nicht im Katalog vorhanden ist, wird sie hinzugefügt; wenn sie bereits vorhanden ist, passiert nichts. Doch bei der Verwendung von include können die Parameter der Klasse nicht während der Klassendeklaration festgelegt werden – alle erforderlichen Parameter müssen aus einer externen Datenquelle – Hiera oder ENC – bereitgestellt werden. Wir werden in dem nächsten Artikel darüber sprechen.

Defines

Wie im vorherigen Abschnitt erwähnt, kann dieselbe Klasse auf einem Knoten nicht mehr als einmal vorhanden sein. In einigen Fällen ist es jedoch erforderlich, denselben Codeblock mit unterschiedlichen Parametern auf demselben Knoten anzuwenden. Mit anderen Worten, es besteht Bedarf an einem eigenen Ressourcentyp.

Um beispielsweise ein PHP-Modul zu installieren, machen wir bei Avito Folgendes:

  1. Wir installieren das Paket mit diesem Modul.
  2. Wir erstellen eine Konfigurationsdatei für dieses Modul.
  3. Wir erstellen einen Symlink zur Konfiguration für php-fpm.
  4. Wir erstellen einen Symlink zur Konfiguration für php cli.

In solchen Fällen wird eine Konstruktion wie define (define, definierter Typ, definierter Ressourcentyp) verwendet. Define ähnelt einer Klasse, hat jedoch Unterschiede: Erstens ist jede Define ein Ressourcentyp und kein Ressourcenobjekt; zweitens hat jede Define einen impliziten Parameter $title, in den der Name der Ressource bei ihrer Deklaration eingeht. Genau wie bei Klassen muss ein Define zuerst beschrieben werden, bevor es verwendet werden kann.

Ein vereinfachtes Beispiel mit einem Modul für PHP:

define php74::module (
  $php_module_name = $title,
  $php_package_name = "php7.4-${title}",
  $version = 'installed',
  $priority = '20',
  $data = "extension=${title}.son",
  $php_module_path = '\/etc\/php\/7.4\/mods-available',
) {
  package { $php_package_name:
    ensure          => $version,
    install_options => ['-o', 'DPkg::NoTriggers=true'],  # Debian-Pakettriggers erzeugen selbst Symlinks und starten den php-fpm-Service neu - das brauchen wir nicht, da wir sowohl mit Symlinks als auch mit dem Service über Puppet umgehen.
  }
  -> file { "${php_module_path}\/${php_module_name}.ini":
    ensure  => $ensure,
    content => $data,
  }
  file { "\/etc\/php\/7.4\/cli\/conf.d\/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}\/${php_module_name}.ini",
  }
  file { "\/etc\/php\/7.4\/fpm\/conf.d\/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}\/${php_module_name}.ini",
  }
}

node server3.testdomain {
  php74::module { 'sqlite3': }
  php74::module { 'amqp': php_package_name => 'php-amqp' }
  php74::module { 'msgpack': priority => '10' }
}

Im Define ist es am einfachsten, den Fehler "Duplicate declaration" zu fangen. Das passiert, wenn im Define eine Ressource mit einem konstanten Namen vorhanden ist und auf einem Knoten zwei oder mehr Instanzen dieses Defines existieren.

Dagegen kann man sich einfach schützen: Alle Ressourcen innerhalb eines Defines sollten einen Namen haben, der von $title. Als Alternative – idempotente Hinzufügung von Ressourcen, im einfachsten Fall genügt es, die für alle Instanzen des Defines gemeinsamen Ressourcen in eine separate Klasse auszulagern und diese Klasse im Define einzufügen – die Funktion include ist idempotent.

Es gibt auch andere Möglichkeiten, Idempotenz beim Hinzufügen von Ressourcen zu erreichen, insbesondere durch die Verwendung von Funktionen defined und ensure_resources, aber darüber werde ich in der nächsten Folge sprechen.

Abhängigkeiten und Benachrichtigungen für Klassen und Defines

Klassen und Defines fügen folgende Regeln zur Verarbeitung von Abhängigkeiten und Benachrichtigungen hinzu:

  • Eine Abhängigkeit von einer Klasse/Define fügt Abhängigkeiten von allen Ressourcen der Klasse/des Defines hinzu;
  • Die Abhängigkeit einer Klasse/des Defines fügt allen Ressourcen der Klasse/des Defines Abhängigkeiten hinzu;
  • Die Benachrichtigung einer Klasse/des Defines benachrichtigt alle Ressourcen der Klasse/des Defines;
  • Die Abonnierung einer Klasse/des Defines abonniert alle Ressourcen der Klasse/des Defines.

Bedingte Operatoren und Selektoren

Dokumentation hier.

if

Hier ist alles einfach:

if AUSDRUCK1 {
  ...
} elsif AUSDRUCK2 {
  ...
} else {
  ...
}

unless

unless — das ist das Gegenteil von if: Ein Block von Code wird ausgeführt, wenn der Ausdruck falsch ist.

unless AUSDRUCK {
  ...
}

Fall

Hier ist auch nichts kompliziert. Als Werte können gewöhnliche Werte (Strings, Zahlen usw.), reguläre Ausdrücke sowie Datentypen verwendet werden.

case AUSDRUCK {
  WERT1: { ... }
  WERT2, WERT3: { ... }
  standard: { ... }
}

Selektoren

Ein Selektor ist eine sprachliche Konstruktion, die ähnlich ist wie Fall, aber anstatt einen Codeblock auszuführen, gibt er einen Wert zurück.

$var = $othervar ? { 'val1' => 1, 'val2' => 2, standard => 3 }

Module

Wenn die Konfiguration klein ist, kann sie leicht in einem Manifest untergebracht werden. Aber je mehr Konfigurationen wir beschreiben, desto mehr Klassen und Knoten gibt es im Manifest, es wächst, und es wird unhandlich.

Außerdem gibt es das Problem der Wiederverwendbarkeit von Code – wenn der gesamte Code in einem Manifest ist, ist es schwierig, diesen Code mit anderen zu teilen. Um diese beiden Probleme zu lösen, gibt es in Puppet ein solches Konzept wie Module.

Module – das sind Sammlungen von Klassen, Defines und anderen Puppet-Entitäten, die in ein separates Verzeichnis ausgelagert sind. Mit anderen Worten, ein Modul ist ein unabhängiges Stück Puppet-Logik. Zum Beispiel kann es ein Modul für die Arbeit mit nginx geben, und darin wird nur das gespeichert, was für die Arbeit mit nginx erforderlich ist, oder ein Modul für die Arbeit mit PHP und so weiter.

Module werden versioniert, außerdem werden die Abhängigkeiten zwischen den Modulen unterstützt. Es gibt ein offenes Repository für Module – Puppet Forge.

Auf dem Puppet-Server liegen die Module im Unterverzeichnis modules des Stammverzeichnisses. Innerhalb jedes Moduls gibt es eine standardmäßige Verzeichnisstruktur – manifests, files, templates, lib und so weiter.

Dateistruktur im Modul

Im Stammverzeichnis des Moduls können folgende Verzeichnisse mit sprechenden Namen vorhanden sein:

  • manifests – hier liegen die Manifeste
  • files – hier liegen die Dateien
  • templates – hier liegen die Vorlagen
  • lib – hier liegt der Ruby-Code

Dies ist keine vollständige Liste von Verzeichnissen und Dateien, aber für diesen Artikel reicht es vorerst aus.

Ressourcennamen und Dateinamen im Modul

Dokumentation hier.

Ressourcen (Klassen, Defines) im Modul können nicht beliebig benannt werden. Außerdem gibt es eine direkte Entsprechung zwischen dem Namen der Ressource und dem Dateinamen, in dem Puppet die Beschreibung dieser Ressource suchen wird. Wenn die Namensregeln verletzt werden, findet Puppet einfach die Beschreibung der Ressourcen nicht, und es entsteht ein Kompilierungsfehler.

Die Regeln sind einfach:

  • Alle Ressourcen im Modul müssen im Namensraum des Moduls liegen. Wenn das Modul heißt foo, dann müssen alle Ressourcen darin genannt werden foo::, oder einfach nur foo.
  • Eine Ressource mit dem Namen des Moduls muss in der Datei init.pp.
  • Für die anderen Ressourcen gilt folgende Namensschema für die Dateien:
    • Der Präfix mit dem Namen des Moduls wird weggelassen
    • Alle doppelten Doppelpunkte, sofern vorhanden, werden durch Schrägstriche ersetzt.
    • Eine Erweiterung wird hinzugefügt. .pp

Ich werde es am Beispiel demonstrieren. Angenommen, ich schreibe ein Modul. nginxIn diesem Modul gibt es folgende Ressourcen:

  • Klasse nginx im Manifest beschrieben. init.pp;
  • Klasse nginx::service im Manifest beschrieben. service.pp;
  • define nginx::server im Manifest beschrieben. server.pp;
  • define nginx::server::location im Manifest beschrieben. server/location.pp.

Vorlagen

Sie wissen sicherlich selbst, was Vorlagen sind, ich werde das hier nicht im Detail ausführen. Aber für den Fall lasse ich einen Link zur Wikipedia..

Wie man Vorlagen verwendet: Der Wert der Vorlage kann mithilfe der Funktion templateenthüllt werden, der der Pfad zur Vorlage übergeben wird. Für Ressourcen vom Typ SETUP.EXE: MS-DOS-Executable, NE für MS Windows 3.x verwenden wir zusammen mit dem Parameter. für Inhalte mit Bildern (alternativer TextZum Beispiel so:

file { '/tmp/example': content => template('modulename/templatename.erb')

Ein Pfad vom Typ / verweist auf die Datei /modules//templates/.

Außerdem gibt es die Funktion inline_template – sie erhält den Text der Vorlage und nicht den Dateinamen.

Innerhalb von Vorlagen können alle Puppet-Variablen im aktuellen Geltungsbereich verwendet werden.

Puppet unterstützt Vorlagen im ERB- und EPP-Format:

Kurz zu ERB

Steuerkonstruktionen:

  • <%= ВЫРАЖЕНИЕ %> – fügt den Wert des Ausdrucks ein.
  • <% ВЫРАЖЕНИЕ %> – berechnet den Wert des Ausdrucks (ohne ihn einzufügen). Hier kommen gewöhnlich bedingte Operatoren (if), Schleifen (each) zum Einsatz.
  • <%# КОММЕНТАРИЙ %>

Ausdrücke in ERB werden in Ruby geschrieben (eigentlich ist ERB Embedded Ruby).

Um auf Variablen aus dem Manifest zuzugreifen, muss man @ an den Variablennamen anhängen. Um den Zeilenumbruch, der nach der Kontrollstruktur erscheint, zu entfernen, muss man den schließenden Tag verwenden. -%>.

Beispiel für die Verwendung einer Vorlage

Angenommen, ich schreibe ein Modul zur Verwaltung von ZooKeeper. Die Klasse, die für die Erstellung der Konfiguration verantwortlich ist, sieht etwa so aus:

class zookeeper::configure (\n  Array[String] $nodes,\n  Integer $port_client,\n  Integer $port_quorum,\n  Integer $port_leader,\n  Hash[String, Any] $properties,\n  String $datadir,\n) {\n  file { '/etc/zookeeper/conf/zoo.cfg':\n    ensure  => present,\n    content => template('zookeeper/zoo.cfg.erb'),\n  }\n}

Und die entsprechende Vorlage zoo.cfg.erb sieht so aus:

0 -%>\n\nserver.=::;\n\n\n\ndataDir=\n\n\n=\n

Fakten und integrierte Variablen

Oft hängt ein bestimmter Teil der Konfiguration davon ab, was gerade auf dem Knoten passiert. Beispielsweise muss je nach installiertem Debian-Releases eine bestimmte Version des Pakets installiert werden. Man kann all das manuell verfolgen, indem man die Manifeste bei Änderungen der Knoten umschreibt. Aber das ist ein unseriöser Ansatz; Automatisierung ist viel besser.

Um Informationen über Knoten in Puppet zu erhalten, gibt es einen Mechanismus namens Fakten. Fakten sind Informationen über den Knoten, die in Manifeste als normale Variablen im globalen Namensraum verfügbar sind. Zum Beispiel der Hostname, die Version des Betriebssystems, die Architektur des Prozessors, die Liste der Benutzer, die Liste der Netzwerkinterfaces und deren Adressen sowie vieles, vieles mehr. Fakten sind in Manifeste und Vorlagen als normale Variablen verfügbar.

Beispiel für die Arbeit mit Fakten:

notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# Der Ressourcentyp notify gibt einfach eine Nachricht ins Log aus

Formal gesprochen hat ein Fakt einen Namen (String) und einen Wert (verschiedene Typen sind verfügbar: Strings, Arrays, Dictionaries). Es gibt eine Menge integrierter Fakten. Man kann auch eigene schreiben. Faktensammler werden als Funktionen in Ruby, oder als ausführbare Dateien. Fakten können auch in Form von Textdateien mit Daten auf den Knoten dargestellt werden.

Während der Ausführung kopiert der Puppet-Agent zunächst alle verfügbaren Faktensammler vom Puppet-Server auf den Knoten, führt sie dann aus und sendet die gesammelten Fakten an den Server; erst danach beginnt der Server mit der Kompilierung des Katalogs.

Fakten in Form von ausführbaren Dateien

Solche Fakten werden in Module im Verzeichnis facts.dgelegt. Selbstverständlich müssen die Dateien ausführbar sein. Beim Ausführen sollten sie Informationen entweder im YAML-Format oder im Format „ Schlüssel=Wert“ auf die Standardausgabe ausgeben.

Denken Sie daran, dass Fakten für alle Knoten gelten, die von dem Puppet-Server verwaltet werden, auf den Ihr Modul bereitgestellt wird. Achten Sie in Ihrem Skript darauf, dass die benötigten Programme und Dateien für die Funktion Ihres Fakts im System vorhanden sind.

#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'

Fakten in Ruby

Solche Fakten werden in Module im Verzeichnis lib/facter.

# всё начинается с вызова функции Facter.add с именем факта и блоком кода
Facter.add('ladvd') do
# в блоках confine описываются условия применимости факта — код внутри блока должен вернуть true, иначе значение факта не вычисляется и не возвращается
  confine do
    Facter::Core::Execution.which('ladvdc') # проверим, что в PATH есть такой исполняемый файл
  end
  confine do
    File.socket?('/var/run/ladvd.sock') # проверим, что есть такой UNIX-domain socket
  end
# в блоке setcode происходит собственно вычисление значения факта
  setcode do
    hash = {}
    if (out = Facter::Core::Execution.execute('ladvdc -b'))
      out.split.each do |l|
        line = l.split('=')
        next if line.length != 2
        name, value = line
        hash[name.strip.downcase.tr(' ', '_')] = value.strip.chomp(''').reverse.chomp(''').reverse
      end
    end
    hash  # значение последнего выражения в блоке setcode является значением факта
  end
end

Textfakten

Solche Fakten werden auf Knoten im Verzeichnis /etc/facter/facts.d im alten Puppet oder /etc/puppetlabs/facts.d im neuen Puppet.

examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue

Zugriff auf Fakten

Es gibt zwei Möglichkeiten, auf die Fakten zuzugreifen:

  • über die Datenbank $facts: $facts['fqdn'];
  • indem Sie den Namen des Fakts als Variablenname verwenden: $fqdn.

Am besten verwenden Sie die Datenbank $facts, und es ist noch besser, den globalen Namensraum anzugeben ($::facts).

Hier ist der benötigte Abschnitt der Dokumentation.

Eingebaute Variablen

Neben den Fakten gibt es auch einige Variablen, die im globalen Namensraum verfügbar sind.

  • trusted facts – Variablen, die aus dem Clientzertifikat stammen (da das Zertifikat normalerweise auf dem Puppet-Server ausgestellt wird, kann der Agent sein Zertifikat nicht einfach so ändern, deshalb sind die Variablen ‚vertrauenswürdig‘): Name des Zertifikats, Hostname und Domäne, Erweiterungen des Zertifikats.
  • server facts – Variablen, die sich auf Informationen über den Server beziehen – Version, Name, IP-Adresse des Servers, Umgebung.
  • agent facts – Variablen, die direkt vom Puppet-Agenten hinzugefügt werden, nicht vom Facter – Name des Zertifikats, Version des Agents, Version von Puppet.
  • master variables – Variablen des Puppet-Masters (sic!). Dort ist ungefähr das Gleiche wie in server facts, plus verfügbar sind die Werte der Konfigurationsparameter.
  • compiler variables – Compiler-Variablen, die sich in jedem Gültigkeitsbereich unterscheiden: der Name des aktuellen Moduls und der Name des Moduls, in dem auf das aktuelle Objekt zugegriffen wurde. Sie können sie beispielsweise verwenden, um zu überprüfen, dass Ihre privaten Klassen nicht direkt aus anderen Modulen verwendet werden.

Zusatz 1: Wie startet und debuggiert man das alles?

In dem Artikel gab es viele Beispiele für Puppet-Code, aber es wurde nicht erklärt, wie man diesen Code ausführt. Na gut, ich werde es nachholen.

Für die Arbeit mit Puppet benötigt man mindestens einen Agenten, aber in den meisten Fällen wird auch ein Server erforderlich sein.

Agent

Mindestens ab Version 5 enthält das Puppet-Agent-Paket aus dem offiziellen Repository von Puppetlabs alle Abhängigkeiten (ruby und die entsprechenden Gems), sodass es keine Schwierigkeiten bei der Installation gibt (ich spreche von Debian-basierten Distributionen – RPM-basierte Distributionen verwenden wir nicht).

Im einfachsten Fall reicht es aus, den Puppet-Agent im One-Server-Modus zu starten: Vorausgesetzt, dass der Puppet-Code auf den Knoten kopiert wurde, führen Sie aus puppet apply:

atikhonov@atikhonov ~\/puppet-test $ cat helloworld.pp \nnode default {\n    notify { 'Hello world!': }\n}\natikhonov@atikhonov ~\/puppet-test $ puppet apply helloworld.pp \nHinweis: Kompilierter Katalog für atikhonov.localdomain in der Umgebung Produktion in 0,01 Sekunden\nHinweis: Hello world!\nHinweis: \/Stage[main]\/Main\/Node[default]\/Notify[Hello world!]\/message: definiert 'message' als 'Hello world!'\nHinweis: Katalog in 0,01 Sekunden angewendet

Es ist natürlich besser, den Server hochzufahren und die Agenten auf den Knoten im Dämonenmodus zu starten - dann wenden sie alle halbe Stunde die vom Server heruntergeladene Konfiguration an.

Man kann ein Push-Modell simulieren - gehen Sie zu dem Knoten, der Sie interessiert, und starten Sie sudo puppet agent -t. Der Schlüssel -t (--test) aktiviert tatsächlich mehrere Optionen, die auch einzeln aktiviert werden können. Zu diesen Optionen gehören:

  • nicht im Dämonenmodus zu arbeiten (standardmäßig wird der Agent im Dämonenmodus gestartet);
  • die Arbeit nach der Anwendung des Katalogs zu beenden (standardmäßig läuft der Agent weiter und wendet die Konfiguration alle halbe Stunde an);
  • ein detailliertes Arbeitsprotokoll zu schreiben;
  • Änderungen in Dateien anzuzeigen.

Der Agent hat einen Modus ohne Änderungen - man kann ihn verwenden, wenn man sich nicht sicher ist, ob die geschriebene Konfiguration korrekt ist, und überprüfen möchte, was der Agent während des Betriebs ändern würde. Dieser Modus wird mit dem Parameter --noop in der Kommandozeile aktiviert: sudo puppet agent -t --noop.

Darüber hinaus kann ein Debug-Log aktiviert werden - darin protokolliert Puppet alle Aktionen, die es ausführt: über die Ressource, die es gerade verarbeitet, die Parameter dieser Ressource, welche Programme es startet. Selbstverständlich ist dies ein Parameter --debug.

Server

Eine vollständige Einrichtung des Puppet-Servers und das Deployment von Code darauf werde ich in diesem Artikel nicht behandeln, ich sage nur, dass eine funktionsfähige Version des Servers direkt aus der Box installiert wird, die keine zusätzliche Konfiguration erfordert, um unter Bedingungen einer geringen Anzahl von Knoten (sagen wir bis zu hundert) zu arbeiten. Eine größere Anzahl von Knoten erfordert bereits Feinabstimmung - standardmäßig startet der Puppet-Server nicht mehr als vier Worker; für eine höhere Leistung muss deren Anzahl erhöht werden, und man darf nicht vergessen, die Speicherkapazitätsgrenzen zu erhöhen, sonst wird der Server die meiste Zeit mit Garbage Collection beschäftigt sein.

Code-Deployment - wenn es schnell und unkompliziert sein muss, schauen Sie sich (auf r10k)[https://github.com/puppetlabs/r10k], für kleinere Installationen sollte es völlig ausreichen.

Ergänzung 2: Empfehlungen zum Schreiben von Code

  1. Lagern Sie alle Logik in Klassen und Defines aus.
  2. Halten Sie Klassen und Definitionen in Modulen und nicht in Manifests mit Knotendefinitionen.
  3. Nutzen Sie Fakten.
  4. Machen Sie keine if-Anweisungen basierend auf Hostnamen.
  5. Scheuen Sie sich nicht, Parameter für Klassen und Definitionen hinzuzufügen – das ist besser als implizite Logik, die im Körper der Klasse/Definition verborgen ist.

Warum ich empfehle, es so zu machen, erkläre ich im nächsten Artikel.

Fazit

Damit beenden wir die Einleitung. Im nächsten Artikel werde ich über Hiera, ENC und PuppetDB sprechen.

Nur registrierte Benutzer können an der Umfrage teilnehmen. Bitte einloggen.

Tatsächlich gibt es viel mehr Material – ich kann Artikel zu den folgenden Themen schreiben, stimmen Sie ab, worüber Sie gerne lesen würden:

  • 59,1%Erweiterte Puppet-Konstrukte – einige fortgeschrittene Themen: Schleifen, Mapping und andere Lambda-Ausdrücke, Ressourcen-Collector, exportierbare Ressourcen und zwischenhostliche Interaktion über Puppet, Tags, Anbieter, abstrakte Datentypen.
  • 31,8%„Ich bin ein Admin von Mama“ oder wie wir bei Avito mehrere Puppet-Server verschiedener Versionen zusammengeschlossen haben, sowie allgemeine Informationen zur Verwaltung eines Puppet-Servers.
  • 81,8%Wie wir Puppet-Code schreiben: Werkzeugumgebung, Dokumentation, Tests, CI/CD.

22 Benutzer haben abgestimmt. 9 Benutzer haben sich enthalten.

Quelle: habr.com

60GB SSD 8Gb DDR4