SaltStack: Einführung
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.