Salt Stack ist ein Tool zur Verwaltung von Rechnern in einem Netzwerk. Es ist damit sehr leicht möglich eine große Anzahl von Maschinen einheitlich (oder auch individuell) zentral zu Konfigurieren.

Der Artikel geht von einem Hostingsetup mit mehreren Xen-Maschinen im Clusterbetrieb aus.

Hier geht’s also direkt ans Eingemachte:

Einzelne Befehle senden

Mit Salt können Befehle an die angebundenen Clients (Minions) gesendet werden, z.B.:

salt ‘*’ cmd.run ‘apt-get install salt-doc’

Der Parameter ‘*’ gibt an dass alle Minions den Befehl erhalten sollen. Man könnte hier z.B. auch *.hosting?.example.com angeben, somit würden nur alle unsere Hosting-DomUs den Befehl erhalten. cmd.run führt den dahinter angegebenen Befehl aus.

Salt liefert dann für jeden Minion den Output des Befehls zurück, z.B.:

minion.example.com:
Reading package lists…
Building dependency tree…
Reading state information…
The following extra packages will be installed:
libjs-sphinxdoc libjs-underscore
The following NEW packages will be installed:
libjs-sphinxdoc libjs-underscore salt-doc
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
Need to get 3460 kB of archives.
After this operation, 12.9 MB of additional disk space will be used.
Get:1 http://10.0.1.7/ftp.de.debian.org/debian/ wheezy/main libjs-underscore all 1.1.6-1+deb7u1 [30.8 kB]
Get:2 http://10.0.1.7/ftp.de.debian.org/debian/ wheezy/main libjs-sphinxdoc all 1.1.3+dfsg-4 [43.8 kB]
Get:3 http://10.0.1.7/debian.saltstack.com/debian/ wheezy-saltstack/main salt-doc all 0.16.4-1~bpo70+1~dst.1 [3385 kB]
[master 176c275] saving uncommitted changes in /etc prior to apt run
1 file changed, 5 insertions(+), 4 deletions(-)
Fetched 3460 kB in 0s (35.0 MB/s)
Selecting previously unselected package libjs-underscore.
(Reading database … 45917 files and directories currently installed.)
Unpacking libjs-underscore (from …/libjs-underscore_1.1.6-1+deb7u1_all.deb) …
Selecting previously unselected package libjs-sphinxdoc.
Unpacking libjs-sphinxdoc (from …/libjs-sphinxdoc_1.1.3+dfsg-4_all.deb) …
Selecting previously unselected package salt-doc.
Unpacking salt-doc (from …/salt-doc_0.16.4-1~bpo70+1~dst.1_all.deb) …
Setting up libjs-underscore (1.1.6-1+deb7u1) …
Setting up libjs-sphinxdoc (1.1.3+dfsg-4) …
Setting up salt-doc (0.16.4-1~bpo70+1~dst.1) …

Einfache States setzen und (einzeln) anwenden

Salt kann aber noch mehr, in einem sog. Salt State Tree (SST) können Konfigurationspakete (sog. States) definiert werden. Der SST befindet sich standardmäßig in /srv/salt/.

Über die Datei top.sls im SST wird festgelegt welche Maschinen welche Pakete (States) erhalten sollen, z.B.:

/srv/salt/top.sls:

base:
‘*’:
  – hosts
  – sources_list
  – standard_packages

‘proxy*’:
  – nginx_proxy

‘*.hosting?.example.com’:
  – phpini
  – apache

Alle Maschinen erhalten somit die States:

  • hosts (Setzen von /etc/hosts)
  • sources_list (Setzen von /etc/apt/sources.list)
  • standard_packages (Installation von APT-Paketen)

Die Maschinen proxy1* erhalten den State nginx_proxy.

Alle unsere Hosting-DomUs erhalten die States:

  • phpini (Setzen der PHP.ini)
  • apache (Setzen der Apache-Konfiguration)

Die States selbst sind entweder in Unterverzeichnissen (welche dem Namen des States in der top.sls entsprechen) oder in einer SLS-Datei auf gleicher Ebene wie top.sls gespeichert (auch hier entspricht der Dateiname dem Wert in der top.sls, es wird lediglich die Endung .sls angefügt).

Ein Unterverzeichnis für die States bietet sich immer dann an, wenn neben der Statedefinition noch weitere Dateien angelegt werden, z.B. um diese durch den State auf die Minions zu verteilen.

Ein Beispiel (einzelnes Statefile):

/srv/salt/sources_list.sls:

base:
  pkgrepo.managed:
    – name: deb http://10.0.1.7:3142/debian.saltstack.com/debian wheezy-saltstack main
    – key_url: https://10.0.1.7/debian-salt-team-joehealy.gpg.key

Dieser State fügt einem Minion das angegebene Debian-Repository hinzu und installiert den zugehörigen GPG-Key, der auf einem beliebigen HTTP-Server liegen kann.

Um den State anzuwenden kann der Befehl salt ‘*’ state.sls sources_list verwendet werden.

Wenn States mit state.sls angewendet werden wird die Zuordnung der States in der top.sls ignoriert. Alle angegebenen Minions erhalten dann die entsprechende Konfiguration.

—-

Nun ein Beispiel mit einem Unterverzeichnis:

/srv/salt/hosts/init.sls:

/etc/hosts:
  file:
    – managed
    – backup: minion
    – source: salt://hosts/hosts
    – user: root
    – group: root
    – mode: 644

States in einem Unterverzeichnis werden immer über eine Datei mit Namen init.sls gesteuert. In obigem Beispiel wird die Datei /srv/salt/hosts/hosts vom Salt-Master auf den Minion kopiert (Ziel auf dem Minion ist /etc/hosts). Außerdem wird zuvor ein Backup der Datei erzeugt und die Datei mit den angegebenen Rechten versehen.

Die Datei /srv/salt/hosts ist dann eine gewöhnliche Hosts-Datei.

Um den States anzuwenden kann der Befehl salt ‘*’ state.sls hosts verwendet werden.

Wenn States mit state.sls angewendet werden wird die Zuordnung der States in der top.sls ignoriert. Alle angegebenen Minions erhalten dann die entsprechende Konfiguration.

Komplexe States setzen

Natürlich sind nicht zwingend alle Konfigurationsdateien auf den Minions völlig identisch. Daher muss mit Platzhaltern oder Ausnahmen gearbeitet werden.

Pillars

Eine Möglichkeit solche Platzhalter zu definieren stellen die sog. pillars dar. Diese befinden sich in /srv/pillars/. Die Struktur dort ähnelt der von /srv/salt/. Auch hier können .sls-Files oder Unterverzeichnisse mit einer init.sls verwendet werden. Auch eine top.sls findet sich hier:

/srv/pillars/top.sls:

base:
  ‘*.hosting?.example.com’:
    – hosting_domU
    – users
    – groups

  ‘foobar.hosting?.example.com’:
    – foobar

Maschinen deren Minion-ID (in der Regel der FQDN) dem Ausdruck *.hosting?.example.com entsprechen erhalten die folgenden Pillars:

  • hosting_domU
  • users
  • groups

Die Maschinen foobar.hosting?.example.com erhalten zusätzlich den Pillar foobar.

Hier ein Pillar-Beispiel:

/srv/pillar/foobar.sls:

apache_conf: salt://apache/default_foobar

info:
type: domU
package: 2

Die hier gesetzen Werte können z.B. mit salt ‘mit*’ pillar.item apache_conf abgefragt werden:

foobar.backup1.example.com:
———-
foobar.hosting2.example.com:
———-
apache_conf:
salt://apache/default_foobar
foobar.hosting1.example.com:
———-
apache_conf:
salt://apache/default_foobar

Natürlich können sie auch in States genutzt werden:

/srv/salt/apache/init.sls:

apache2:
  pkg.installed

/etc/apache2/sites-available/default:
  file:
    – managed
    – backup: minion
    – user: root
    – group: root
    – mode: 644

{% if salt[‘pillar.get’](‘apache_conf’) %}
    – source: {{ salt[‘pillar.get’](‘apache_conf’) }}
{% else %}
    – source: salt://apache/default
{% endif %}

apache_signal:
  cmd.wait:
    – name: /etc/init.d/apache2 reload
    – watch:
    – file: /etc/apache2/sites-available/default
    – pkg: apache2

Dieser State installiert zunächst das Paket apache2 (sofern nötig) und schreibt dann die Datei /etc/apache2/sites-available/default/ auf dem Minion. Standardmäßig wird hierbei die Datei /srv/salt/apache/default verwendet. Gibt es jedoch für einen Minion ein Pillar mit dem Wert apache_conf wird der hier gesetzte Wert verwendet. Im Falle von foobar also /srv/salt/apache/default_foobar.

Abschließend führt der State den Befehl /etc/init.d/apache2 reload aus, allerdings nur wenn das Paket Apache2 oder die Apache-Konfigurationsdatei installiert/aktualisiert wurde (siehe - watch-Anweisung).

Der State kann mit salt ‘foobar.hosting?.example.com’ state.sls apache gesetzt werden. Eine Übersicht über alle Pillars die ein Minion gesetzt hat erhält man mit salt ‘*’ pillar.items.

Pillars können aber noch umfangreicher genutzt werden:

/srv/salt/users/init.sls:

{% for user, args in pillar[‘users’].iteritems() %}
{{ user }}:
  group.present:
    – gid: {{ args[‘gid’] }}
  user.present:
    – home: {{ args[‘home’] }}
    – shell: {{ args[‘shell’] }}
    – uid: {{ args[‘uid’] }}
    – gid: {{ args[‘gid’] }}
{% if ‘createhome’ in args %}
    – createhome: {{ args[‘createhome’] }}
{% endif %}
{% if ‘password’ in args %}
    – password: {{ args[‘password’] }}
{% if ‘enforce_password’ in args %}
    – enforce_password: {{ args[‘enforce_password’] }}
{% endif %}
{% endif %}
    – fullname: {{ args[‘fullname’] }}
{% if ‘groups’ in args %}
    – groups: {{ args[‘groups’] }}
{% endif %}
    – require:
    – group: {{ user }}

{% if ‘ssh-key’ in args %}
{{ args[‘home’] }}/.ssh/authorized_keys:
  file:
    – managed
    – makedirs: True
    – user: {{ user }}
    – group: {{ user }}
    – mode: 600
    – source: salt://users/authorized_keys
    – require:
      – group: {{ user }}
      – user: {{ user }}

ssh_auth:
  – present
  – user: {{ user }}
  – source: {{ args[‘ssh-key’] }}
  – require:
  – file: {{ args[‘home’] }}/.ssh/authorized_keys
  – group: {{ user }}
  – user: {{ user }}
{% endif %}
{% endfor %}

Hier werden Pillars in einer for-Schleife ausgelesen und die entsprechenden Angaben genutzt um Benutzer anzulegen, außerdem werden ggf. public SSH-Keys hinterlegt. Hierzu ist folgende Datei notwendig:

/srv/pillar/users.sls:

users:
  foo:
    fullname: foo bar
    uid: 4000
    gid: 5000
    shell: /bin/bash
    home: /home/foo
    createhome: True
    groups:
      – is
      – sudo
    password: $6$dhszapgu$tiwutiwerutiewrutzieruzieruizuerizueiruzeiorzierzu.
    enforce_password: False
    ssh-key: salt://users/foo.id_rsa.pub

  bar:
    fullname: bar foo
    uid: 4001
    gid: 5001
    shell: /bin/bash
    home: /home/bar
    createhome: True
    groups:
      – is
      – sudo
    password: $6$tzskgusv$ritoerioerizoeirzopeirzoeirzopeirpzieproizeroizoezi.
    enforce_password: False
    ssh-key: salt://users/bar.id_rsa.pub

Die nötigen Kennwörter können über den Befehl python -c ‘import crypt; print crypt.crypt(“password”, “$6$random_salt”)’ erzeugt werden. enforce-password: False gibt an dass das Kennwort nicht wieder auf diesen Wert zurückgesetzt wird, wenn der entsprechende Benutzer es bereits geändert hat.

Grains

Eine weitere Möglichkeit für abweichende Konfigurationen sind die sog. Grains. Diese werden ähnlich verwendet die Pillars, werden jedoch vom Minion nicht vom Salt-Master gesetzt.

Alle gesetzen Grains können mit dem Befehl salt ‘*’ grains.items abgerufen werden. Hierbei ist jedoch zu beachten dass Grains immer nur beim Start den salt-minion-Dienstes neu gesetzt werden.

Ein sehr praktisches Grain ist z.B. ip_interfaces welches z.B. über salt ‘proxy*’ grains.item ip_interfaces abgefragt werden kann und alle Netzwerkinterfaces nebst IPs des Minions zurückliefert:

proxy2.example.com:
ip_interfaces: {‘lo’: [‘127.0.0.1’], ‘eth2’: [‘10.0.1.3’], ‘eth0’: [‘89.238.87.3’]}
proxy1.example.com:
ip_interfaces: {‘lo’: [‘127.0.0.1’], ‘eth2’: [‘10.0.1.2’, ‘10.0.0.2’], ‘eth0’: [‘1.2.3.4’, ‘4.6.7.8’, ‘2.3.4.5’]}

Hier nun ein Beispiel eines States der auf Grains zurückgreift:

/srv/salt/cluster/init.sls:

corosync:
  pkg.installed

pacemaker:
  pkg.installed

/etc/default/corosync:
  file:
    – managed
    – source: salt://cluster/default
    – user: root
    – group: root
    – mode: 644
    – require:
      – pkg: corosync

/etc/corosync/corosync.conf:
  file:
    – managed
    – source: salt://cluster/corosync.conf
    – user: root
    – group: root
    – mode: 644
    – template: jinja
    – context:
    bind: {{ salt[‘grains.get’](‘ip_interfaces:eth0’) }}
    – require:
      – pkg: corosync

corosync_signal:
  service:
    – name: corosync
    – running
    – enable: True
    – reload: True
    – watch:
      – pkg: corosync
      – file: /etc/default/corosync
      – file: /etc/corosync/corosync.conf

Die grundsätzlichen Befehle im State wurden ja bereits erklärt. Die Besonderheit findet sich in den Zeilen - template: jinja (eine der Template-Engines von Salt) und der - context-Anweisung. Hierbei wird das Grain ip_interfaces ausgelesen, genauer die IP-Adressen von eth0. Diese können nun in der Datei /srv/salt/cluster/corosync.conf über den Wert bind referenziert werden:

/srv/salt/cluster/corosync.conf:

interface {
  # The following values need to be set based on your environment
  ringnumber: 0
  bindnetaddr: {{ bind[0] }}
  mcastaddr: 226.94.74.20
  mcastport: 5405
}

Mit {{ bind[0] }} wird die erste IP-Adresse die eth0 auf dem jeweiligen Minion zugewiesen wurde als bindnetaddr gesetzt.

States anwenden (Batchmode)

Wenn man alle States die für die jeweiligen Minions gelten (siehe top.sls) auf einmal anwenden möchte geht dies mit dem Befehl salt ‘*’ state.highstate. Damit es hier keine Überraschungen gibt sollte zuvor ein Testlauf (Dryrun) durchgeführt werden: salt ‘*’ state.highstate -v test=True

Wenn man den Output von salt automatisiert prüfen möchte kann man auch das Outputformat ändern, z.B. ist hier auch eine Ausgabe im JSON-Format vorgesehen: salt ‘*’ state.highstate -v test=True –out=json

Um die Hosting-Maschinen nicht zu stark zu belasten kann Salt im Batch-Mode ausgeführt werden, hierbei kann bestimmt werden, wie viele Minions gleichzeitig bearbeitet werden.

salt ‘*’ state.highstate -b 10

Hierbei werden jeweils 10 Maschinen gleichzeitig in den Highstate versetzt, erst wenn diese ihren Status zurückgemeldet haben werden die nächsten 10 Maschinen verarbeitet.

Targeting

Ein paar einfache Methoden zum Targeting haben wir bereits kennengelernt. Zum einen die Zuweisung über die top.sls zum anderen der ‘*’ Parameter von Salt selbst. Es gibt aber noch zahlreiche weitere Möglichkeiten: http://docs.saltstack.com/topics/targeting/index.html

Man kann Grains ganz einfach auf den Minions setzen, indem man sie in /etc/salt/grains ablegt:

/etc/salt/grains:

cluster:
  – passive

Nach einem Neustart des salt-minion kann man auf die neuen Grains zugreifen:

salt ‘*.hosting?.example.com’ grains.item cluster –out=json

Der Output (als JSON):

{
  “foobar.hosting2.example.com”: {
    “cluster”: [
      “passive”
    ]
  }
}
{
  “foobar.hosting1.example.com”: {
    “cluster”: [
      “active”
    ]
  }
}

Grains können natürlich auch in einem State genutzt werden:

{% if ‘active’ in grains[‘cluster’] %}
dosomething
{% endif %}

Eine weitere Möglichkeit sind die sog. Nodegroups. Diese können Serverseitig definiert werden:

/etc/salt/master:

nodegroups:
active: ‘E@^.*\.hosting(1|2)\.example\.com and S@10.0.0.0/24’
passive: ‘E@^.*\.hosting(1|2)\.example\.com and not S@10.0.0.0/24’

Hierbei wird für alle Minions welche dem Namen *.hosting1.example.com bzw. *.hosting2.example.com entsprechend und eine IP im Bereich 10.0.0.0/24 aufweisen die Nodegroup active gesetzt (10.0.0.0/24 ist der Bereich für die Service-IPs im Cluster). Minions die diese IP nicht aufweisen sind demnach passiv.

Die Mitglieder der Gruppen können wie folgt angesprochen werden:

salt -N active test.ping

Der Output:

foobar.hosting1.example.com:
True

Die Passive-Gruppe:

salt -N passive test.ping

Der Output:

foobar.hosting2.example.com:
True

Die Nodegroups können z.B. in der top.sls genutzt werden:

/srv/salt/top.sls:

base:
  passive:
    – match: nodegroup
    – apt_upgrade

Hier würde der State apt_upgrade nur auf passiven Nodes gesetzt werden.

Next Post Previous Post