Einführung in Puppet

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

Ich arbeite jetzt seit über fünf Jahren mit Puppet zusammen. Bei diesem Text handelt es sich im Wesentlichen um eine übersetzte und neu geordnete Zusammenstellung der wichtigsten Punkte aus der offiziellen Dokumentation, die es Anfängern ermöglicht, die Essenz von Puppet schnell zu verstehen.

Einführung in Puppet

Grundlegende Informationen

Das Betriebssystem von Puppet ist Client-Server, unterstützt jedoch auch einen serverlosen Betrieb mit eingeschränkter Funktionalität.

Es wird ein Pull-Betriebsmodell verwendet: Standardmäßig kontaktieren Clients alle halbe Stunde den Server, um eine Konfiguration anzufordern, und wenden diese an. Wenn Sie mit Ansible gearbeitet haben, verwenden diese ein anderes Push-Modell: Der Administrator initiiert den Prozess der Anwendung der Konfiguration, die Clients selbst übernehmen nichts.

Bei der Netzwerkkommunikation kommt eine bidirektionale TLS-Verschlüsselung zum Einsatz: Server und Client verfügen über eigene private Schlüssel und entsprechende Zertifikate. Typischerweise stellt der Server Zertifikate für Clients aus, grundsätzlich ist es jedoch möglich, eine externe CA zu verwenden.

Einführung in Manifeste

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

Puppet DSL ist eine deklarative Sprache. Es beschreibt den gewünschten Zustand des Knotens in Form von Deklarationen einzelner Ressourcen, zum Beispiel:

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

Ressourcen können miteinander verbunden sein:

  • Es gibt Abhängigkeiten, sie wirken sich auf die Reihenfolge aus, in der Ressourcen verwendet werden.
    Beispiel: „Installieren Sie zuerst das Paket, bearbeiten Sie dann die Konfigurationsdatei und starten Sie dann den Dienst.“
  • Es gibt Benachrichtigungen – wenn sich eine Ressource geändert hat, sendet sie Benachrichtigungen an die Ressourcen, die sie abonniert hat.
    Wenn sich beispielsweise die Konfigurationsdatei ändert, können Sie den Dienst automatisch neu starten.

Darüber hinaus verfügt die Puppet-DSL über Funktionen und Variablen sowie bedingte Anweisungen und Selektoren. Es werden auch verschiedene Template-Mechanismen unterstützt – EPP und ERB.

Puppet ist in Ruby geschrieben, daher stammen viele der Konstrukte und Begriffe von dort. Mit Ruby können Sie Puppet erweitern – komplexe Logik, neue Arten von Ressourcen und Funktionen hinzufügen.

Während Puppet ausgeführt wird, werden Manifeste für jeden spezifischen Knoten auf dem Server in einem Verzeichnis kompiliert. Verzeichnis ist eine Liste von Ressourcen und ihren Beziehungen nach der Berechnung des Wertes von Funktionen, Variablen und der Erweiterung bedingter Anweisungen.

Syntax und Codestil

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

Hier ist ein Beispiel dafür, wie das 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, werden jedoch empfohlen Styleguide. Zusammenfassung:

  • Einrückungen mit zwei Leerzeichen und Tabulatoren werden nicht verwendet.
  • Geschweifte Klammern werden durch ein Leerzeichen getrennt; Doppelpunkte werden nicht durch ein Leerzeichen getrennt.
  • Kommas nach jedem Parameter, einschließlich des letzten. Jeder Parameter steht in einer separaten Zeile. Eine Ausnahme gibt es für den Fall ohne Parameter und einen Parameter: Sie können in einer Zeile und ohne Komma schreiben (d. h. resource { 'title': } и resource { 'title': param => value }).
  • Die Pfeile auf den Parametern sollten auf gleicher Höhe sein.
  • Davor sind Pfeile für Ressourcenbeziehungen geschrieben.

Speicherort der Dateien auf Pappetserver

Zur weiteren Erläuterung werde ich das Konzept des „Stammverzeichnisses“ vorstellen. Das Stammverzeichnis ist das Verzeichnis, das die Puppet-Konfiguration für einen bestimmten Knoten enthält.

Das Stammverzeichnis variiert je nach Puppet-Version und den verwendeten Umgebungen. Umgebungen sind unabhängige Konfigurationssätze, die in separaten Verzeichnissen gespeichert sind. Wird normalerweise in Kombination mit Git verwendet. In diesem Fall werden Umgebungen aus Git-Zweigen erstellt. Dementsprechend befindet sich jeder Knoten in der einen oder anderen Umgebung. Dies kann auf dem Knoten selbst oder in ENC konfiguriert werden, worüber ich im nächsten Artikel sprechen werde.

  • In der dritten Version („old 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, werden diese normalerweise in gespeichert /etc/puppet/environments, das Stammverzeichnis ist das Umgebungsverzeichnis. Wenn keine Umgebungen verwendet werden, ist das Stammverzeichnis das Basisverzeichnis.
  • Ab der vierten Version („new Puppet“) wurde die Verwendung von Umgebungen verpflichtend und das Basisverzeichnis wurde dorthin verschoben /etc/puppetlabs/code. Dementsprechend werden Umgebungen in gespeichert /etc/puppetlabs/code/environmentsDas Stammverzeichnis ist das Umgebungsverzeichnis.

Im Stammverzeichnis muss ein Unterverzeichnis vorhanden sein manifests, das ein oder mehrere Manifeste enthält, die die Knoten beschreiben. Darüber hinaus sollte ein Unterverzeichnis vorhanden sein modules, das die Module enthält. Was Module sind, erzähle ich euch etwas später. Darüber hinaus verfügt das alte Puppet möglicherweise auch über ein Unterverzeichnis files, das verschiedene Dateien enthält, die wir auf die Knoten kopieren. Im neuen Puppet werden alle Dateien in Modulen abgelegt.

Manifestdateien haben die Erweiterung .pp.

Ein paar Kampfbeispiele

Beschreibung des Knotens und der darauf befindlichen Ressource

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

Wir schreiben ein Manifest:

node 'server1.testdomain' {   # блок конфигурации, относящийся к ноде server1.testdomain
    file { '/etc/issue':   # описываем файл /etc/issue
        ensure  => present,   # этот файл должен существовать
        content => 'Debian GNU/Linux n l',   # у него должно быть такое содержимое
        owner   => root,   # пользователь-владелец
        group   => root,   # группа-владелец
        mode    => '0644',   # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Beziehungen zwischen Ressourcen auf einem Knoten

Auf dem Knoten server2.testdomain Nginx muss ausgeführt werden und mit einer zuvor vorbereiteten Konfiguration arbeiten.

Zerlegen wir das Problem:

  • Das Paket muss installiert werden nginx.
  • Es ist notwendig, dass die Konfigurationsdateien vom Server kopiert werden.
  • Der Dienst muss ausgeführt werden nginx.
  • Wenn die Konfiguration aktualisiert wird, muss der Dienst neu gestartet werden.

Wir schreiben ein Manifest:

node 'server2.testdomain' {   # блок конфигурации, относящийся к ноде server2.testdomain
    package { 'nginx':   # описываем пакет nginx
        ensure => installed,   # он должен быть установлен
    }
  # Прямая стрелка (->) говорит о том, что ресурс ниже должен
  # создаваться после ресурса, описанного выше.
  # Такие зависимости транзитивны.
    -> file { '/etc/nginx':   # описываем файл /etc/nginx
        ensure  => directory,   # это должна быть директория
        source  => 'puppet:///modules/example/nginx-conf',   # её содержимое нужно брать с паппет-сервера по указанному адресу
        recurse => true,   # копировать файлы рекурсивно
        purge   => true,   # нужно удалять лишние файлы (те, которых нет в источнике)
        force   => true,   # удалять лишние директории
    }
  # Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
  # подписаться на изменения ресурса, описанного выше.
  # Волнистая стрелка включает в себя прямую (->).
    ~> service { 'nginx':   # описываем сервис nginx
        ensure => running,   # он должен быть запущен
        enable => true,   # его нужно запускать автоматически при старте системы
    }
  # Когда ресурс типа service получает уведомление,
  # соответствующий сервис перезапускается.
}

Damit dies funktioniert, benötigen Sie ungefähr den folgenden Dateispeicherort auf dem Puppet-Server:

/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 hier in der DokumentationHier beschreibe ich fünf Grundtypen, die in meiner Praxis ausreichen, um die meisten Probleme zu lösen.

Datei

Verwaltet Dateien, Verzeichnisse, Symlinks, deren Inhalte und Zugriffsrechte.

Optionen:

  • Ressourcenname — Pfad zur Datei (optional)
  • Weg — Pfad zur Datei (falls nicht im Namen angegeben)
  • gewährleisten - Dateityp:
    • absent - Eine Datei löschen
    • present — Es muss eine Datei beliebigen Typs vorhanden sein (wenn keine Datei vorhanden ist, wird eine reguläre Datei erstellt)
    • file - reguläre Datei
    • directory - Verzeichnis
    • link - Symlink
  • Inhalt — Dateiinhalte (nur für normale Dateien geeignet, können nicht zusammen mit verwendet werden Quelle oder Ziel)
  • Quelle — ein Link zum Pfad, von dem Sie den Inhalt der Datei kopieren möchten (kann nicht zusammen mit verwendet werden). Inhalt oder Ziel). Kann entweder als URI mit einem Schema angegeben werden puppet: (dann werden Dateien vom Puppet-Server verwendet) und mit dem Schema http: (Ich hoffe, es ist klar, was in diesem Fall passieren wird) und sogar mit dem Diagramm file: oder als absoluter Pfad ohne Schema (dann wird die Datei vom lokalen FS auf dem Knoten verwendet)
  • Ziel — wohin der Symlink zeigen soll (kann nicht zusammen mit verwendet werden). Inhalt oder Quelle)
  • Eigentümer – der Benutzer, dem die Datei gehören soll
  • Gruppe — die Gruppe, zu der die Datei gehören soll
  • Modus — Dateiberechtigungen (als String)
  • rekursiv - ermöglicht die rekursive Verzeichnisverarbeitung
  • Säuberung – ermöglicht das Löschen von Dateien, die nicht in Puppet beschrieben sind
  • Stärke – ermöglicht das Löschen von Verzeichnissen, die nicht in Puppet beschrieben sind

Paket

Installiert und entfernt Pakete. Kann Benachrichtigungen verarbeiten – installiert das Paket neu, wenn der Parameter angegeben ist reinstall_on_refresh.

Optionen:

  • Ressourcenname — Paketname (optional)
  • Name — Paketname (falls nicht im Namen angegeben)
  • Versorger — Zu verwendender Paketmanager
  • gewährleisten — gewünschter Zustand des Pakets:
    • present, installed - jede Version installiert
    • latest - Neueste Version installiert
    • absent - gelöscht (apt-get remove)
    • purged – zusammen mit den Konfigurationsdateien gelöscht (apt-get purge)
    • held - Paketversion ist gesperrt (apt-mark hold)
    • любая другая строка — Die angegebene Version ist installiert
  • reinstall_on_refresh - wenn true, dann wird das Paket nach Erhalt der Benachrichtigung neu installiert. Nützlich für quellbasierte Distributionen, bei denen möglicherweise eine Neuerstellung von Paketen erforderlich ist, wenn Build-Parameter geändert werden. Default false.

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

Optionen:

  • Ressourcenname — Zu verwaltender Dienst (optional)
  • Name — der Dienst, der verwaltet werden muss (falls nicht im Namen angegeben)
  • gewährleisten — gewünschter Zustand des Dienstes:
    • running - gestartet
    • stopped - gestoppt
  • ermöglichen – steuert die Möglichkeit, den Dienst zu starten:
    • true — Autorun ist aktiviert (systemctl enable)
    • mask - verkleidet (systemctl mask)
    • false — Autorun ist deaktiviert (systemctl disable)
  • Wiederaufnahme - Befehl zum Neustart des Dienstes
  • Status – Befehl zum Überprüfen des Dienststatus
  • hasrestart – Geben Sie an, ob das Dienst-Initscript einen Neustart unterstützt. Wenn false und der Parameter ist angegeben Wiederaufnahme — Der Wert dieses Parameters wird verwendet. Wenn false und Parameter Wiederaufnahme nicht angegeben – der Dienst wird gestoppt und gestartet, um neu zu starten (aber systemd verwendet den Befehl systemctl restart).
  • hasstatus – Geben Sie an, ob das Dienst-Initscript den Befehl unterstützt status. Wenn false, dann wird der Parameterwert verwendet Status. Default true.

exec

Führt externe Befehle aus. Wenn Sie keine Parameter angeben schafft, nur wenn, es sei denn oder Nur aktualisieren, wird der Befehl jedes Mal ausgeführt, wenn Puppet ausgeführt wird. Kann Benachrichtigungen verarbeiten – führt einen Befehl aus.

Optionen:

  • Ressourcenname — Befehl, der ausgeführt werden soll (optional)
  • Befehl — der auszuführende Befehl (falls er nicht im Namen angegeben ist)
  • Weg – Pfade, in denen nach der ausführbaren Datei gesucht werden soll
  • nur wenn — Wenn der in diesem Parameter angegebene Befehl mit einem Null-Rückgabecode abgeschlossen wurde, wird der Hauptbefehl ausgeführt
  • es sei denn — Wenn der in diesem Parameter angegebene Befehl mit einem Rückkehrcode ungleich Null abgeschlossen wurde, wird der Hauptbefehl ausgeführt
  • schafft — Wenn die in diesem Parameter angegebene Datei nicht existiert, wird der Hauptbefehl ausgeführt
  • Nur aktualisieren - wenn true, dann wird der Befehl nur ausgeführt, wenn diese Exec eine Benachrichtigung von anderen Ressourcen erhält
  • cwd – Verzeichnis, von dem aus der Befehl ausgeführt werden soll
  • Benutzer – der Benutzer, von dem aus der Befehl ausgeführt werden soll
  • Versorger - So führen Sie den Befehl aus:
    • POSIX — Es wird einfach ein untergeordneter Prozess erstellt. Geben Sie dies unbedingt an Weg
    • Schale - Der Befehl wird in der Shell gestartet /bin/sh, darf nicht angegeben werden Wegkönnen Sie Globbing, Pipes und andere Shell-Funktionen verwenden. Wird normalerweise automatisch erkannt, wenn Sonderzeichen vorhanden sind (|, ;, &&, || usw).

cron

Steuert Cronjobs.

Optionen:

  • Ressourcenname - nur eine Art Identifikator
  • gewährleisten — Crownjob-Zustand:
    • present - erstellen, falls nicht vorhanden
    • absent - löschen, falls vorhanden
  • Befehl - Welcher Befehl soll ausgeführt werden?
  • Umwelt — in welcher Umgebung der Befehl ausgeführt werden soll (Liste der Umgebungsvariablen und ihrer Werte über =)
  • Benutzer – Von welchem ​​Benutzer aus der Befehl ausgeführt werden soll
  • Minute, Stunde, Wochentag, Monat, Monat Tag – wann cron ausgeführt werden soll. Wenn eines dieser Attribute nicht angegeben ist, wird sein Wert in der Crontab angegeben *.

In Puppet 6.0 cron wie aus der Box genommen im Puppetserver, daher gibt es auf der allgemeinen Seite keine Dokumentation. Aber er ist in der Box im Puppet-Agent, sodass keine separate Installation erforderlich ist. Sie können die Dokumentation dazu einsehen in der Dokumentation zur fünften Version von PuppetOder auf GitHub.

Über Ressourcen im Allgemeinen

Anforderungen an die Einzigartigkeit der Ressource

Der häufigste Fehler, dem wir begegnen, ist Doppelte Erklärung. Dieser Fehler tritt auf, wenn zwei oder mehr Ressourcen desselben Typs mit demselben Namen im Verzeichnis angezeigt werden.

Deshalb schreibe ich noch einmal: Manifeste für denselben Knoten sollten keine Ressourcen desselben Typs mit demselben Titel enthalten!

Manchmal besteht die Notwendigkeit, Pakete mit demselben Namen, aber mit unterschiedlichen Paketmanagern zu installieren. In diesem Fall müssen Sie den Parameter verwenden nameum den Fehler zu vermeiden:

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

Andere Ressourcentypen verfügen über ähnliche Optionen, um Duplikate zu vermeiden name у , command у exec, usw.

Metaparameter

Jeder Ressourcentyp weist unabhängig von seiner Art einige spezielle Parameter auf.

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

Auswahlliste:

  • erfordern — Dieser Parameter gibt an, von welchen Ressourcen diese Ressource abhängig ist.
  • Bevor – Dieser Parameter gibt an, welche Ressourcen von dieser Ressource abhängig sind.
  • Abonnieren — Dieser Parameter gibt an, von welchen Ressourcen diese Ressource Benachrichtigungen erhält.
  • benachrichtigen – Dieser Parameter gibt an, welche Ressourcen Benachrichtigungen von dieser Ressource erhalten.

Alle aufgeführten Metaparameter akzeptieren entweder einen einzelnen Ressourcenlink oder eine Reihe von Links in eckigen Klammern.

Links zu Ressourcen

Ein Ressourcenlink ist lediglich eine Erwähnung der Ressource. Sie werden hauptsächlich verwendet, um Abhängigkeiten anzuzeigen. Der Verweis auf eine nicht vorhandene Ressource führt zu einem Kompilierungsfehler.

Die Syntax des Links lautet wie folgt: Ressourcentyp mit einem Großbuchstaben (wenn der Typname doppelte Doppelpunkte enthält, wird jeder Teil des Namens zwischen den Doppelpunkten großgeschrieben), dann der Ressourcenname in eckigen Klammern (die Groß-/Kleinschreibung des Namens). ändert sich nicht!). Es dürfen keine Leerzeichen stehen; eckige Klammern werden unmittelbar nach dem Typnamen geschrieben.

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 übrigens vorsichtig beim Hinzufügen von Abhängigkeiten – Sie können zyklische Abhängigkeiten erstellen, die einen Kompilierungsfehler verursachen.

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

  • Wenn die Ressource eine Benachrichtigung erhält, wird sie aktualisiert. Die Aktualisierungsaktionen hängen vom Ressourcentyp ab exec führt den Befehl aus, startet den Dienst neu, Paket installiert das Paket neu. Wenn für die Ressource keine Aktualisierungsaktion definiert ist, passiert nichts.
  • Während einer Ausführung von Puppet wird die Ressource höchstens einmal aktualisiert. Dies ist möglich, weil Benachrichtigungen Abhängigkeiten enthalten und das Abhängigkeitsdiagramm keine Zyklen enthält.
  • Wenn Puppet den Status einer Ressource ändert, sendet die Ressource Benachrichtigungen an alle Ressourcen, die sie abonniert hat.
  • Wenn eine Ressource aktualisiert wird, sendet sie Benachrichtigungen an alle Ressourcen, die sie abonniert haben.

Umgang mit nicht spezifizierten Parametern

Wenn ein Ressourcenparameter keinen Standardwert hat und dieser Parameter nicht im Manifest angegeben ist, ändert Puppet diese Eigenschaft in der Regel nicht für die entsprechende Ressource auf dem Knoten. Wenn es sich beispielsweise um eine Ressource vom Typ handelt Datei Parameter nicht angegeben owner, dann ändert Puppet den Besitzer der entsprechenden Datei nicht.

Einführung in Klassen, Variablen und Definitionen

Angenommen, wir haben mehrere Knoten, die den gleichen Teil der Konfiguration haben, aber es gibt auch Unterschiede – sonst könnten wir alles in einem Block beschreiben node {}. Natürlich können Sie identische Teile der Konfiguration einfach kopieren, aber im Allgemeinen ist das eine schlechte Lösung – die Konfiguration wächst, und wenn Sie den allgemeinen Teil der Konfiguration ändern, müssen Sie an vielen Stellen dasselbe bearbeiten. Gleichzeitig ist es leicht, einen Fehler zu machen, und im Allgemeinen wurde das DRY-Prinzip (nicht wiederholen) aus einem bestimmten Grund erfunden.

Um dieses Problem zu lösen, gibt es ein Design wie Klasse.

Классы

Klasse ist ein benannter Poppet-Codeblock. Zur Wiederverwendung von Code werden Klassen benötigt.

Zunächst muss die Klasse beschrieben werden. Die Beschreibung selbst fügt nirgendwo 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 – lassen Sie uns die Installation und Konfiguration von Nginx in eine Klasse verschieben:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recure => 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 die gleiche Nginx-Konfiguration mitbringt. Lassen Sie uns den Pfad zur Konfigurationsvariablen festlegen, dann kann diese Klasse verwendet werden, um Nginx mit jeder Konfiguration zu installieren.

Es ist machbar Verwendung von Variablen.

Achtung: Variablen in Puppet sind unveränderlich!

Darüber hinaus kann auf eine Variable nur zugegriffen werden, nachdem sie deklariert wurde, andernfalls wird der Wert der Variablen geändert undef.

Beispiel für die 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}"

Puppet hat Namensräume, und die Variablen haben dementsprechend Sichtbereich: Eine Variable mit demselben Namen kann in verschiedenen Namespaces definiert werden. Beim Auflösen des Werts einer Variablen wird die Variable im aktuellen Namensraum, dann im umschließenden Namensraum usw. gesucht.

Beispiele für Namespaces:

  • global – Variablen außerhalb der Klassen- oder Knotenbeschreibung werden dorthin verschoben;
  • Knoten-Namensraum in der Knotenbeschreibung;
  • Klassennamensraum in der Klassenbeschreibung.

Um Unklarheiten beim Zugriff auf eine Variable zu vermeiden, können Sie den Namensraum im Variablennamen angeben:

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

Lassen Sie uns zustimmen, dass der Pfad zur Nginx-Konfiguration in der Variablen liegt $nginx_conf_source. Dann sieht die Klasse so aus:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # здесь используем переменную вместо фиксированной строки
        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
}

Allerdings ist das gegebene Beispiel schlecht, weil es ein „Geheimwissen“ gibt, dass irgendwo innerhalb der Klasse eine Variable mit diesem oder jenem Namen verwendet wird. Es ist viel richtiger, dieses Wissen allgemein zu halten – Klassen können Parameter haben.

Klassenparameter sind Variablen im Klassennamensraum, sie werden im Klassenkopf angegeben und können wie reguläre Variablen im Klassenkörper verwendet werden. Parameterwerte werden bei Verwendung der Klasse im Manifest angegeben.

Der Parameter kann auf einen Standardwert gesetzt werden. Wenn ein Parameter keinen Standardwert hat und der Wert bei Verwendung nicht festgelegt wird, führt dies zu einem Kompilierungsfehler.

Lassen Sie uns die Klasse aus dem obigen Beispiel parametrisieren und zwei Parameter hinzufügen: Der erste, erforderlich, ist der Pfad zur Konfiguration und der zweite, optional, ist der Name 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 werden Variablen typisiert. Essen viele Datentypen. Datentypen werden typischerweise zur Validierung von Parameterwerten verwendet, die an Klassen und Definitionen übergeben werden. Wenn der übergebene Parameter nicht mit dem angegebenen Typ übereinstimmt, tritt ein Kompilierungsfehler auf.

Der Typ wird unmittelbar vor dem Parameternamen geschrieben:

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

Klassen: Klassenname vs. Klasse{'Klassenname':} einschließen

Jede Klasse ist eine Ressource vom Typ Klasse. Wie bei jedem anderen Ressourcentyp kann es nicht zwei Instanzen derselben Klasse auf demselben Knoten geben.

Wenn Sie versuchen, eine Klasse zweimal mit demselben Knoten hinzuzufügen class { 'classname':} (kein Unterschied, bei unterschiedlichen oder identischen Parametern) kommt es zu einem Kompilierungsfehler. Wenn Sie jedoch eine Klasse im Ressourcenstil verwenden, können Sie alle ihre Parameter sofort explizit im Manifest festlegen.

Wenn Sie jedoch verwenden include, dann kann die Klasse beliebig oft hinzugefügt werden. Die Sache ist die include ist eine idempotente Funktion, die prüft, ob eine Klasse zum Verzeichnis hinzugefügt wurde. Wenn sich die Klasse nicht im Verzeichnis befindet, wird sie hinzugefügt. Wenn sie bereits vorhanden ist, wird nichts unternommen. Aber im Falle einer Verwendung include Sie können während der Klassendeklaration keine Klassenparameter festlegen – alle erforderlichen Parameter müssen in einer externen Datenquelle festgelegt werden – Hiera oder ENC. Wir werden im nächsten Artikel darüber sprechen.

Definiert

Wie im vorherigen Block erwähnt, kann dieselbe Klasse nicht mehr als einmal auf einem Knoten vorhanden sein. In manchen Fällen müssen Sie jedoch in der Lage sein, denselben Codeblock mit unterschiedlichen Parametern auf demselben Knoten zu verwenden. Mit anderen Worten: Es besteht Bedarf an einem eigenen Ressourcentyp.

Um beispielsweise das PHP-Modul zu installieren, gehen wir in Avito wie folgt vor:

  1. Installieren Sie das Paket mit diesem Modul.
  2. Erstellen wir 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 ist ein Design wie z definieren (definieren, definierter Typ, definierter Ressourcentyp). Ein Define ähnelt einer Klasse, es gibt jedoch Unterschiede: Erstens ist jedes Define ein Ressourcentyp und keine Ressource. Zweitens hat jede Definition einen impliziten Parameter $title, wohin der Ressourcenname geht, wenn er deklariert wird. Genau wie bei Klassen muss zunächst eine Definition beschrieben werden, um sie dann verwenden zu können.

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'],  # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
  }
  -> 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' }
}

Der einfachste Weg, den Fehler „Duplizierte Deklaration“ abzufangen, ist Definieren. Dies geschieht, wenn eine Definition eine Ressource mit einem konstanten Namen hat und es auf einem Knoten zwei oder mehr Instanzen dieser Definition gibt.

Davor kann man sich leicht schützen: Alle Ressourcen innerhalb der Definition müssen einen Namen haben, der davon abhängt $title. Eine Alternative ist das idempotente Hinzufügen von Ressourcen. Im einfachsten Fall reicht es aus, die allen Instanzen der Definition gemeinsamen Ressourcen in eine separate Klasse zu verschieben und diese Klasse in die Definitionsfunktion aufzunehmen include idempotent.

Es gibt andere Möglichkeiten, Idempotenz beim Hinzufügen von Ressourcen zu erreichen, nämlich die Verwendung von Funktionen defined и ensure_resources, aber davon erzähle ich euch in der nächsten Folge.

Abhängigkeiten und Benachrichtigungen für Klassen und Definitionen

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

  • Abhängigkeit von einer Klasse/Definition fügt Abhängigkeiten von allen Ressourcen der Klasse/Definition hinzu;
  • Eine Klassen-/Definitionsabhängigkeit fügt Abhängigkeiten zu allen Klassen-/Definitionsressourcen hinzu.
  • Klassen-/Definitionsbenachrichtigung benachrichtigt alle Ressourcen der Klasse/Definition;
  • Das Klassen-/Definitionsabonnement abonniert alle Ressourcen der Klasse/Definition.

Bedingte Anweisungen und Selektoren

Dokumentation hier.

if

Hier ist alles einfach:

if ВЫРАЖЕНИЕ1 {
  ...
} elsif ВЫРАЖЕНИЕ2 {
  ...
} else {
  ...
}

es sei denn

Es sei denn, es handelt sich um ein umgekehrtes Wenn: Der Codeblock wird ausgeführt, wenn der Ausdruck falsch ist.

unless ВЫРАЖЕНИЕ {
  ...
}

Häuser

Auch hier gibt es nichts Kompliziertes. Als Werte können Sie reguläre Werte (Strings, Zahlen usw.), reguläre Ausdrücke und Datentypen verwenden.

case ВЫРАЖЕНИЕ {
  ЗНАЧЕНИЕ1: { ... }
  ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
  default: { ... }
}

Selektoren

Ein Selektor ist ein Sprachkonstrukt ähnlich case, aber anstatt einen Codeblock auszuführen, gibt es einen Wert zurück.

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

Module

Wenn die Konfiguration klein ist, kann sie problemlos in einem Manifest gehalten werden. Aber je mehr Konfigurationen wir beschreiben, desto mehr Klassen und Knoten gibt es im Manifest, es wächst und es wird unpraktischer, damit zu arbeiten.

Darüber hinaus besteht das Problem der Wiederverwendung von Code: Wenn sich der gesamte Code in einem Manifest befindet, ist es schwierig, diesen Code mit anderen zu teilen. Um diese beiden Probleme zu lösen, verfügt Puppet über eine Entität namens Module.

Module – Hierbei handelt es sich um Gruppen von Klassen, Definitionen und anderen Puppet-Entitäten, die in einem separaten Verzeichnis abgelegt werden. Mit anderen Worten, ein Modul ist ein unabhängiger Teil der Puppet-Logik. Beispielsweise kann es ein Modul für die Arbeit mit Nginx geben, das nur das enthält, was für die Arbeit mit Nginx benötigt wird, oder es kann ein Modul für die Arbeit mit PHP und so weiter geben.

Module werden versioniert und auch Abhängigkeiten von Modulen untereinander werden unterstützt. Es gibt ein offenes Repository mit Modulen - Marionettenschmiede.

Auf dem Puppet-Server befinden sich Module im Unterverzeichnis „modules“ des Stammverzeichnisses. In jedem Modul gibt es ein Standardverzeichnisschema – Manifeste, Dateien, Vorlagen, Bibliothek usw.

Dateistruktur in einem Modul

Das Stammverzeichnis des Moduls kann die folgenden Verzeichnisse mit beschreibenden Namen enthalten:

  • manifests - Es enthält Manifeste
  • files - Es enthält Dateien
  • templates - Es enthält Vorlagen
  • lib – es enthält Ruby-Code

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

Namen von Ressourcen und Namen von Dateien im Modul

Dokumentation hier.

Ressourcen (Klassen, Definitionen) in einem Modul können nicht beliebig benannt werden. Darüber hinaus besteht eine direkte Entsprechung zwischen dem Namen einer Ressource und dem Namen der Datei, in der Puppet nach einer Beschreibung dieser Ressource sucht. Wenn Sie gegen die Benennungsregeln verstoßen, findet Puppet die Ressourcenbeschreibung einfach nicht und Sie erhalten einen Kompilierungsfehler.

Die Regeln sind einfach:

  • Alle Ressourcen in einem Modul müssen sich im Modul-Namespace befinden. Wenn das Modul aufgerufen wird foo, dann sollten alle darin enthaltenen Ressourcen benannt werden foo::<anything>oder nur foo.
  • Die Ressource mit dem Namen des Moduls muss in der Datei enthalten sein init.pp.
  • Für andere Ressourcen lautet das Dateibenennungsschema wie folgt:
    • das Präfix mit dem Modulnamen wird verworfen
    • Alle Doppelpunkte, sofern vorhanden, werden durch Schrägstriche ersetzt
    • Erweiterung hinzugefügt .pp

Ich werde es anhand eines Beispiels demonstrieren. Nehmen wir an, 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;
  • definieren nginx::server im Manifest beschrieben server.pp;
  • definieren nginx::server::location im Manifest beschrieben server/location.pp.

Patterns

Sicher wissen Sie selbst, was Vorlagen sind, ich werde sie hier nicht im Detail beschreiben. Aber ich werde es für alle Fälle belassen Link zu Wikipedia.

So verwenden Sie Vorlagen: Die Bedeutung einer Vorlage kann über eine Funktion erweitert werden template, dem der Pfad zur Vorlage übergeben wird. Für Ressourcen vom Typ Datei zusammen mit dem Parameter verwendet werden content. Zum Beispiel so:

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

Pfad anzeigen <modulename>/<filename> impliziert Datei <rootdir>/modules/<modulename>/templates/<filename>.

Außerdem gibt es eine Funktion inline_template — Es erhält den Vorlagentext als Eingabe, nicht den Dateinamen.

Innerhalb von Vorlagen können Sie alle Puppet-Variablen im aktuellen Bereich verwenden.

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

Kurz über ERB

Kontrollstrukturen:

  • <%= ВЫРАЖЕНИЕ %> — Geben Sie den Wert des Ausdrucks ein
  • <% ВЫРАЖЕНИЕ %> — Berechnen Sie den Wert eines Ausdrucks (ohne ihn einzufügen). Hier kommen normalerweise bedingte Anweisungen (if) und Schleifen (jeweils) zum Einsatz.
  • <%# КОММЕНТАРИЙ %>

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

Um auf Variablen aus dem Manifest zuzugreifen, müssen Sie hinzufügen @ zum Variablennamen. Um einen Zeilenumbruch zu entfernen, der nach einem Kontrollkonstrukt erscheint, müssen Sie ein schließendes Tag verwenden -%>.

Beispiel für die Verwendung der Vorlage

Nehmen wir an, ich schreibe ein Modul zur Steuerung von ZooKeeper. Die für die Erstellung der Konfiguration zuständige Klasse sieht in etwa 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 die entsprechende Vorlage zoo.cfg.erb - Also:

<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>

dataDir=<%= @datadir %>

<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>

Fakten und integrierte Variablen

Oftmals hängt der spezifische Teil der Konfiguration davon ab, was gerade auf dem Knoten passiert. Abhängig von der Debian-Version müssen Sie beispielsweise die eine oder andere Version des Pakets installieren. Sie können dies alles manuell überwachen und Manifeste neu schreiben, wenn sich Knoten ändern. Aber das ist kein seriöser Ansatz; Automatisierung ist viel besser.

Um Informationen über Knoten zu erhalten, verfügt Puppet über einen Mechanismus namens Fakten. Fakten – Hierbei handelt es sich um Informationen über den Knoten, die in Manifesten in Form gewöhnlicher Variablen im globalen Namespace verfügbar sind. Zum Beispiel Hostname, Betriebssystemversion, Prozessorarchitektur, Benutzerliste, Liste der Netzwerkschnittstellen und deren Adressen und vieles mehr. Fakten sind in Manifesten und Vorlagen als reguläre Variablen verfügbar.

Ein Beispiel für die Arbeit mit Fakten:

notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог

Formal gesehen hat ein Fakt einen Namen (String) und einen Wert (verschiedene Typen stehen zur Verfügung: Strings, Arrays, Wörterbücher). Essen Reihe integrierter Fakten. Sie können auch Ihre eigenen schreiben. Faktensammler werden beschrieben wie Funktionen in Rubyoder beides ausführbare Dateien. Auch Sachverhalte können im Formular dargestellt werden Textdateien mit Daten auf den Knoten.

Während des Betriebs kopiert der Puppet-Agent zunächst alle verfügbaren Faktensammler vom Pappetserver auf den Knoten, startet sie anschließend und sendet die gesammelten Fakten an den Server. Danach beginnt der Server mit der Zusammenstellung des Katalogs.

Fakten in Form ausführbarer Dateien

Solche Fakten werden in Modulen im Verzeichnis abgelegt facts.d. Natürlich müssen die Dateien ausführbar sein. Wenn sie ausgeführt werden, müssen sie Informationen entweder im YAML- oder im Schlüssel=Wert-Format an die Standardausgabe ausgeben.

Vergessen Sie nicht, dass die Fakten für alle Knoten gelten, die vom Poppet-Server gesteuert werden, auf dem Ihr Modul bereitgestellt wird. Stellen Sie daher im Skript sicher, dass das System über alle Programme und Dateien verfügt, die für die Funktion Ihres Fakts erforderlich sind.

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

Ruby-Fakten

Solche Fakten werden in Modulen 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

Textfakten

Solche Fakten werden auf Knoten im Verzeichnis platziert /etc/facter/facts.d im alten Marionetten- bzw /etc/puppetlabs/facts.d im neuen Puppet.

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

Den Fakten auf den Grund gehen

Es gibt zwei Möglichkeiten, sich den Fakten zu nähern:

  • durch das Wörterbuch $facts: $facts['fqdn'];
  • Verwenden des Faktennamens als Variablennamen: $fqdn.

Am besten verwenden Sie ein Wörterbuch $facts, oder noch besser, geben Sie den globalen Namespace an ($::facts).

Hier ist der relevante Abschnitt der Dokumentation.

Integrierte Variablen

Neben den Fakten gibt es auch einige Variablen, verfügbar im globalen Namespace.

  • vertrauenswürdige Fakten – Variablen, die dem Zertifikat des Clients entnommen werden (da das Zertifikat normalerweise auf einem Poppet-Server ausgestellt wird, kann der Agent sein Zertifikat nicht einfach übernehmen und ändern, daher sind die Variablen „vertrauenswürdig“): der Name des Zertifikats, der Name des Host und Domäne, Erweiterungen aus dem Zertifikat.
  • Fakten zum Server – Variablen im Zusammenhang mit Informationen über den Server – Version, Name, Server-IP-Adresse, Umgebung.
  • Agentenfakten – Variablen, die direkt vom Puppet-Agent hinzugefügt werden, und nicht von Faktor – Zertifikatsname, Agentenversion, Puppet-Version.
  • Mastervariablen - Pappetmaster-Variablen (sic!). Es ist ungefähr das Gleiche wie in Fakten zum Server, plus Konfigurationsparameterwerte sind verfügbar.
  • Compiler-Variablen — Compilervariablen, die sich in jedem Bereich unterscheiden: der Name des aktuellen Moduls und der Name des Moduls, in dem auf das aktuelle Objekt zugegriffen wurde. Sie können beispielsweise verwendet werden, um zu überprüfen, ob Ihre privaten Klassen nicht direkt von anderen Modulen verwendet werden.

Zusatz 1: Wie kann ich das alles ausführen und debuggen?

Der Artikel enthielt viele Beispiele für Puppet-Code, sagte uns jedoch überhaupt nicht, wie dieser Code ausgeführt wird. Nun, ich korrigiere mich.

Zum Ausführen von Puppet reicht ein Agent aus, in den meisten Fällen benötigen Sie jedoch auch einen Server.

Agent

Zumindest seit Version XNUMX gibt es Puppet-Agent-Pakete von offizielles Puppetlabs-Repository enthalten alle Abhängigkeiten (Ruby und die entsprechenden Gems), sodass es keine Installationsschwierigkeiten gibt (ich spreche von Debian-basierten Distributionen – wir verwenden keine RPM-basierten Distributionen).

Im einfachsten Fall reicht es zur Verwendung der Puppet-Konfiguration aus, den Agenten im serverlosen Modus zu starten: Vorausgesetzt, der Puppet-Code wird auf den Knoten kopiert, starten Sie ihn 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

Es ist natürlich besser, den Server einzurichten und die Agenten auf den Knoten im Daemon-Modus auszuführen – dann wenden sie alle halbe Stunde die vom Server heruntergeladene Konfiguration an.

Sie können das Push-Arbeitsmodell nachahmen – gehen Sie zu dem Knoten, der Sie interessiert, und beginnen Sie sudo puppet agent -t. Taste -t (--test) enthält tatsächlich mehrere Optionen, die einzeln aktiviert werden können. Diese Optionen umfassen Folgendes:

  • nicht im Daemon-Modus ausführen (standardmäßig startet der Agent im Daemon-Modus);
  • Herunterfahren nach dem Anwenden des Katalogs (standardmäßig arbeitet der Agent weiter und wendet die Konfiguration alle halbe Stunde an);
  • ein detailliertes Arbeitsprotokoll schreiben;
  • Änderungen in Dateien anzeigen.

Der Agent verfügt über einen Betriebsmodus ohne Änderungen – Sie können ihn verwenden, wenn Sie nicht sicher sind, ob Sie die richtige Konfiguration geschrieben haben und überprüfen möchten, was genau der Agent während des Betriebs ändern wird. Dieser Modus wird durch den Parameter aktiviert --noop auf der Kommandozeile: sudo puppet agent -t --noop.

Darüber hinaus können Sie das Debugging-Protokoll der Arbeit aktivieren – darin schreibt Puppet über alle Aktionen, die es ausführt: über die Ressource, die es gerade verarbeitet, über die Parameter dieser Ressource, darüber, welche Programme es startet. Natürlich ist das ein Parameter --debug.

Server

Ich werde in diesem Artikel nicht auf die vollständige Einrichtung des Pappetservers und die Bereitstellung von Code darauf eingehen; ich möchte nur sagen, dass es standardmäßig eine voll funktionsfähige Version des Servers gibt, die keine zusätzliche Konfiguration erfordert, um mit einer kleinen Anzahl von Servern zu arbeiten Knoten (sagen wir bis zu hundert). Eine größere Anzahl von Knoten erfordert eine Optimierung – standardmäßig startet puppetserver nicht mehr als vier Worker. Für eine höhere Leistung müssen Sie deren Anzahl erhöhen und nicht vergessen, die Speichergrenzen zu erhöhen, da der Server sonst die meiste Zeit Müll sammelt.

Code-Bereitstellung – wenn Sie es schnell und einfach benötigen, schauen Sie sich (bei r10k) an[https://github.com/puppetlabs/r10k], für kleine Installationen sollte es völlig ausreichen.

Anhang 2: Codierungsrichtlinien

  1. Platzieren Sie die gesamte Logik in Klassen und Definitionen.
  2. Behalten Sie Klassen und Definitionen in Modulen, nicht in Manifesten, die Knoten beschreiben.
  3. Nutzen Sie die Fakten.
  4. Erstellen Sie keine Ifs basierend auf Hostnamen.
  5. Fühlen Sie sich frei, Parameter für Klassen und Definitionen hinzuzufügen – das ist besser als implizite Logik, die im Hauptteil der Klasse/Definition versteckt ist.

Warum ich dies empfehle, erkläre ich im nächsten Artikel.

Abschluss

Lassen Sie uns mit der Einleitung abschließen. Im nächsten Artikel werde ich Ihnen von Hiera, ENC und PuppetDB erzählen.

An der Umfrage können nur registrierte Benutzer teilnehmen. Einloggenbitte.

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

  • 59,1%Fortgeschrittene Puppet-Konstrukte – ein bisschen Mist der nächsten Ebene: Schleifen, Mapping und andere Lambda-Ausdrücke, Ressourcensammler, exportierte Ressourcen und Kommunikation zwischen Hosts über Puppet, Tags, Anbieter, abstrakte Datentypen.13
  • 31,8%„Ich bin der Administrator meiner Mutter“ oder wie wir uns in Avito mit mehreren Poppet-Servern verschiedener Versionen angefreundet haben, und im Prinzip der Teil über die Verwaltung des Poppet-Servers.7
  • 81,8%Wie wir Puppet-Code schreiben: Instrumentierung, Dokumentation, Tests, CI/CD.18

22 Benutzer haben abgestimmt. 9 Benutzer enthielten sich der Stimme.

Source: habr.com