Dies ist der erste von drei Beiträgen, die die Konzepte und Best Practices zur Erweiterung von Puppet mit Hilfe von benutzerdefinierten Fakten, benutzerdefinierten Funktionen, benutzerdefinierten Typen und Providern behandeln.
Teil 1 (dieser Beitrag) erklärt benutzerdefinierte Fakten und wie ein Puppet Agent dem Puppet-Server Informationen bereitstellen kann.
Teil 2 wird sich auf benutzerdefinierte Funktionen konzentrieren und detailliert erläutern, wie Datenverarbeitungs- oder Ausführungsfunktionen entwickelt werden können.
Teil 3 wird benutzerdefinierte Typen und Provider abdecken, die die Funktionalität der Puppet-DSL erweitern.
Benutzerdefinierte Fakten:
Es gibt mehrere Gründe, benutzerdefinierte Fakten zu entwickeln:
- Wann immer ein VM-Namensschema verwendet wird, empfehlen wir, das Namensschema als benutzerdefinierten Fakt bereitzustellen.
- Es ist manchmal erforderlich, den Zustand bestimmter Konfigurationen zu bestimmen oder ob bestimmte Software installiert ist.
- Die Rechenzentrumsabteilung benötigt möglicherweise einen Überblick über die physische Hardware, Details zu den Hardwareanbietern und End-of-Life (EOL)-Supportdaten. Darüber hinaus kann die Finanzabteilung Informationen über die Anzahl der verwendeten kommerziellen Lizenzen und die Versionen der installierten Software benötigen.
Wann immer Informationen von einem Puppet Agenten benötigt werden, bietet Puppet die Möglichkeit, benutzerdefinierte Fakten zu implementieren.
Inhalt:
- Faktenerstellung
- Fakten in Modulen
- Allgemeine API
- Einschränkungen
- Hilfsfunktionen
- Zugriff auf andere Fakten
- Rückgabewerte
- Windows-Fakten
- Weitere Konzepte
- Codierungsstrategien und strukturierte Daten
- Zusammenfassung
Faktenerstellung
Benutzerdefinierte Fakten können lokal auf einem Arbeitsplatz oder auf dem System entwickelt werden, auf dem der Fakt benötigt wird. Bevor der ungetestete Fakt in den Puppet-Code aufgenommen wird, kann der Faktladepfad auf zwei Arten angegeben und unabhängig gestestet werden.
Die folgende Verzeichnisstruktur wird verwendet:
ls ~/new_fact/
betadots_application_version.rb
Um den Fakt auszuführen, verwenden man einen der folgenden Befehle:
FACTERLIB=~/new_fact facter betadots_application_version
facter --custom-dir ~/new_fact betadots_application_version
Fakten in Modulen
Puppet stellt eine API zur Entwicklung benutzerdefinierter Fakten bereit. Um sicherzustellen, dass der Puppet-Agent sie findet, müssen benutzerdefinierte Fakten in einem bestimmten Verzeichnis innerhalb eines Moduls abgelegt werden.
Alle Dateien im lib
-Verzeichnis von Modulen werden auf alle Agenten synchronisiert. Es ist nicht möglich, einzuschränken, welche Dateien synchronisiert werden.
Benutzerdefinierte Fakten sollten im Verzeichnis lib/facter
eines Moduls abgelegt werden. Obwohl man einen beliebigen Dateinamen wählen kann, wird empfohlen, den Namen des Fakts als Dateinamen zu verwenden, um eine einfache Identifikation zu gewährleisten. Der Dateiname muss mit .rb
enden.
Wir empfehlen außerdem, dem Namen des benutzerdefinierten Fakts einen Firmen- oder Abteilungspräfix hinzuzufügen.
Allgemeine API
Facter bietet eine API zum Erstellen neuer benutzerdefinierter Fakten.
Beispiel:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
setcode do
# Code hier
end
end
Der Block setcode do ... end
enthält den gesamten Ruby-Code für den Fakt. Das Ergebnis des letzten Befehls oder der letzten Variablen wird als Rückgabewert des Fakts verwendet.
Einschränkungen
Da alle benutzerdefinierten Fakten an alle Puppet-Agenten verteilt werden, ist es wichtig sicherzustellen, dass betriebssystem- oder anwendungsspezifische Fakten nur dort ausgeführt werden, wo es erforderlich ist.
Dies wird durch Einschränkungen (confining) erreicht.
Einschränkungen können auf einfache oder strukturierte Fakten oder auf anderen Ruby-Code wie File.exist?
angewendet werden.
Einfache Fakt-Einschränkung:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine kernel: 'Linux'
setcode do
# Code hier
end
end
Einschränkung unter Verwendung eines Ruby-Blocks und Fakten:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine 'os' do |os|
os['family'] == 'RedHat'
end
setcode do
# Code hier
end
end
Einschränkung unter Verwendung eines Ruby-Blocks und Ruby-Code:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine do
File.exist?('/etc/sysconfig/application')
end
setcode do
# Code hier
end
end
Hilfsfunktionen
Facter bietet mehrere Hilfsmethoden, die verwendet werden können, ohne dass die Klasse facter
erforderlich ist:
Hilfsfunktion | Beschreibung |
---|---|
Facter::Core::Execution::execute |
Führt ein ausführbares Programm auf dem System mit einer Timeout-Option aus, um das Aufhängen von Facter-Läufen zu verhindern. |
Facter::Core::Execution::which |
Prüft, ob ein ausführbares Programm verfügbar ist. Funktioniert auf jedem unterstützten Betriebssystem und durchsucht die Standardpfade ENV['PATH'] + ['/sbin', '/usr/sbin'] . |
Facter.value |
Bietet Zugriff auf den Wert eines anderen Fakts. Achtung! Loops vermeiden! |
Weitere Informationen findet man in der API-Dokumentation.
Bitte beachten, dass Facter::Core::Execution::exec
zugunsten von Facter::Core::Execution::execute
veraltet ist. Dies ist wichtig, wenn man von älteren Versionen von Facter migriert.
Die timeout
-Options-Hash kann auf zwei Arten festgelegt werden:
Gültig für den gesamten Fakt:
Facter.add('<name>', {timeout: 5}) do
...
end
Pro Ausführungsmethode:
Facter::Core::Execution::execute('<cmd>', options = {:timeout => 5})
Zum Beispiel, wenn eine Anwendung ihre Konfiguration über /opt/app/bin/app config
zurückgibt, kann man ein Timeout von 5 Sekunden festlegen:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine do
File.exist?('/etc/sysconfig/application')
end
setcode do
Facter::Core::Execution.execute('/opt/app/bin/app config', {:timeout => 5})
end
end
Das Ergebnis des letzten Befehls oder die Angabe einer Variable wird als Faktenergebnis verwendet.
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine do
File.exist?('/etc/sysconfig/application')
end
setcode do
config_list = Facter::Core::Execution.execute('/opt/app/bin/app config', {:timeout => 5})
config_list
end
end
Zugriff auf andere Fakten
Es ist möglich, dass ein benutzerdefinierter Fakt den Wert eines anderen Fakts verwendet, indem er Facter.value
verwendet. Dies sollte jedoch vorsichtig geschehen, um zyklische Abhängigkeiten zwischen Fakten zu vermeiden.
In unserem Beispiel hat die Anwendung unterschiedliche Konfigurationspfade für verschiedene Betriebssysteme.
- RedHat - /etc/sysconfig/application
- Debian - /etc/default/application
Beispiel mit Zugriff auf einen anderen Fakt:
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version) do
confine do
app_cfg_file = case Facter.value('os')['family']
when 'RedHat'
'/etc/sysconfig/application'
when 'Debian'
'/etc/default/application'
end
File.exist?(app_cfg_file)
end
setcode do
Facter::Core::Execution.execute('/opt/app/bin/app config')
end
end
Das Beispiel ruft den os
-Fakt ab und verwendet den family
-Schlüssel, um das Betriebssystem zu vergleichen, und wendet dann eine Logik an, um zu bestimmen, welchen Anwendungskonfigurationspfad geprüft werden soll.
Rückgabewerte
Puppet erwartet, dass benutzerdefinierte Fakten einen gültigen Werttyp zurückgeben, insbesondere Strings, Ganze Zahlen oder Arrays.
Wenn der Wert eines Fakts nil
oder undef
ist, wird der Fakt einfach nicht definiert.
Windows-Fakten
Facter ist eine plattformübergreifende API und stellt die gleichen Funktionen auf allen Betriebssystemen bereit. Es ist jedoch wichtig zu beachten, dass Pfade und ausführbare Dateien in einer Windows-Umgebung unterschiedlich sein können.
Mit Hilfe des Facter::Core::Execution.execute
werden üblicherweise Powershell Kommandos gestartet.
Das folgende Beispiel erstellt einen Windows-Fakt:
# ~/new_fact/windows_version.rb
Facter.add(:windows_version) do
confine osfamily: 'windows'
setcode do
Facter::Core::Execution.execute('reg query "HKLM\Software\Microsoft\Internet Explorer" /v svcVersion', {:timeout => 5})
end
end
Das Beispiel verwendet den reg query
-Befehl, um die Version des Internet Explorers zu erhalten.
Bitte beachten, dass die Win32 API calls seit Ruby 1.9 deprecated sind.
Man kann statt dessen die Fiddle oder andere Ruby Bibliotheken nutzen.
Weitere Konzepte
Protokollierung
Zur Analyse eines Facts kann man auf die debug
Methode zugreifen.
Facter.add(:betadots_application_version) do
setcode do
Facter.debug("Custom fact 'betadots_application_version' running")
...
end
end
Es sind folgende Log Level möglich: debug
, info
, warn
, error
und fatal
.
Aggregierung
Eine weitere Option ist das Zusammenstellen von Facts als Hash mit Hilfe von aggregates
und chunks
.
Jeder chunk
benötigt einen Namen (key) und einen Wert (value), wobei der Wert entweder ein Array oder ein Hash sein muss!
Nachdem Facter alle chunks eingelesen und validiert hat, werden diese aggregiert. Als default werden die Arrays oder Hashes gemerged.
Es besteht die Möglichkeit, eine eigene Aggregierungs Funktion zu schreiben.
Wir gehen davon aus, dass unsere Beispielanwendung es uns ermöglicht, Einstellungen auszulesen: /opt/app/bin/app config <key>
.
# ~/new_fact/betadots_application_version.rb
Facter.add(:betadots_application_version, :type => :aggregate) do
confine do
app_cfg_file = case Facter.value('os')['family']
when 'RedHat'
'/etc/sysconfig/application'
when 'Debian'
'/etc/default/application'
end
File.exist?(app_cfg_file)
end
chunk(:config_version) do
{:version => Facter::Core::Execution.execute('/opt/app/bin/app config version')}
end
chunk(:config_cache_size) do
{:cache_size => Facter::Core::Execution.execute('/opt/app/bin/app config cache_size')}
end
chunk(:config_log_level) do
{:log_level => Facter::Core::Execution.execute('/opt/app/bin/app config log_level')}
end
end
Ergebnis:
betadots_application_version => {
version => '1.2.4',
cache_size => '400M',
log_level => 'info',
}
Gewichtung
Facter ermöglicht es, den Rückgabe Wert auf unterschiedliche Arten zu ermitteln. Die einzelnen Arten bekommen eine Gewichtung, damit Facter danach entscheiden kann, welche Art (Methode) vals Rückgabewert verwendet werden soll.
Wir gehen davon aus, dass unsere Besipielanwendung entweder über systemd oder über eine andere Art gestartet wurde (PID Datei).
Hinweis: Externe Fakten haben eine Gewichtung von 1000. Man kann externe Fakten überschreiben, indem man für den Custom Fakt den gleichen Namen verwendet und eine Gewichtung über 1000 hinterlegt.
Weitere Informationen findet man in der Fact precedence Sektion der Custom facts overview Seite.
Facter.add('application_running') do
has_weight 100
setcode do
Facter::Core::Execution.execute('systemctl status app')
end
end
Facter.add('application_running') do
has_weight 50
setcode do
File.exist?('/var/run/application.pid')
end
end
Blocking und Caching
Normalerweise werden Facts bei jedem Lauf eines Puppet Agenten erneut ermittelt. Dies kann bei manchen Facts dazu führen, dass Facter mehr Zeit benötigt, bis alle Facts geladen sind. Dies gilt insbesondere, wenn Facts eine Anwendung abfragen und diese Abfrage eine hohe Last erzeugt und lange dauert oder es gibt "teure" Facts, die gar nicht benötigt werden.
Facter ermöglicht es, einen Cache zu verwenden, so dass Facts nur einmalig und danch erst nach der Lebensdauer des Cache Objektes erneut abgefragt werden und man kann Blacklisten mit unerwünschen Facts pflegen.
Die Facter Konfiguration erfolgt in /etc/puppetlabs/facter/facter.conf
auf *nix Systemen oder in C:\ProgramData\PuppetLabs\facter\etc\facter.conf
auf Windows.
Bitte beachten, dass die Konfiguration auf dem Puppet Agent erfolgen muss!
# /etc/puppetlabs/facter/facter.conf
facts : {
blocklist : [ "file system", "EC2", "processors.isa" ]
ttls : [
{ "timezone": 30 days },
{ "my-fact-group": 30 days },
]
}
fact-groups : {
my-fact-group : [ "os", "ssh.version"]
}
Innerhalb des Elementes blocklist
kann man entweder einen Fact, ein Subelement eines Fact Hashes oder eine Facter Gruppe angeben.
Existierende Facter Gruppen kann man aus Facter auslesen:
Facter block groups
facter --list-block-groups
EC2
- ec2_metadata
- ec2_userdata
file system
- mountpoints
- filesystems
- partitions
hypervisors
- hypervisors
Facter cache groups
facter --list-cache-groups
EC2
- ec2_metadata
- ec2_userdata
GCE
- gce
...
Außerdem kann man eigene Facter Gruppen innerhalb der facts-group
Sektion hinterlegen.
Bitte beachten, dass das Dylan Ratcliffe's facter_cache Module seit Puppet 6 nicht mehr notwendig ist.
Mehr Informationen findet man auf der Puppet Webseite im Bereich facter.conf
settings.
Codierungsstrategien und strukturierte Daten
- Anzahl dr Fakten redizieren
- Hashes verwenden
- Generischer Code und Ruby Module
Wenn ein Fakt komplexe Daten zurückgeben muss, sollten strukturierte Daten verwendet werden, um hierarchische Informationen bereitzustellen. Weitere Informationen zur Strukturierung von Daten finden Sie in der Puppet-Dokumentation zu strukturierten Daten in benutzerdefinierten Fakten.
Eine bewährte Praxis besteht darin, sich für eine Struktur zu entscheiden, wenn ein Fakt mehr als eine einzelne Zeichenfolge zurückgeben soll. Solche Daten werden in der Regel in Hashes oder Arrays organisiert, die wahlweise direkt einen Hash liefern oder über aggregates zusammengesetzt werden:
Lösung 1: Hash in Ruby:
Facter.add(:betadots_file_check) do
setcode do
file_list = case Facter.value(:kernel)
when 'windows'
[
'c:/Program Data/Application/bin/app',
'c:/backup/state.txt',
]
when 'Linux'
[
'/opt/app/bin/app',
'/etc/backup/state.txt',
]
end
result = {}
file_list.each |name| do
result[name] = {
:size => File.size(name),
:world_writable => File.world_writable?(name)
} if File.exist?(name)
end
result
end
end
Facter Ergebnis:
{
'/opt/app/bin/app' => {
size => 64328,
world_writable => null,
},
'/etc/backup/state.txt' => {
size => 0,
world_writable => 438,
}
}
Lösung 2: Hashes mit Hilfe von aggregate:
Facter.add(:betadots_file_check, :type => :aggregate) do
file_list = case Facter.value(:kernel)
when 'windows'
[
'c:/Program Data/Application/bin/app',
'c:/backup/state.txt',
]
when 'Linux'
[
'/opt/app/bin/app',
'/etc/backup/state.txt',
]
end
chunk(:file_size) do
result = {}
file_list.each |name| do
result[name] = { :size => File.size(name) } if File.exist?(name)
end
result
end
chunk(:file_world_writable) do
result = {}
file_list.each |name| do
result[name] = { :world_writable => File.world_writable?(name) } if File.exist?(name)
end
result
end
end
Facter Ergebnis:
{
'/opt/app/bin/app' => {
size => 64328,
world_writable => null,
},
'/etc/backup/state.txt' => {
size => 0,
world_writable => 438,
}
}
Ein weiteres Facter Besipiel liest das Agent Zertifikat aus und liefert die Zertifikatserweiterungen als Fact Hash:
Facter.add(:betadots_cert_extension) do
setcode do
require 'openssl'
require 'puppet'
require 'puppet/ssl/oids'
# set variables
extension_hash = {}
certdir = Puppet.settings[:certdir]
certname = Puppet.settings[:certname]
certificate_file = "#{certdir}/#{certname}.pem"
# get puppet ssl oids
oids = {}
Puppet::SSL::Oids::PUPPET_OIDS.each do |o|
oids[o[0]] = o[1]
end
# read the certificate
cert = OpenSSL::X509::Certificate.new File.read certificate_file
# cert extensions differs if we run via agent (numeric) or via facter (names)
cert.extensions.each do |extension|
case extension.oid.to_s
when %r{^1\.3\.6\.1\.4\.1\.34380\.1\.1}
short_name = oids[extension.oid]
value = extension.value[2..-1]
extension_hash[short_name] = value unless short_name == 'pp_preshared_key'
when %r{^pp_}
short_name = extension.oid
value = extension.value[2..-1]
extension_hash[short_name] = value unless short_name == 'pp_preshared_key'
end
end
extension_hash
end
end
Zusammenfassung
Dieser Beitrag hat die Grundlagen zur Erstellung von benutzerdefinierten Fakten in Puppet beschrieben. Benutzerdefinierte Fakten sind ein leistungsfähiges Werkzeug, um einem Puppet-Server oder einem Management-Tool Informationen zu liefern. Facts werden in Modulen hinterlegt und sollten bei Bedarf eingeschränkt (confine) werden. Die Rückgabewerte sollten als Hash oder Array und nur in seltenen Fällen als Bool, String oder Integer ausgegeben werden.
Ein letzter Hinweis: Nur lokale Kommandos ausführen! Wenn man Remote Systeme in Kombination mit Facter nutzen möchte, müssen diese Hochverfügbar und skaliert aufgebaut sein.
Happy puppetizing,
Martin
Top comments (0)