DEV Community

Martin Alfke for betadots

Posted on

Moderne Puppet Node Klassifizierung

Innerhalb von Puppet werden mit Hilfe von Modulen bestimmte technische Komponenten konfiguriert.
Module können Upstream entwickelt und auf der Puppet Forge zur Verfügung gestellt werden. Diese Module nennen wir gerne Bibliotheken oder Komponenten Module.
Alternativ handelt es sich um selbst geschriebene Technische Implementierungs-Profile.

Da es sich bei Puppet um ein Client-Server Modell handelt, muss der Server wissen, welche Module auf einem System zum Einsatz kommen sollen. Diesen Vorgang nennt man Node Klassifizierung.

Innerhalb von Puppet stehen unterschiedliche Varianten für die Node Klassifizierung zur Verfügung.
Dieser Artikel beschreibt die klassische Node Klassifizierung und ihre Einschränkungen und geht danach auf flexiblere, Hiera daten-basierte Node Klasifizierung ein.

  1. Klassische Node Klassifizierung
    1. Node Resource Typ
    2. Rollen Klassifizierung
    3. External Node Klassifizierung
  2. Hiera basierte Klassifizierung
    1. Hiera Array
    2. Hiera Hash
    3. Hiera Multiple Hashes
  3. Zusammenfassung

Klassische Node Klassifizierung

Der Puppet Server muss wissen, wo die Node Klassifizierungen zu finden sind. Dafür liest der Puppet Server zuerst die Konfigurationseinstellung manifest. Hier erwartet der Puppet Server einen Pfad und innerhalb des Pfades die Datei site.pp.
Der Standardwert lautet: /etc/puppetlabs/code/environments/production/manifests

Innerhalb der site.pp Datei (und weiteren Dateien und Unter-Verzeichnissen) kann man Puppet Code für Node Klassifizierung hinterlegen.

Node Resource Typ

Die einfachste Lösung ist die Verwendung des node Resource Typen. Die Node Resource verwendet den Distinguished oder Common Name des Puppet Agent Zertifikates zur Identifikation. Dieser basiert normalerweise auf dem FQDN des Systems.
Ausserdem sind reguläre Ausdrücke möglich.

# manifests/site.pp
...
node 'app1web03-dev.domain.tld' {
  include profile::base
  include profile::accounts::dev
  include profile::webserver::nginx
  include profile::application::app01
}
...
Enter fullscreen mode Exit fullscreen mode

Hinweis: Der Puppet Server muß ein Node Klassifizierungs Objekt erhalten. Daher kann man einen leeren 'default' Fallback Node hinterlegen:

node default {}
Enter fullscreen mode Exit fullscreen mode

Die Node Resource ist eine einfache Lösung und eignet sich für kleinere Serverlandschaften mit wenigen Nodes.
Bei größeren Umgebungen wird dieser Ansatz sehr zeitintensiv - auch wenn man die Möglichkeit nutzt, Servergruppen in einzelne Dateien und Verzeichnisse auszulagern.

Rollen Klassifizierung

In der Datei site.pp kann beliebiger Puppet Code hinterlegt werden. Hier kann man z.B. auch auf Facts oder Hiera Daten zugreifen.
Wenn alle Systeme einen Facts Namen role hinterlegt bekommen haben, kann man diesen Fact verwenden, um Rollen Klassen einzubinden:

# manifests/site.pp
include "role::${facts['role']}"
Enter fullscreen mode Exit fullscreen mode

Das Rollen Prinzip macht Sinn, wenn man eine Vielzahl gleicher Systeme hat, die identisch konfiguriert werden müssen.
Dies bedeutet aber auch, dass jeder Server eine Rolle bekommen muss.
Bei vielen unterschiedlichen Server Rollen ist auch hier mit einem Mehraufwand zu rechnen.

External Node Klassifizierung

Mit Puppet ist man in der Lage beliebige Tools für die Node Klassifizierung zu verwenden. Diese Tools werden aus Puppet Sicht 'External Node Classifiers (ENC)' genant. Diese Option wird von Puppet Enterprise und Foreman genutzt.

Achtung: Man muss beachten, dass die Node Klassifizierungen in einem ENC eine zusätzliche Option ist und kein Ersatz!
Wenn ein Node eine Klassifizierung sowohl in manifests wie auch im ENC hat, werden die Klassifizierungen beider Verfahren genutzt.

Damit Puppet einen ENC verwenden kann, müssen Konfigurationseinstellungen vorgenommen werden:

# /etc/puppetlabs/puppet/puppet.conf
[master]
  node_terminus = exec
  external_nodes = /usr/local/bin/enc
Enter fullscreen mode Exit fullscreen mode

In diesem Fall führt der Puppet Server das Kommando aus, welches unter external_nodes angegeben ist und übergibt den Common Name des Agent Zertifikates als Argument.
Das Kommando wird durch den Puppet Server user ausgeführt, kann in einer beliebigen Programmiersprache erstellt worden sein und unterschiedliche Quellen referenzieren (Anfrage an eine Web API, eine Datenbank, Inhalte von Dateien, ...) und muss YAML Ausgabe produzieren und :

---
environment: production
classes:
  profile::base:
    time_servers: ['time.domain.tld']
  profile::accounts::dev: {}
  profile::webserver::nginx: {}
  profile::application::app01: {}
Enter fullscreen mode Exit fullscreen mode

Innerhalb des classes Keys erwartet der Puppet Server ein Array oder einen Hash von Klassen, welche für den Node eingebunden werden sollen.
Wenn Hashes verwendet werden, kann man bei jedem Element einen Sub-Hash von Klassenparametern mit Werten setzen.
Im optionalen Key environment kann man das Node Environment festlegen, welches für den Node verwendet werden muss.

Im folgenden ein Shell Script, welches Dateien in einem Verzeichnis ausliest:

# /usr/local/bin/enc
#!/bin/bash
if [ -e /etc/enc/nodes/$1.yaml ]; then
  cat /etc/enc/nodes/$1.yaml
else
  cat /etc/enc/default.yaml
fi
Enter fullscreen mode Exit fullscreen mode

Die Puppet ENC Klassifizierung macht Sinn, wenn man die Möglichkeit schaffen möchte, Node Klassifizierungen extern von Puppet zu verwalten.

Hier muss man darauf achten, dass der Klassifizierungsprozess durch die Freiheiten, die eine Programmiersprache bietet nicht zu komplex und schlecht wartbar wird.
Weiterhin sollte das Ergebnis schnell vorhanden sein. In größeren Landschaften sollte man ausserdem die Last und Performance prüfen. Wenn andere Systeme befragt werden, sollten diese Hochverfügbar und Hochperformant sein.

Hiera basierte Klassifizierung

Hiera ist das in Puppet eingebaute Daten Backend, in dem üblicherweise Klassen Parameter hinterlegt werden. So kann man darstellen, dass Server im Rechenzentrum A andere DNS Server verwenden sollen, als die Server in Rechenzentrum B.

In der Hiera Konfigurationsdatei kann man verschiedene Ebenen hinterlegen. Üblicherweise empfehlen wir den folgenden Ansatz:

  • Node spezifische Daten
  • Applikations- und Stage-Daten
  • Lokation oder Netwerk-spezifische Daten
  • OS spezifische Daten (nur falls notwendig)
  • Allgemeine oder globale Daten

Weitere Informationen über Hiera findet man auf der Puppet Hiera Webseite.

Innerhalb von Hiera können beliebige Daten hinterlegt werden.
Dies erlaubt es uns, Hiera auch für die Node Klassifizierung zu verwenden, indem wir einen bestimmten Key mit Hilfe der Puppet lookup Funktion abfragen.
Bei der lookup Funktion können Default Werte angegeben werden:

  • Name des Keys
  • Daten Typ des Keys
  • Merge Verhalten

Hiera Array

Die Klassifizierungen sollten über die verschiedenen Hiera Ebenen hinterlegt werden. (application1-dev, net-dmz, os-version, ...).
Normalerweise liefert Hiera den Wert des ersten Vorfinden eines Key zurück. Dieses Verhalten kann mit der Angabe des Merge Verhaltens überschrieben werden:

lookup( {
  'name'          => 'classes',
  'value_type'    => Array,
  'default_value' => [],
  'merge'         => {
    'strategy' => 'unique',
  },
} ).each | $c | {
  # Hinweis: Die Variable `$class` kann nicht verwendet werden,
  # weil es sich hierbei um ein reserviertes Wort handelt!
  include $c
}
Enter fullscreen mode Exit fullscreen mode

Innerhalb der Hiera Ebenen kann man den 'classes' Key hinterlegen:

# common.yaml
---
classes:
  - profile::base

# os/CentOS.yaml
---
classes:
  - profile::base::centos

# stage/dev.yaml
---
classes:
  - profile::accounts::dev

# application/app01.yaml
---
classes:
  - profile::webserver::nginx
  - profile::application::app01
Enter fullscreen mode Exit fullscreen mode

Weiterhin kann das Merge Verhaltens auch innerhalb von Hiera gesetzt werden.

Das Merge Verhalten kann man in der common.yaml angeben:

# common.yaml
---
lookup_options:
  'classes':
    merge:
      strategy: 'unique'
Enter fullscreen mode Exit fullscreen mode

Danach kann man den merge Key aus der lookup Funktion entfernen:

# manifests/site.pp
lookup( {
  'name'          => 'classes',
  'value_type'    => Array,
  'default_value' => []
}).each | $c | {
  include $c
}
Enter fullscreen mode Exit fullscreen mode

Dadurch ist man nun in der Lage für ein System, welches komplett anders ist, ein anderes Merge Verhalten anzugeben.
z.B. sollen auf einem System zwar User angelegt werden, aber ohne die User aus der Common Ebene.

In diesem Fall setzt man Node spezifisch ein anderes Merge Verhalten:

# node/hr_server.domain.tld.yaml
---
lookup_options:
  'classes':
    merge:
      strategy: 'first'

classes:
  - profile::base::hr
  - profile::accounts::hr
Enter fullscreen mode Exit fullscreen mode

Hier muss man wissen, dass man auch alle anderen Klassifizierungen aus anderen Ebenen an dieser Stelle mit angeben muss.

Hiera Hash

Eine weitere Möglichkeit mit mehr Optionen ist die Verwendung von Hashes in Hiera. Damit bekommt man die Möglichkeit, Klassifizierungen individuell neu zu setzen oder sogar herauszunehmen.

Dafür müssen 2 Einstellungen vorgenommen werden:

common.yaml: Wechsel der Merge Strategie von 'unique' auf 'deep'

---
lookup_options:
  'classes':
    merge:
      strategy: 'deep'
Enter fullscreen mode Exit fullscreen mode

site.pp: Wechsel des Daten Typen von Array zu Hash und iterieren über Schlüssel (key) und Wert (c)

lookup( { 'name'          => 'classes',
          'value_type'    => Hash,
          'default_value' => {}
}).each | $key, $c | {
  if $c != '' {
    include $c
  }
}
Enter fullscreen mode Exit fullscreen mode

Ab sofort können in Hiera Hashes verwendet werden:

# common.yaml
---
classes:
  base_class: 'profile::base'

# os yaml
---
classes:
  security_class: 'profile::base::centos'

# stage yaml
---
classes:
  accounts_class: 'profile::accounts::dev'

# app yaml
classes:
  webserver_class: 'profile::webserver::nginx'
  application_class: 'profile::application::app01'
Enter fullscreen mode Exit fullscreen mode

Der key 'classes' ist ein Hash, dessen keys eindeutige Identifikationen sein müssen und der Wert des Keys der einzubindenden Klasse entspricht.
Die Keys (Identifikatoren) werden nur innerhalb von Hiera, nicht aber innerhalb von Puppet verwendet.

Diese Key-Wert Angaben erlaubt es nun, Klassifizierungen auch zu überschreiben:

# node yaml
---
classes:
  webserver_class: 'profile::webserver::tomcat'
Enter fullscreen mode Exit fullscreen mode

Auf diesem Server wird anstelle des Standard Webserver 'profile::webserver::nginx' die Klasse 'profile::webserver::tomcat' verwendet.

Ausserem kann man mit Hashes auch eine Klassifizierung aus einer unteren Ebene wieder herausnehmen:

---
classes:
  application_class: ''
Enter fullscreen mode Exit fullscreen mode

Dies überschreibt den 'application_class' Key und setzt statt dessen eine leere Klasse. Im Puppet Code lassen wir leere Klassen aus. Optional kann man hier auch eine notice Function nutzen, um das auslassen der Klasse im Puppet Server Log zu protokollieren.

Hiera Multiple Hashes

Eine weitere Möglichkeit bietet das Unterteilen der Keys nach bestimmten Kriterien, wie z.B. Common, Betriebssystem und Anwendungen.

Im folgenden ein Beispiel für die Nutzung von Hiera Hash Keys basierend auf dem Kernel Fact:

# common.yaml
---
lookup_options:
  /.*_classes/:
    merge:
      strategy: 'deep'

# manifests/site.pp
$kernel_down = $facts['kernel'].downcase    
lookup( { 'name'          => "${kernel_down}_classes",
          'value_type'    => Hash,
          'default_value' => {}
}).each | $key, $c | {
  if $c != '' {
    include $c
  }
}
Enter fullscreen mode Exit fullscreen mode

Jetzt kann man Keys nach OS-spezifika angeben:

linux_classes:
  hostname: 'profile::linux::hostname'
  repo: 'profile::linux::repo'
  sudo: 'profile::linux::sudo'
  ssh: 'profile::linux::ssh'
  mail: 'postfix'
  webshop: 'profile::application::webshop::nginx'

windows_classes:
  hostname: 'profile::windows::hostname'
  hosts: 'profile::windows::hosts'
  features: 'profile::windows::features'
  time: 'profile::windows::time'
  users: 'profile::windows::ad_auth'
  webserver: 'iis'
Enter fullscreen mode Exit fullscreen mode

Weiterhin kann man mit diesem Prinzip auf Klassen Reihenfolgen eingehen:

# manifests/site.pp
lookup( { 'name'          => "pre_classes",
          'value_type'    => Hash,
          'default_value' => {}
}).each | $key, $c | {
  if $c != '' {
    class { $c:
      tags => 'pre',
    }
  }
}

# manifests/site.pp
$kernel_down = $facts['kernel'].downcase
lookup( { 'name'          => "${kernel_down}_classes",
          'value_type'    => Hash,
          'default_value' => {}
}).each | $key, $c | {
  if $c != '' {
    include $c
    Class<| tags == 'pre' |> -> Class[$c]
  }
}
Enter fullscreen mode Exit fullscreen mode

Zu guter Letzt kann man auf Custom, External oder Trusted Facts für Applikationen eingehen.

Zusammenfassung

Je nach Infrastruktur und den Anforderungen sollte man sich die beste Variante aussuchen.

Eine komplexere Infrastruktur braucht einen eleganteren Ansatz zur Node Klassifizierung.

Durch die Limitierung des Rollen Modells empfehlen wir die Nutzung des Hiera basierten Node Klassifizierungsverfahren.

Bei einer großen Anzahl von Variationen von Anwendungen und Betriebssystemen sollte man den Hiera Hash Ansatz wählen, in einfacheren Umgebungen reicht der Hiera Array Ansatz aus.

Die Option multiple Hashes zu nutzen, kann auch sinnvoll sein, wenn unterschiedliche Teams unterschiedliche Daten liefern müssen.
Mit dem Hash basierten Ansatz kann man das Management von Basis-Einstellungen von Anwendungen separieren.

Happy puppetizing,
Martin Alfke

Top comments (0)