DEV Community

Martin Alfke for betadots

Posted on

Die Ruby-Seite von Puppet - Teil 1 - Benutzerdefinierte Fakten

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:

  1. Faktenerstellung
  2. Fakten in Modulen
  3. Allgemeine API
  4. Einschränkungen
  5. Hilfsfunktionen
  6. Zugriff auf andere Fakten
  7. Rückgabewerte
  8. Windows-Fakten
  9. Weitere Konzepte
  10. Codierungsstrategien und strukturierte Daten
  11. 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
Enter fullscreen mode Exit fullscreen mode

Um den Fakt auszuführen, verwenden man einen der folgenden Befehle:

  1. FACTERLIB=~/new_fact facter betadots_application_version
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Pro Ausführungsmethode:

Facter::Core::Execution::execute('<cmd>', options = {:timeout => 5})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 chunkbenö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
Enter fullscreen mode Exit fullscreen mode

Ergebnis:

betadots_application_version => {
  version => '1.2.4',
  cache_size => '400M',
  log_level => 'info',
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Facter cache groups

facter --list-cache-groups
EC2
  - ec2_metadata
  - ec2_userdata
GCE
  - gce
...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Facter Ergebnis:

{
  '/opt/app/bin/app' => {
    size => 64328,
    world_writable => null,
  },
  '/etc/backup/state.txt' => {
    size => 0,
    world_writable => 438,
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Facter Ergebnis:

{
  '/opt/app/bin/app' => {
    size => 64328,
    world_writable => null,
  },
  '/etc/backup/state.txt' => {
    size => 0,
    world_writable => 438,
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)