Einführung in Puppet

Puppet ist ein Konfigurationsmanagement-System. 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 Anfängern ermöglicht, sich schnell in die Grundlagen von Puppet einzuarbeiten.

Einführung in Puppet

Grundinformationen

Die Arbeitsweise von Puppet ist Client-Server-basiert, wobei auch eine serverlose Variante mit eingeschränkter Funktionalität unterstützt wird.

Es wird ein Pull-Modell verwendet: Standardmäßig wenden sich die Clients alle halbe Stunde an den Server, um die Konfiguration abzurufen und anzuwenden. Wenn Sie mit Ansible gearbeitet haben, dort wird ein anderes, Push-Modell verwendet: Der Administrator initiiert den Prozess zur Anwendung der Konfiguration, die Clients wenden von sich aus nichts an.

Bei der Netzwerkkommunikation wird eine bidirektionale TLS-Verschlüsselung verwendet: sowohl der Server als auch der Client besitzen ihre eigenen privaten Schlüssel und die entsprechenden Zertifikate. Normalerweise stellt der Server Zertifikate für die Clients aus, aber grundsätzlich ist auch die Verwendung eines externen CA möglich.

Einführung in die Manifeste

In der Terminologie von Puppet zum Puppet-Server verbinden sich Knoten (nodes). Die Konfiguration für Knoten wird in Manifesten in einer speziellen Programmiersprache — Puppet DSL, geschrieben.

Puppet DSL ist eine deklarative Sprache. Darin wird der gewünschte Zustand eines Knotens durch die Deklaration einzelner Ressourcen beschrieben, zum Beispiel:

  • Die Datei existiert und hat einen bestimmten Inhalt.
  • Das Paket ist installiert.
  • Der Dienst ist gestartet.

Ressourcen können miteinander verknüpft sein:

  • Es gibt Abhängigkeiten, die die Reihenfolge der Anwendung von Ressourcen beeinflussen.
    Zum Beispiel: „Zuerst das Paket installieren, dann die Konfigurationsdatei anpassen, anschließend den Dienst starten.“
  • Es gibt Benachrichtigungen — wenn eine Ressource geändert wird, sendet sie Benachrichtigungen an die abonnierten Ressourcen.
    Wenn sich zum Beispiel die Konfigurationsdatei ändert, kann der Dienst automatisch neu gestartet werden.

Darüber hinaus verfügt Puppet DSL über Funktionen und Variablen sowie über bedingte Operatoren und Selektoren. Verschiedene Template-Mechanismen — 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 — komplexe Logik, neue Ressourcentypen und Funktionen zu schreiben.

Während der Ausführung werden die Puppet-Manifestdateien für jede spezifische Node auf dem Server in ein Verzeichnis kompiliert. Verzeichnis — dies ist eine Liste der Ressourcen und ihrer Beziehungen nach der Berechnung der Funktionswerte, Variablen und der Entfaltung der bedingten Operatoren.

Syntax und Code-Stil

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

Hier ist ein Beispiel, 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, jedoch gibt es empfohlene Style-Guides.Zusammenfassung:

  • Doppelte Leerzeichen und Tabs werden nicht verwendet.
  • Geschweifte Klammern werden durch ein Leerzeichen getrennt, ein Doppelpunkt wird nicht durch ein Leerzeichen getrennt.
  • Kommas nach jedem Parameter, einschließlich des letzten. Jeder Parameter steht in einer eigenen Zeile. Ausnahmen sind für den Fall ohne Parameter und einen Parameter: können in einer Zeile und ohne Komma geschrieben werden (d.h. resource { 'title': } und resource { 'title': param => value }).
  • Pfeile bei den Parametern sollten auf derselben Ebene stehen.
  • Die Pfeile der Ressourcenbeziehungen stehen davor.

Dateispeicherung auf dem Puppet-Server

Um weiterführende Erklärungen zu geben, führe ich den Begriff "Stammverzeichnis" ein. Das Stammverzeichnis ist das Verzeichnis, in dem sich die Puppet-Konfiguration für einen bestimmten Knoten befindet.

Das Stammverzeichnis variiert je nach Puppet-Version und Nutzung von Umgebungen. Umgebungen sind unabhängige Konfigurationssammlungen, die in separaten Verzeichnissen gespeichert werden. Sie werden normalerweise in Kombination mit Git verwendet, wobei Umgebungen aus Git-Branches erstellt werden. Dementsprechend befindet sich jeder Knoten in einer bestimmten Umgebung. Dies kann entweder direkt am Knoten oder über den ENC konfiguriert werden, über den ich im nächsten Artikel sprechen werde.

  • In der dritten Version ("alter Puppet") war das Basisverzeichnis /etc/puppet. Die Nutzung von Umgebungen ist optional – wir verwenden sie zum Beispiel nicht mit dem alten Puppet. Wenn Umgebungen verwendet werden, befinden sie sich normalerweise in /etc/puppet/environments, wobei das Stammverzeichnis das Verzeichnis der Umgebung ist. Wenn keine Umgebungen verwendet werden, ist das Stammverzeichnis das Basisverzeichnis.
  • Seit der vierten Version ("neuer Puppet") ist die Nutzung von Umgebungen obligatorisch, und das Basisverzeichnis wurde in /etc/puppetlabs/code. Daher werden die Umgebungen in /etc/puppetlabs/code/environments, das Stammverzeichnis – das Verzeichnis der Umgebung.

Im Stammverzeichnis muss es ein Unterverzeichnis geben Manifeste, in dem sich ein oder mehrere Manifeste mit der Beschreibung der Knoten befinden. Zudem sollte ein Unterverzeichnis modules, in dem die Module abgelegt sind. Was Module sind, erkläre ich später. Außerdem kann es im alten Puppet ein Unterverzeichnis geben files, in dem verschiedene Dateien aufbewahrt werden, die wir auf die Knoten kopieren. Im neuen Puppet hingegen sind alle Dateien in Module ausgelagert.

Die Manifestdateien haben die Erweiterung .pp.

Eine Reihe von praktischen Beispielen

Beschreibung des Knotens und der Ressourcen auf ihm

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

Manifest schreiben:

node 'server1.testdomain' {   # Konfigurationsblock für den Node server1.testdomain
    file { '/etc/issue':   # Beschreibung der Datei /etc/issue
        ensure  => present,   # diese Datei muss vorhanden sein
        content => 'Debian GNU/Linux n l',   # der Inhalt muss so sein
        owner   => root,   # Eigentümer
        group   => root,   # Besitzergruppe
        mode    => '0644',   # Dateirechte. Diese sind als String angegeben (in Anführungszeichen), da sonst eine Zahl mit 0 am Anfang als im Oktalsystem interpretiert wird, was nicht gewünscht ist
    }
}

Ressourcenzuordnungen am Node

Auf dem Knoten server2.testdomain muss nginx gestartet werden, das mit einer vorbereiteten Konfiguration arbeitet.

Zerlegen wir 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 Änderung der Konfiguration muss der Dienst neu gestartet werden.

Manifest schreiben:

node 'server2.testdomain' {   # Konfigurationsblock für den Knoten server2.testdomain
    package { 'nginx':   # beschreiben des nginx-Pakets
        ensure => installed,   # es muss installiert sein
    }
  # Der direkte Pfeil (->) zeigt an, dass die Ressource darunter
  # nach der oben beschriebenen Ressource erstellt werden muss.
  # Diese Abhängigkeiten sind transitiv.
    -> file { '/etc/nginx':   # beschreiben der Datei /etc/nginx
        ensure  => directory,   # dies sollte ein Verzeichnis sein
        source  => 'puppet:///modules/example/nginx-conf',   # der Inhalt sollte vom Puppet-Server unter der angegebenen Adresse entnommen werden
        recurse => true,   # Dateien rekursiv kopieren
        purge   => true,   # unnötige Dateien (die nicht in der Quelle sind) sollten gelöscht werden
        force   => true,   # unnötige Verzeichnisse löschen
    }
  # Der gewellte Pfeil (~>) zeigt an, dass die Ressource darunter
  # auf Änderungen der oben beschriebenen Ressource reagieren muss.
  # Der gewellte Pfeil umfasst den direkten Pfeil (->).
    ~> service { 'nginx':   # beschreiben des nginx-Dienstes
        ensure => running,   # er muss laufen
        enable => true,   # er sollte beim Start des Systems automatisch gestartet werden
    }
  # Wenn eine Ressource vom Typ service eine Benachrichtigung erhält,
  # wird der entsprechende Dienst neu gestartet.
}

Um dies zum Laufen zu bringen, sollte die Dateiablage 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 fünf grundlegende Typen beschreiben, die in meiner Praxis für die meisten Aufgaben ausreichend sind.

datei

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

Parameter:

  • Name der Ressource — Pfad zur Datei (optional)
  • der Pfad. — Pfad zur Datei (falls nicht im Namen angegeben)
  • ensure — Dateityp:
    • absent — Datei löschen
    • present — eine Datei beliebigen Typs muss vorhanden sein (wenn die Datei nicht vorhanden ist, wird eine Standarddatei erstellt)
    • datei — einfache Datei
    • directory — Verzeichnis
    • link — symbolischer Link
  • content — Inhalt der Datei (nur für einfache Dateien geeignet, kann nicht zusammen mit source oder target)
  • source — Verweis auf den Pfad, von dem der Inhalt der Datei kopiert werden soll (kann nicht zusammen mit content oder target). Dies kann sowohl im URI-Format mit dem Schema puppet: (dann werden die Dateien vom Puppet-Server verwendet), als auch mit dem Schema http: (hoffentlich ist klar, was in diesem Fall passieren wird), und sogar im Schema file: oder in Form eines absoluten Pfades ohne Schema (dann wird die Datei vom lokalen Dateisystem auf dem Knoten verwendet)
  • target — wohin das Symlink zeigen soll (kann nicht zusammen mit content oder source)
  • owner — Benutzername, dem die Datei gehören soll
  • group — Gruppe, der die Datei gehören soll
  • Modus — Berechtigungen für die Datei (in Form einer Zeichenkette)
  • recurse — schließt die rekursive Verarbeitung von Verzeichnissen ein
  • purge — schließt das Löschen von Dateien ein, die nicht in Puppet beschrieben sind
  • force — schließt das Löschen von Verzeichnissen ein, die nicht in Puppet beschrieben sind

Paket

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

Parameter:

  • Name der Ressource — Name des Pakets (optional)
  • name — Name des Pakets (wenn nicht im Titel angegeben)
  • provider — Paketmanager, der verwendet werden soll
  • ensure — gewünschter Zustand des Pakets:
    • present, installed — irgendeine Version installiert
    • latest — letzte Version installiert
    • absent — entfernt (apt-get remove)
    • purged — zusammen mit Konfigurationsdateien entfernt (apt-get purge)
    • held — Version des Pakets ist gesperrt (apt-mark hold)
    • irgendeine andere Zeichenkette — angegebene Version installiert
  • reinstall_on_refresh — wenn true, dann wird das Paket bei Erhalt einer Benachrichtigung erneut installiert. Nützlich für source-based Distributionen, wo eine Neubau der Pakete bei Änderungen der Build-Parameter erforderlich sein kann. Standardmäßig false.

service

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

Parameter:

  • Name der Ressource – Dienst, der verwaltet werden soll (optional)
  • name – Dienst, der verwaltet werden soll (wenn im Namen nicht angegeben)
  • ensure – gewünschter Zustand des Dienstes:
    • Laufen – gestartet
    • stopped – gestoppt
  • enable – verwaltet die Möglichkeit, den Dienst zu starten:
    • true – Autostart aktiviert (systemctl enable)
    • mask – maskiert (systemctl mask)
    • false – Autostart deaktiviert (systemctl disable)
  • restart – Befehl zum Neustarten des Dienstes
  • status – Befehl zur Überprüfung des Dienststatus
  • 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 für einen Neustart neu gestartet (aber in systemd wird der Befehl systemctl restart).
  • hasstatus – angeben, ob das Init-Skript des Dienstes den Befehl statususeString 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 Durchlauf von Puppet ausgeführt. Kann Benachrichtigungen verarbeiten – führt den Befehl aus.

Parameter:

  • Name der Ressource – Befehl, der ausgeführt werden muss (optional)
  • command — Der auszuführende Befehl (sofern nicht im Namen angegeben)
  • der Pfad. — Pfade, in denen nach der ausführbaren Datei gesucht werden soll
  • onlyif — wenn der in diesem Parameter angegebene Befehl mit einem Rückgabewert von null endet, wird der Hauptbefehl ausgeführt
  • unless — wenn der in diesem Parameter angegebene Befehl mit einem Nicht-Null-Rückgabewert endet, wird der Hauptbefehl ausgeführt
  • creates — wenn die angegebene Datei in diesem Parameter nicht existiert, wird der Hauptbefehl ausgeführt
  • refreshonly — wenn true, wird der Befehl nur ausgeführt, wenn dieser Exec eine Benachrichtigung von anderen Ressourcen erhält
  • cwd — Das Verzeichnis, von dem der Befehl ausgeführt werden soll
  • Benutzer — Der Benutzer, unter dem der Befehl ausgeführt werden soll
  • provider — Mit welchem Mittel der Befehl ausgeführt wird:
    • posix — Es wird einfach ein Kindprozess erstellt, muss angegeben werden der Pfad.
    • Shell — Der Befehl wird in einer Shell ausgeführt /bin/sh, kann weggelassen werden der Pfad., es können Globben, Pipes und andere Shell-Funktionen verwendet werden. Wird normalerweise automatisch bestimmt, wenn es spezielle Zeichen gibt (|, ;, &&, || und so weiter).

cron

Verwaltet Cronjobs.

Parameter:

  • Name der Ressource — Einfach eine Kennung
  • ensure — Der Status des Cronjobs:
    • present — Erstellen, wenn nicht vorhanden
    • absent — Löschen, wenn vorhanden
  • command — Welchen Befehl ausführen
  • environment — in welcher Umgebung der Befehl ausgeführt werden soll (Liste der Umgebungsvariablen und deren Werte durch =)
  • Benutzer — unter welchem Benutzer der Befehl ausgeführt werden soll
  • Minute, Stunde, Wochentag, Monat, Tag im Monat — wann der Cronjob ausgeführt werden soll. Wenn einer dieser Attribute nicht angegeben ist, wird sein Wert in der Crontab *.

In Puppet 6.0 cron sozusagen aus dem Standardpaket entfernt in puppetserver, daher gibt es keine Dokumentation auf der allgemeinen Webseite. Aber es ist im Paket in puppet-agent, daher muss es nicht separat installiert werden. Die Dokumentation dazu kann man in der Dokumentation zur fünften Version von Puppet, oder auf GitHub.

Zu den Ressourcen im Allgemeinen

Anforderungen an die Einzigartigkeit von Ressourcen

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

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

Manchmal ist es notwendig, Pakete mit dem gleichen Namen, aber unterschiedlichen Paketmanagern zu installieren. In diesem Fall muss man den Parameter verwenden name, um den Fehler zu vermeiden:

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

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

Metaparameter

Jeder Ressourcentyp verfügt über 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 für den Link lautet wie folgt: Der Ressourcentyp beginnt mit einem Großbuchstaben (wenn im Typnamen doppelte Doppelpunkte enthalten sind, wird jeder Teil des Namens zwischen den Doppelpunkten mit einem Großbuchstaben geschrieben). Danach folgt in eckigen Klammern der Name der Ressource (die Schreibweise des Namens bleibt unverändert!). Es dürfen keine Leerzeichen vorhanden sein, die eckigen Klammern müssen direkt nach dem Typnamen geschrieben werden.

Beispiel:

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

Abhängigkeiten und Benachrichtigungen

Die Dokumentation finden Sie hier.

Wie bereits erwähnt, sind einfache Abhängigkeiten zwischen Ressourcen transitiv. Seien Sie jedoch vorsichtig beim Setzen von Abhängigkeiten – es können zirkuläre Abhängigkeiten entstehen, die einen Kompilierungsfehler hervorrufen.

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 Aktionen bei der Aktualisierung hängen vom Ressourcentyp ab – exec führt einen Befehl aus, service startet einen Dienst neu, Paket installiert ein Paket neu. Wenn für die Ressource keine Aktualisierungsaktion definiert ist, passiert nichts.
  • Bei einem Durchlauf von Puppet wird eine Ressource höchstens einmal aktualisiert. Dies ist möglich, da die Benachrichtigungen Abhängigkeiten enthalten, und das Abhängigkeitsdiagramm keine Zyklen aufweist.
  • Wenn Puppet den Status einer Ressource ändert, sendet die Ressource Benachrichtigungen an alle Ressourcen, die auf sie abonniert sind.
  • Wenn eine Ressource aktualisiert wird, sendet sie Benachrichtigungen an alle Ressourcen, die auf sie abonniert sind.

Verarbeitung nicht angegebener Parameter

Im Allgemeinen wird, wenn ein Parameter einer Ressource keinen Standardwert hat und nicht im Manifest angegeben ist, Puppet dieses Attribut der entsprechenden Ressource im Knoten nicht ändern. Beispiel: Wenn bei einer Ressource vom Typ datei der Parameter owner, nicht angegeben ist, wird Puppet den Eigentümer der entsprechenden Datei nicht ändern.

Einführung in Klassen, Variablen und Defines

Nehmen wir an, wir haben mehrere Knoten, auf denen ein Teil der Konfiguration identisch ist, aber es gibt auch Unterschiede – sonst könnten wir dies alles in einem Block beschreiben. node {}. Natürlich könnte man einfach die gleichen Teile der Konfiguration kopieren, aber im Allgemeinen ist das eine schlechte Lösung – die Konfiguration wird unübersichtlich, und bei Änderungen an den gemeinsamen Teilen muss man das gleiche an vielen Stellen anpassen. Dabei sind Fehler leicht gemacht und überhaupt wurde das Prinzip DRY (don’t repeat yourself) nicht ohne Grund ins Leben gerufen.

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

Klassen

Klasse – dies ist ein benannter Block von Puppet-Code. Klassen sind notwendig, um den Code wiederverwendbar zu machen.

Zuerst muss die Klasse beschrieben werden. Die Beschreibung selbst fügt keine Ressourcen hinzu. Eine Klasse wird in Manifesten beschrieben:

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

Nach dieser Beschreibung kann die Klasse verwendet werden:

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

Beispiel aus der vorherigen Aufgabe – wir heben die Installation und Konfiguration von Nginx in eine Klasse hervor:

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 überhaupt nicht flexibel, da sie immer dieselbe nginx-Konfiguration zurückgibt. Lassen Sie uns den Pfad zur Konfiguration variabel gestalten, damit diese Klasse zur Installation von nginx mit beliebigen Konfigurationen verwendet werden kann.

Das kann erreicht werden mit Hilfe von Variablen.

Achtung: Variablen in Puppet sind unveränderlich!

Außerdem kann auf eine Variable erst nach ihrer Deklaration zugegriffen werden, 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 dementsprechend eine Gültigkeitsbereich: Eine Variable mit demselben Namen kann in verschiedenen Namensräumen definiert werden. Bei der Auflösung des Wertes einer Variable wird zuerst im aktuellen Namensraum, dann im übergeordneten und so weiter nachgeschaut.

Beispiele für Namensräume:

  • global – hier fallen alle Variablen außerhalb der Klassendefinition oder Knotenbeschreibung hinein;
  • Knotennamensraum in der Knotenbeschreibung;
  • Klassennamensraum in der Klassendefinition.

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

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

Lassen Sie uns vereinbaren, dass der Weg zur Konfiguration von nginx in der Variablen liegt $nginx_conf_source. Dann wird die Klasse folgendermaßen aussehen:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # hier verwenden wir die Variable statt einer festen Zeichenfolge
        recure => 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 jedoch problematisch, da es ein gewisses „geheimes Wissen“ darüber gibt, dass irgendwo innerhalb der Klasse eine Variable mit einem bestimmten Namen verwendet wird. Es wäre viel besser, dieses Wissen allgemein zu machen – Klassen können Parameter haben.

Klassenparameter — das sind Variablen im Namensraum der Klasse, die im Kopf der Klasse definiert werden und wie normale Variablen im Körper der Klasse 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 bei der Verwendung kein Wert 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, der obligatorisch ist – den Pfad zur Konfiguration, und den zweiten, der optional ist – den Namen des Pakets mit nginx (in Debian gibt es beispielsweise 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 verwendet, um die Werte der Parameter, die an Klassen und Definitionen übergeben werden, zu validieren. Wenn der übergebene Parameter nicht dem angegebenen Typ entspricht, tritt ein Kompilierungsfehler auf.

Der Typ wird direkt vor dem Namen des Parameters geschrieben:

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

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

Jede Klasse ist eine Ressource vom Typ class. Wie bei allen anderen Ressourcentypen kann auf einem Knoten nicht mehr als eine Instanz derselben Klasse vorhanden sein.

Wenn Sie versuchen, die Klasse zweimal auf denselben Knoten mit class { 'classname':} (unabhängig davon, ob mit unterschiedlichen oder identischen Parametern), tritt ein Kompilierungsfehler auf. Bei der Verwendung der Klasse im Ressourcestil können jedoch sofort alle ihre Parameter im Manifest angegeben werden.

Wenn Sie jedoch verwenden include, dass die Klasse beliebig oft hinzugefügt werden kann. Der Punkt ist, dass include — eine idempotente Funktion, die überprüft, ob die Klasse im Katalog vorhanden ist. Wenn die Klasse nicht im Katalog vorhanden ist, wird sie hinzugefügt, andernfalls passiert nichts. Im Falle der Verwendung von include dürfen die Klasseneigenschaften während der Klassendeklaration nicht festgelegt werden — alle erforderlichen Parameter müssen aus einer externen Datenquelle angegeben werden — Hiera oder ENC. Darauf werden wir im nächsten Artikel eingehen.

Defines

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

Zum Beispiel, um 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 auf die Konfigurationsdatei für php-fpm.
  4. Wir erstellen einen Symlink auf die Konfigurationsdatei für php cli.

In solchen Fällen wird eine Konstruktion wie define (define, definierter Typ, definierter Ressourcentyp). Define ähnelt einer Klasse, aber es gibt Unterschiede: Erstens, jedes Define ist ein Ressourcentyp und kein Ressourcenobjekt; zweitens hat jede Define einen impliziten Parameter. $title, wo der Ressourcenname bei seiner Deklaration eingeht. Ähnlich wie bei Klassen muss ein Define zunächst 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'],  # Die Trigger der Debian-PHP-Pakete erstellen selbst Symlinks und starten den PHP-FPM-Dienst neu - das ist nicht nötig, da wir sowohl mit Symlinks als auch dem Dienst über Puppet verwalten.
  }
  -> 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 Definieren ist es am einfachsten, einen Fehler bei der doppelten Deklaration zu erfassen. Dies geschieht, wenn im Definieren eine Ressource mit einem konstanten Namen vorhanden ist und auf einem Knoten zwei oder mehr Instanzen dieses Definierens existieren.

Um sich davor zu schützen, ist es einfach: Alle Ressourcen innerhalb des Definierens sollten einen von $titleabhängigen Namen haben. Alternativ kann man ressourcenübergreifende, idempotente Hinzufügungen verwenden. Im einfachsten Fall reicht es aus, gemeinsame Ressourcen für alle Instanzen des Definierens in eine separate Klasse auszulagern und diese Klasse im Definieren einzuschließen — 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 Definitionen

Klassen und Definitionen fügen den folgenden Regeln zur Verarbeitung von Abhängigkeiten und Benachrichtigungen hinzu:

  • Die Abhängigkeit von einer Klasse/Definition fügt Abhängigkeiten von allen Ressourcen der Klasse/Definition hinzu;
  • Die Abhängigkeit von Klasse/Definition fügt allen Ressourcen der Klasse/Definition Abhängigkeiten hinzu;
  • Die Benachrichtigung von Klasse/Definition benachrichtigt alle Ressourcen der Klasse/Definition;
  • Die Anmeldung bei einer Klasse/Definition registriert für alle Ressourcen der Klasse/Definition.

Bedingte Operatoren und Selektoren

Die Dokumentation finden Sie hier.

wenn

Hier ist alles einfach:

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

unless

unless ist das Gegenteil von if: Der Codeblock wird ausgeführt, wenn der Ausdruck falsch ist.

unless AUSDRUCK {
  ...
}

Fall

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

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

Selektoren

Ein Selektor ist eine Sprachkonstruktion, die ähnlich ist wie Fall, aber anstelle der Ausführung eines Codeblocks gibt sie einen Wert zurück.

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

Module

Wenn die Konfiguration klein ist, kann man sie leicht in einem Manifest halten. Je mehr Konfigurationen wir jedoch beschreiben, desto mehr Klassen und Knoten kommen im Manifest hinzu, es wächst, und es wird unhandlich.

Außerdem gibt es das Problem der Wiederverwendbarkeit von Code – wenn der ganze 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 so etwas wie Module.

Module — ist eine Sammlung von Klassen, Definitionsdateien und anderen Puppet-Elementen, die in ein separates Verzeichnis ausgelagert wurden. Mit anderen Worten, ein Modul stellt ein unabhängiges Stück Puppet-Logik dar. Beispielsweise könnte es ein Modul für die Arbeit mit nginx geben, das genau das enthält, was für die Nutzung von nginx erforderlich ist, oder ein Modul für die Arbeit mit PHP usw.

Module werden versioniert und unterstützen auch Abhängigkeiten zwischen Modulen. Es gibt ein öffentliches Repository für Module — Puppet Forge.

Auf dem Puppet-Server befinden sich die Module im Unterverzeichnis modules des Wurzelverzeichnisses. Innerhalb jedes Moduls ist das Standard-Verzeichnislayout — manifests, files, templates, lib usw.

Dateistruktur im Modul

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

  • Manifeste — darin befinden sich die Manifeste
  • files — darin befinden sich die Dateien
  • templates — darin befinden sich die Vorlagen
  • lib — darin befindet sich Ruby-Code

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

Ressourcennamen und Dateinamen im Modul

Die Dokumentation finden Sie hier.

Ressourcen (Klassen, Definitionen) in einem Modul können nicht beliebig benannt werden. Darüber hinaus besteht eine direkte Zuordnung 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 tritt ein Kompilierungsfehler auf.

Die Regeln sind einfach:

  • Alle Ressourcen in einem Modul müssen sich im Namensraum des Moduls befinden. Wenn das Modul heißt foo, müssen alle Ressourcen darin benannt werden foo::, oder einfach foo.
  • Die Ressource mit dem Namen des Moduls muss in der Datei init.pp.
  • Für die anderen Ressourcen gilt folgendes Namensschema:
    • Der Präfix mit dem Modulnamen entfällt
    • alle doppelten Doppelpunkte, falls vorhanden, werden durch Schrägstriche ersetzt
    • die Erweiterung wird hinzugefügt .pp

Ich werde das an einem Beispiel demonstrieren. Angenommen, ich schreibe ein Modul nginx. Es enthält die folgenden 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 sicher selbst, was Vorlagen sind, ich werde das hier nicht ausführlich darstellen. Aber für den Fall lasse ich einen Link zur Wikipedia.

So verwenden Sie Vorlagen: Der Wert der Vorlage kann mithilfe einer Funktion aufgeschlüsselt werden. template, die den Pfad zur Vorlage angibt. Für Ressourcen vom Typ datei nutzen wir zusammen mit dem Parameter content. Zum Beispiel so:

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

Ein Pfad der Form / bedeutet die Datei /modules//templates/.

Darüber hinaus gibt es die Funktion inline_template — hier wird der Text der Vorlage übergeben, nicht der Dateiname.

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

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

Kurz über ERB

Kontrollstrukturen:

  • <%= ВЫРАЖЕНИЕ %> — Wert des Ausdrucks einfügen
  • <% ВЫРАЖЕНИЕ %> — Wert des Ausdrucks berechnen (ohne ihn einzufügen). Hier kommen übliche bedingte Operatoren (if), Schleifen (each) hinein.
  • <%# КОММЕНТАРИЙ %>

Ausdrücke in ERB werden in Ruby geschrieben (genau genommen, ERB ist Embedded Ruby).

Um auf Variablen aus dem Manifest zuzugreifen, muss man @ zum Variablennamen hinzufügen. Um den Zeilenumbruch, der nach der Kontrollstruktur auftritt, zu entfernen, muss das schließende Tag verwendet werden -%>.

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 zuständig ist, sieht ungefähr so aus:

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

Und das entsprechende Template zoo.cfg.erb sieht so aus:

0 -%>

server.=::;



dataDir=


=

Fakten und eingebaute Variablen

Oft hängt ein bestimmter Teil der Konfiguration davon ab, was derzeit auf der Node passiert. Zum Beispiel, je nach Debian-Version, die installiert ist, muss eine bestimmte Paketversion installiert werden. Man könnte all dies manuell überwachen, indem man die Manifeste bei Änderungen an den Nodes umschreibt. Aber das ist nicht praktikabel, Automatisierung ist viel effektiver.

Um Informationen über die Nodes zu erhalten, bietet Puppet einen Mechanismus namens Fakten. Fakten — dies sind Informationen über den Knoten, die in Manifesten als gewöhnliche Variablen im globalen Namensraum verfügbar sind. Zum Beispiel: Hostname, Betriebssystemversion, Prozessorarchitektur, Benutzerliste, Liste der Netzwerk-Interfaces und deren Adressen sowie viele, viele weitere. Fakten sind in Manifesten und Vorlagen als gewöhnliche Variablen verfügbar.

Beispiel für die Arbeit mit Fakten:

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

Formal gesagt hat ein Fakt einen Namen (String) und einen Wert (es sind verschiedene Typen verfügbar: Strings, Arrays, Dictionaries). Es gibt eine Reihe von integrierten Fakten. Man kann auch eigene schreiben. Fakten-Collector werden als Funktionen in Ruby, oder als ausführbare Dateien. Darüber hinaus können Fakten in Form von Textdateien mit Daten auf Knoten dargestellt werden.

Während der Ausführung kopiert der Puppet-Agent zunächst alle verfügbaren Fakten-Collector 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 Verzeichnisses.

Fakten in Form ausführbarer Dateien

Solche Fakten werden in Module im Verzeichnis abgelegt facts.d. Natürlich müssen die Dateien ausführbar sein. Bei der Ausführung sollten sie Informationen entweder im YAML-Format oder im Format «Schlüssel=Wert» auf die Standardausgabe ausgeben.

Vergessen Sie nicht, dass die Fakten auf alle Knoten angewendet werden, die unter der Kontrolle des Puppet-Servers stehen, auf dem Ihr Modul bereitgestellt wird. Daher sollten Sie im Skript sicherstellen, dass alle erforderlichen 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 abgelegt 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

Textliche Fakten

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

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

Zugriff auf Fakten

Auf die Fakten kann auf zwei Arten zugegriffen werden:

  • über das Dictionary $facts: $facts['fqdn'];
  • indem der Faktname als Variablenname verwendet wird: $fqdn.

Es ist am besten, das Dictionary zu verwenden $facts, noch besser ist es, den globalen Namensraum anzugeben ($::facts).

Hier ist der relevante Abschnitt der Dokumentation.

Eingebaute Variablen

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

  • trusted facts — Variablen, die aus dem Kundenzertifikat stammen (da das Zertifikat normalerweise auf dem Puppet-Server ausgestellt wird, kann der Agent sein Zertifikat nicht einfach ändern; daher die Variablen und die "vertrauenswürdigen"): Zertifikatname, Host- und Domainname, Erweiterungen aus dem Zertifikat.
  • Server-Fakten — Variablen, die sich auf die Serverinformationen beziehen — Version, Name, IP-Adresse des Servers, Umgebung.
  • Agent-Fakten — Variablen, die direkt vom Puppet-Agent hinzugefügt werden und nicht von Facter — Zertifikatname, Agent-Version, Puppet-Version.
  • Master-Variablen — Variablen des Puppet-Masters (sic!). Dort finden sich ungefähr die gleichen Informationen wie in Server-Fakten, zusätzlich sind die Werte der Konfigurationsparameter verfügbar.
  • Compiler-Variablen — Variablen des Compilers, die in jedem Sichtfeld unterschiedlich sind: Name des aktuellen Moduls und Name des Moduls, von dem aus auf das aktuelle Objekt zugegriffen wurde. Sie können beispielsweise verwendet werden, um zu überprüfen, dass Ihre privaten Klassen nicht direkt aus anderen Modulen verwendet werden.

Ergänzung 1: Wie startet und debuggt man das alles?

In dem Artikel wurden viele Beispiele für Puppet-Code gegeben, aber es wurde gar nicht erklärt, wie man diesen Code ausführt. Nun, das werde ich nachholen.

Für die Verwendung von Puppet genügt der Agent, aber in den meisten Fällen wird auch ein Server benötigt.

Agent

Mindestens ab der Version 5 enthalten die Pakete puppet-agent 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 Agent im serverlosen Modus zu starten: Vorausgesetzt, der Puppet-Code ist auf dem Knoten kopiert, führen Sie puppet apply:

atikhonov@atikhonov ~/puppet-test $ cat helloworld.pp 
node default {
    notify { 'Hello world!': }
}
atikhonov@atikhonov ~/puppet-test $ puppet apply helloworld.pp 
Notice: Compiled catalog for atikhonov.localdomain in environment production in 0.01 seconds
Notice: Hello world!
Notice: /Stage[main]/Main/Node[default]/Notify[Hello world!]/message: defined 'message' as 'Hello world!'
Notice: Applied catalog in 0.01 seconds

Am besten ist es, einen Server hochzufahren und die Agenten auf den Knoten im Demomodus auszuführen – dann wenden sie alle 30 Minuten die Konfiguration an, die vom Server heruntergeladen wurde.

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

  • nicht im Demomodus zu arbeiten (standardmäßig wird der Agent im Demomodus gestartet);
  • nach der Anwendung des Verzeichnisses zu beenden (standardmäßig läuft der Agent weiter und wendet die Konfiguration alle 30 Minuten an);
  • ein detailliertes Protokoll des Betriebs zu schreiben;
  • Änderungen in den Dateien anzuzeigen.

Der Agent verfügt über einen Modus ohne Änderungen – dieser kann verwendet werden, wenn Sie nicht sicher sind, dass Sie eine korrekte Konfiguration geschrieben haben und überprüfen möchten, was der Agent während seines Betriebs ändern würde. Dieser Modus wird mit dem Parameter --noop in der Befehlszeile aktiviert: sudo puppet agent -t --noop.

Darüber hinaus kann ein Debug-Protokoll aktiviert werden – darin protokolliert Puppet alle Aktionen, die er durchführt: über die Ressource, die gerade verarbeitet wird, über die Parameter dieser Ressource und über die Programme, die gestartet werden. Selbstverständlich ist dies ein Parameter --debug.

Server

Ich werde in diesem Artikel die vollständige Einrichtung von Puppetserver und das Deployment von Code darauf nicht detailliert behandeln. Nur so viel: Aus dem Karton heraus können Sie eine voll funktionsfähige Serverversion installieren, die keine zusätzliche Konfiguration benötigt, um in Umgebungen mit einer geringen Anzahl von Knoten (sagen wir bis zu hundert) zu funktionieren. Eine höhere Anzahl an Knoten erfordert jedoch Feineinstellungen – standardmäßig startet Puppetserver nicht mehr als vier Worker. Für eine bessere Leistung sollten Sie deren Anzahl erhöhen und die Speicherkapazitäten anpassen, da der Server ansonsten die meiste Zeit mit Garbage Collection beschäftigt sein wird.

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

Ergänzung 2: Empfehlungen zum Schreiben von Code

  1. Lagern Sie alle Logik in Klassen und Definitionen aus.
  2. Halten Sie Klassen und Definitionen in Modulen und nicht in Manifesten mit Knotenbeschreibungen.
  3. Nutzen Sie Fakten.
  4. Vermeiden Sie 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 dazu rate, erkläre ich im nächsten Artikel.

Fazit

Damit beenden wir die Einleitung. Im nächsten Artikel erzähle ich von Hiera, ENC und PuppetDB.

Nur registrierte Benutzer können an der Umfrage teilnehmen. Bitte melden Sie sich an.Sind Sie an Contour interessiert?

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, exportierte Ressourcen und Inter-Host-Kommunikation über Puppet, Tags, Provider, abstrakte Datentypen.
  • 31,8%„Ich bin der Admin von Mama“ oder wie wir bei Avito mehrere Puppet-Server unterschiedlicher Versionen zusammengebracht haben, sowie einen Teil über die Verwaltung eines Puppet-Servers.
  • 81,8%Wie wir Puppet-Code schreiben: Instrumentierung, Dokumentation, Tests, CI/CD.

22 Benutzer haben abgestimmt. 9 Benutzer haben sich enthalten.

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