Dies ist der zweite Beitrag einer dreiteiligen Serie, in der die Konzepte und Best Practices zum Erweitern von Puppet mithilfe von benutzerdefinierten Facts, benutzerdefinierten Funktionen und benutzerdefinierten Typen und Providern behandelt werden.
Teil 1 behandelt Benutzerdefinierte Facts, die erklären, wie ein Knoten Informationen an den Puppet-Server übermitteln kann.
Teil 2 (dieser Beitrag) konzentriert sich auf Benutzerdefinierte Funktionen und zeigt, wie man Funktionen für Datenverarbeitung und Ausführung entwickelt.
Teil 3 wird darstellen, wie man die DSL-Funktionalität von Puppet mit neuen Typen und Provider erweitert.
Benutzerdefinierte Funktionen in Puppet:
Funktionen in Puppet werden auf dem Puppet-Server, insbesondere im Compiler, ausgeführt. Diese Funktionen haben Zugriff auf alle Facts und Puppet-Variablen, solange sie mit dem Namensraum angegeben werden.
Es gibt zwei Haupttypen von Funktionen in Puppet:
- Anweisungsfunktionen
- Rückgabefunktionen
Anweisungsfunktionen können mit den internen Mechanismen von Puppet interagieren. Die gebräuchlichsten Anweisungsfunktionen sind include
und contain
, die Klassen zum Katalog hinzufügen, oder noop
und notice
, die alle Ressourcen in den Simulationsmodus versetzen oder Einträge in die Protokolldatei des Puppet-Servers schreiben.
Rückgabefunktionen wie lookup
oder read_url
geben Werte aus Hiera oder externen Quellen wie Webservern zurück. Einige Rückgabefunktionen können auch Daten manipulieren (each
, filter
, map
, reduce
).
Warum benutzerdefinierte Funktionen erstellen?
Hier sind einige Gründe, warum man benutzerdefinierte Funktionen in Puppet entwickeln könnte:
- Erstellen einer benutzerdefinierten Hiera-Suchfunktion
- Umwandeln von Datenstrukturen für spezifische Anforderungen
Inhalt:
- Funktion-Entwicklung
- Funktionen in Modulen
- Allgemeine API
- Dispatcher und Definition
- Verwendung von Funktionen in der Puppet-DSL
- Zusammenfassung
Funktion-Entwicklung
Da Funktionen nur zur Kompilierzeit ausgeführt werden, empfiehlt es sich, sich zunächst auf ihre Kernfunktionalität zu konzentrieren, bevor sie in die Funktions-API integriert werden. Dadurch kann die Logik mit lokalen Daten getestet werden, ohne dass Probleme auf dem Puppet-Server auftreten, die die Kompilierung unterbrechen oder den Server sogar zum Absturz bringen könnten.
Als Beispiel wird eine Datenstruktur definiert, die verschiedene Aspekte der Konfiguration eines Systems darstellt. Mit einer Funktion kann man bestimmte Teile dieser Datenstruktur als Variablen verfügbar machen.
Beispiel für eine Datenstruktur:
server_hash:
'postfix':
'config':
'transport': 'mta'
'ssl': true
'apache':
'config':
'vhost': 'server.domain.tld'
'document_root': '/srv/www/html/server.domain.tld'
'ssl': true
'proxy_pass': 'http://localhost:8080'
'tomcat':
'config':
'application': 'crm'
'service': 'frontend'
Angenommen, diese Datenstruktur stammt aus Hiera, und man möchte anderen Teams, die ihre eigenen Module schreiben, die Wiederverwendung dieser Daten ohne zusätzliche Abfragen ermöglichen. Dadurch kann man die interne Datenstruktur ändern und die Funktion anpassen, während die Teams weiterhin die benötigten Informationen zur Verfügung gestellt bekommen.
Beispiel für eine Funktion:
# Test Daten
server_hash = {
'postfix' => {
'config' => {
'transport' => 'mta',
'ssl' => true
}
},
'apache' => {
'config' => {
'vhost' => 'server.domain.tld',
'document_root' => '/srv/www/html/server.domain.tld',
'ssl' => true,
'proxy_pass' => 'http://localhost:8080'
}
},
'tomcat' => {
'config' => {
'application' => 'crm',
'service' => 'frontend'
}
}
}
# Funktion definition
def read_service_config(data, service)
data[service]
end
# Testen der Funktion
puts read_service_config(server_hash, 'tomcat')
Funktionen in Modulen
Puppet stellt eine API zum Erstellen benutzerdefinierter Funktionen zur Verfügung. Damit der Puppet Compiler diese Funktionen finden kann, müssen sie in bestimmten Verzeichnissen innerhalb eines Moduls abgelegt werden.
Dateien im lib
-Verzeichnis des Moduls werden auf alle Agenten synchronisiert. Es ist nicht möglich, zu steuern, welche Dateien synchronisiert werden.
Benutzerdefinierte Funktionen können zu einer der beiden folgenden Verzeichnisstrukturen hinzugefügt werden:
-
lib/puppet/parser/functions
- Funktionen API v1 -
lib/puppet/functions
- Funktionen API v2
Die neuere Funktionen-API v2 unterstützt Unterverzeichnisse, sodass man Funktionen in Namespaces organisieren können. Der Dateiname sollte mit dem Funktionsnamen übereinstimmen.
In diesem Beitrag konzentrieren wir uns auf die moderne API v2.
Beispiel für eine Verzeichnisstruktur:
# <modulepath>/stdlib/
\- lib/
\- puppet/
\- functions/
\- stdlib/
\- to_yaml.rb
# stdlib/lib/puppet/functions/stdlib/to_yaml.rb
Puppet::Functions.create_function(:'stdlib::to_yaml') do
# ...
end
Wir empfehlen, benutzerdefinierte Funktionen mit dem Modulnamen zu präfixen, um Namenskonflikte zu vermeiden.
Allgemeine API
Um benutzerdefinierte Funktionen in Puppet zu erstellen, verwendet man die folgende Struktur:
Beispiel:
# <modulepath>/betadots/lib/puppet/functions/betadots/resolve.rb
Puppet::Functions.create_function(:'betadots::resolve') do
# ...
end
Bitte beachten, dass der Funktions Namespace identisch zum Modulnamen (betadots) sein muss.
Dispatcher und Definition
Innerhalb des do ... end
Blocks der Funktion muss ein Dispatcher und eine Definition hinzugefügt werden. Der Dispatcher validiert die Eingabedaten und verknüpft sie mit einer benannten Definition, die den entsprechenden Ruby-Code ausführt.
Der Dispatcher kann Datentypen mithilfe von param
prüfen und Werte Ruby-Symbolen zuordnen. Die Definition verwendet den Namen des Dispatchers und die im Dispatcher bereitgestellten Parameter.
Der letzte Befehl in der Definition bestimmt den Rückgabewert.
Beispiel für einen Dispatcher für eine IPv4-Adresse:
# <modulepath>/betadots/lib/puppet/functions/betadots/resolve.rb
Puppet::Functions.create_function(:'betadots::resolve') do
dispatch :ip do
param 'Regexp[/^(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})$/]', :ipaddr
end
end
Hier ist eine Tabelle mit gängigen Methoden für Parameter in Dispatchern:
Methode | Beschreibung |
---|---|
param oder required_param
|
Ein obligatorisches Argument. Kann mehrfach verwendet werden. Position: Alle Pflichtargumente müssen zuerst erscheinen. |
optional_param |
Ein Argument, das ausgelassen werden kann. |
repeated_params oder optional_repeated_params
|
Ein wiederholbares Argument, das null oder mehr Werte annehmen kann. |
required_repeated_param |
Ein wiederholbares Argument, das einen oder mehr Werte annehmen muss. |
block_param oder required_block_param
|
Ein obligatorisches Lambda. |
optional_block_param |
Ein optionales Lambda. |
Hinzufügen einer Definition:
Beschreibung | Dispatcher | Definition |
---|---|---|
Name | dispatch :ip |
def ip |
Parameter | param ..., :ipaddr |
def ip(ipaddr) |
# <modulepath>/betadots/lib/puppet/functions/betadots/resolve.rb
Puppet::Functions.create_function(:'betadots::resolve') do
require 'resolv'
dispatch :ip do
param 'Regexp[/^(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})$/]', :ipaddr
end
def ip(ipaddr)
Resolv.getname ipaddr
end
end
Nun können zusätzliche Dispatcher und Definitionen hinzugefügt werden:
# <modulepath>/betadots/lib/puppet/functions/betadots/resolve.rb
Puppet::Functions.create_function(:'betadots::resolve') do
require 'socket'
dispatch :ip do
param 'Regexp[/^(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})$/]', :ipaddr
end
dispatch :dnsname do
param 'Regexp[/.*/]', :dnsname
end
dispatch :local_hostname do
param 'Regexp[//]', :local_hostname
end
def ip(ipaddr)
Addrinfo.tcp(ipaddr, '80').getnameinfo[0]
end
def dnsname(dnsname)
Addrinfo.ip(dnsname).getnameinfo[0]
end
def local_hostname
Socket.gethostname
end
end
Verwendung von Funktionen in der Puppet-DSL
Innerhalb von Puppet werden Funktionen in der Regel während der Kompilierzeit ausgeführt.
Eine besondere Ausnahme bildet die deferred function, die auf dem Agenten ausgeführt wird.
Im folgenden betrachten wir die zwei Anwendungsfälle:
- Verwendung der
resolve
-Funktion im Compiler - Verwendung der
resolve
-Funktion auf dem Agenten
Anwendungsfall 1: Ausführung im Compiler
Die Funktion kann an beliebiger Stelle im Code verwendet werden:
class betadots::resolver (
Stdlib::Fqdn $local_hostname = betadots::resolve(),
) {
$ip_from_google = betadots::resolve('www.google.com')
}
Anwendungsfall 2: Ausführung auf dem Agenten
Die Funktion muss in deferred mode deklariert werden, um in Puppet auf dem Agenten ausgeführt zu werden. Die Funktion sollte nicht als Parameter im Header der Klasse verwendet werden, da dies möglicherweise den Funktionsaufruf mit hiera-Daten überschreiben könnte.
class betadots::local_resolve (
) {
$local_api_ip = Deferred('betadots::resolve', ['api.int.domain.tld'])
}
Zusammenfassung
Puppet bietet eine stabile API zum Erstellen benutzerdefinierter Funktionen.
Bevor eine neue Funktion erstellt wird, bitte prüfen, ob bereits eine Lösung im Stdlib- oder Extlib-Modul vorhanden ist.
Funktionen sollten mit dem Modulnamen zu präfixen, um Konflikte zu vermeiden und deren Herkunft zu kennzeichnen.
Viel Erfolg beim Puppetisieren,
Martin
Top comments (0)