SaltStack: Eine Einführung

Abstract

Mit states lassen sich minions in einen definierten Zustand überführen. states beschreiben einen Zustand, den ein Minion erreichen muss. Das verwendete Format YAML ist einfach, erfordert aber eine exakte Formatierung.

States

Die in diesem Blog verwendeten Begrifflichkeiten werden am Ende in einem kurzen Glossar erläutert. SaltStack bietet viele Möglichkeiten. Angenehm ist die Struktur, die einen einfachen Einstieg ermöglicht. Im Verlaufe der Arbeit mit SaltStack ergeben sich jedoch immer wieder neue Anforderungen. Vorweg, fast alles ist möglich, doch der Reihe nach.

States sind ein Basiskonzept von SaltStack. Mit states lassen sich minions in einen definierten Zustand überführen. Sie beschreiben einen Zustand, den ein minion erreichen muss. Mehrere Rechner lassen sich wie folgt in vorab definierte Zustände versetzen:

salt '*.sys4.de' state.highstate

Der Befehl bringt alle Systeme des Netzes sys4.de in einen Zustand, der über Konfigurationsdateien festgelegt wird. Das Standardformat einer Konfigurationsdatei ist YAML, weitere Formate wie z.B. JSON sind aber möglich. Eine YAML Datei hat den folgenden Aufbau:

YAML

# inkludieren weiterer YAML Dateien
<Include Declaration>:
  - <Module Reference>
  - <Module Reference>

# Zustände erweitern
<Extend Declaration>:
  <ID Declaration>:
    [<overrides>]

# Standard Deklaration
<ID Declaration>:
  <State Declaration>:
    - <Function>
    - <Function Arg>
    - <Function Arg>
    - <Function Arg>
    - <Name>: <name>
    - <Requisite Declaration>:
      - <Requisite Reference>
      - <Requisite Reference>

# inline Funktionen und Namen
<ID Declaration>:
  <State Declaration>.<Function>:
    - <Function Arg>
    - <Function Arg>
    - <Names>:
      - <name>
      - <name>
    - <Requisite Declaration>:
      - <Requisite Reference>
      - <Requisite Reference>

# mehrere Zustände, z.B. Mail und Web
<ID Declaration>:
  <State Declaration>:
    - <Function>
    - <Function Arg>
    - <Name>: <name>
    - <Requisite Declaration>:
      - <Requisite Reference>
  <State Declaration>:
    - <Function>
    - <Function Arg>
    - <Names>:
      - <name>
      - <name>
    - <Requisite Declaration>:
      - <Requisite Reference>

Das YAML-Format ist einfach, erfordert aber eine exakte Formatierung. Kommentare werden über ein # eingeführt. Wie auch in Python sind die Einrückungen von Bedeutung und müssen genau eingehalten werden. Fehlende Ausgaben von SaltStack lassen sich meistens auf fehlerhafte YAML-Dateien zurückführen.

Um Zustände zu definieren, dürfen mehrere Dateien verwendet werden. Es ist von Vorteil, sich im Voraus eine sinnvolle Verzeichnisstruktur zu überlegen. Obwohl hier das verwendete YAML-Format ein wenig angepasst wurde, empfiehlt es sich, hier nur mit Leerzeichen zu arbeiten.

Verzeichnisstruktur

In den folgenden Beispielen wird folgende Verzeichnisstruktur verwendet:

/srv/salt                      # Das Basis-Verzeichnis für saltStack States
/srv/salt/src                  # hier liegen die Dateien, die auf den Minion kopiert werden sollen
/srv/salt/tasks                # Aufgaben, die auf einem Minion erledigt werden sollen, wie z.B. das Anlegen eines Systembenutzers, die Installation und Konfiguration einer Software
/srv/salt/hosts                # Hier werden weitere, nach dem FQDN des Hosts benannte Unterverzeichnisse angelegt, in denen Host-spezifische Dateien abgelegt werden
/srv/salt/_grains              # Definition eigener Grains
/srv/salt/_modules             # Definition eigener Module
/srv/salt/_returners           # Definition eigener Rückgaben, z.B. eigener Datenbanken
/srv/salt/_states              # Definition eigener Status

Die Struktur versteht sich als Vorschlag und ist nicht bindend, alle Verzeichnisse können nach eigenen Vorgaben angelegt werden.

Eigene Erweiterungen der Software werden in Verzeichnissen gespeichert, die mit einem Underscore beginnen. Updates überschreiben so keine bestehenden Erweiterungen. Eigene Erweiterungen können aber auch in dem Verzeichnissystem von SaltStack selbst gespeichert werden.

Ein konkreter Zustand

SaltStack stellt einen konkreten Zustand her, wenn beim Aufruf eine einzelne Konfigurationsdatei angegeben wird:

salt '*.sys4.de' state.sls tasks.user.jz

Das Trennzeichen in den Pfaden zu den Konfigurationsdateien ist nicht das bekannte / (Slash), sondern . (ein einfacher Punkt). Der Benutzer jz wird hier mit einer Konfiguration relativ zum Basisverzeichnis angelegt. Die Datei liegt also im Verzeichnis /srv/salt/tasks/user/jz.sls.

Befindet sich in einem Verzeichnis eine Datei mit dem Namen init.sls, so wird diese ausgeführt, um einen Zustand zu erreichen. Der Dateiname muss in diesem Fall nicht extra eingegeben werden. Mehrere States können dann gemeinsam über eine Datei Namens top.sls definiert werden.

Grains

Damit einzelne Systeme gemeinsam adressiert werden können, verwendet SaltStack ein Konzept Namens Grains. Grains sind feste, frei definierbare Eigenschaften von Systemen. So lassen sich – wie im obigen Beispiel – Systeme über die Eigenschaft FQDN adressieren. Welche Grains für ein System existieren, lässt sich einfach herausfinden:

salt 'host1.sys4.de' grains.ls

zeigt alle Grains eines Systems an. Und der folgende Befehl gibt die Grains und ihre Werte aus:

salt 'host1.sys4.de' grains.items

Es besteht die Möglichkeit, eigene Grains zu definieren, um z.B. ein System als Webserver oder Mailserver zu definieren. Anhand dieser Grains lassen sich dann Systeme gezielt ansprechen.

salt -G 'os:CentOS' test.ping

Führt einen Ping auf allen Systemen aus, auf denen als Betriebssystem ein CentOS läuft.

Benutzer anlegen

In einem ersten Beispiel soll ein Benutzer jz angelegt werden. Der Benutzer soll als Shell die zsh bekommen. Für diese Shell gibt es eine Konfiguration, die in das Heimatverzeichnis des neuen Benutzers kopiert werden soll. Außerdem soll der Benutzer die primäre Gruppe users erhalten, Mitglied der Gruppe sys4 sein und ein SHA-512-gehashtes Passwort erhalten. Das Passwort wird vorher mit dem Befehl

echo 'vollgeheim' | mkpasswd -m sha-512 -s

erzeugt. Das Heimatverzeichnis und alle Dateien sollen vernünftige Rechte erhalten und überhaupt soll alles Notwendige erzeugt werden, sofern es nicht existiert.

Die Datei tasks/user/jz.sls, in der all diese Aufgaben definiert sind, sieht wie folgt aus:

zsh:
  pkg:
    - installed
    - name: zsh
jz:
  user.present:
    - fullname: Jörg Zimmermann
    - shell: /usr/bin/zsh
    - home: /home/jz
    - password: $6$KzNjOthAU2.BZvg3$QYizgJAwpWIxNIXXvROAtdW7tawtAO/MBfpPQUaE/Subj6aRPhVUpERUVejQBUfbXPjvL3NnL8oNEHs.IKU0u.
    - uid: 1000
    - groups:
      - users
    - require:
      - group: sys4
/home/jz:
  file.directory:
    - user: jz
    - group: users
    - mode: 0700
    - makedirs: True
/home/jz/.zshrc:
  file.managed:
    - source: salt://src/users/jz/.zshrc
    - mode: 600
    - user: jz
    - group: users
    - require:
      - file: /home/jz
sys4:
  group.present:
    - gid: 1001
    - system: False

Der Benutzer kann jetzt wie folgt auf beliebigen System angelegt werden.

salt '*.sys4.de' state.sls tasks.user.jz

Das vorstehende Beispiel legt zuverlässig den Benutzer jz an. Für eine ausgewachsene Benutzerverwaltung ist die bestehende Lösung allerdings zu starr. Dieses Problem wird in einem weiteren Blogeintrag gelöst; dort werden die Werte in einer weiteren Struktur abgelegt, die die State-Dateien mit dynamischen Werten befüllt.

Glossar

Master
Server, der die Status auf die Clients verteilt
Minion
Client-Rechner, der in den angeforderten Status versetzt wird
Pillar
spezifische Datensätze, z.B. Softwarenamen in unterschiedlichen Linux-Distributionen
Grains
Eigenschaften der Minions. Grains können fest mit dem Minion verbunden sein, z.B. das auf dem Minion laufende Betriebssystem, oder sie können frei hinzugefügt werden. Ein Minion kann z.B. die Eigenschaft Webserver besitzen, die frei hinzugefügt werden kann.
YAML
Ein einfaches Format zur Konfiguration der Minions
JINJA
Ein Templatesystem, das in Konfigurationsdateien auf dem Minion über einfache Anweisungen komplexere Anforderungen umsetzten kann. So lässt sich z.B. je nach Minion eine IP setzen, die nur für einen konkreten Minion gilt. Neben einfachen Variablen sind hier auch Schleifen oder Bedingungen möglich.
Jörg Zimmermann, 06. October 2013