Deploy 3 VM and write down their IP:
- master01 (prompt vert) : 10.1.1.____
- minion01 1 CPU 1 GB RAM (prompt jaune): 10.1.1.____
- minion02 2 CPU 1.5 GB RAM (prompt bleu): 10.1.1.____
Green
export PS1="\[$(tput bold)\]\[$(tput setaf 2)\][\u@\h \W]\\$ \[$(tput sgr0)\]"
Yellow
export PS1="\[$(tput bold)\]\[$(tput setaf 3)\][\u@\h \W]\\$ \[$(tput sgr0)\]"
Blue
export PS1="\[$(tput bold)\]\[$(tput setaf 6)\][\u@\h \W]\\$ \[$(tput sgr0)\]"
Colorize: https://www.kirsle.net/wizards/ps1.html
Add node names in /etc/hosts on each node
For demo disable firewall on master: systemctl stop firewalld
- master and minions
yum install https://repo.saltstack.com/yum/redhat/salt-repo-latest-1.el7.noarch.rpm
- install salt-master RHEL + config
yum -y install salt-master
/etc/salt/master
file_roots:
base:
- /srv/salt/states
pillar_roots:
base:
- /srv/salt/pillars
- install salt-minion RHEL + config
yum -y install salt-minion
/etc/salt/minion
master: master01
- accept salt-minion keys :
salt-key -a minion*
salt '*' test.ping
salt '*' grains.items
- targetting based on grain:
salt -G 'mem_total:988' test.ping
(only one should return) salt -G 'num_cpus:1' test.ping
-
remote execution
- cmd.run
- iptables.version
- selinux.getenforce (will show something is missing, install pkg in next step)
- salt 'minion01' pkg.install policycoreutils-python
- selinux.getenforce again
- Create states and pillars directories
mkdir -p /srv/salt/{states,pillars}
- manage /etc/motd
/srv/salt/states/motd/init.sls
motd:
file.managed:
- name: /etc/motd
- source: salt://motd/motd.jinja
- template: jinja
/srv/salt/states/motd/motd.jinja
Hello world
/srv/salt/states/top.sls
base:
'*':
- motd
Verif : salt '*' state.show_top
minion01:
----------
base:
- motd
salt '*' state.highstate -v test=True
salt '*' state.highstate
- reminder:
grains.items
andgrains.item grain_name
- jinja template: use
os
ormem_total
grains - declare custom grain with multiple values
salt 'minion01' grains.setval ROLE ['frontend','haproxy']
salt 'minion02' grains.setval ROLE ['backend','apache']
- use custom grain in MOTD and display raw result first then prettify it with jinja logic
Roles :
{% for i in grains['ROLE'] -%}
- {{ i }}
{% endfor -%}
- manage service.running postfix
Manage conf (retrieve /etc/postfix/main.cf and add comment at the top)
Manage service without watch statement (will tell service is already running)
Run tail -f /var/log/maillog
Add a watch statement and make a change to conf and state.highstate
postfix-conf:
file.managed:
- name: /etc/postfix/main.cf
- source: salt://postfix/main.cf.jinja
- template: jinja
postfix-service:
service.running:
- name: postfix
- enable: True
- reload: False
- watch:
- file: postfix-conf
- show content of /var/cache/salt/minion/files/base on minion (cache code)
After showing how code is stored on minion and presents security issues, show use of pillars
Put a password in clear text in conf main.cf.jinja and show file in cache again
Now create a pillar
/srv/salt/pillars/postfix/init.sls
postfix:
password: Redlfmskdmlfs
/srv/salt/pillars/top.sls
base:
'minion01':
- postfix
salt '*' pillar.data
Replace hardcoded "password=Truc" by "password={{ pillar['postfix']['password'] }}"
Show both syntaxes
password={{ pillar['postfix']['password'] }}
password={{ salt['pillar.get']('postfix:password') }}
Simple syntax gives error if pillar doesn't exist, other syntax is better
Show on a pillar that doesn't exist:
password={{ salt['pillar.get']('postfix:password2', 'valeur par defaut') }}
Show events on master bus: salt-run state.event pretty=True
Run: salt 'minion01' test.ping -v
salt/job/20170411144701244715/new {
"_stamp": "2017-04-11T18:47:01.245539",
"arg": [],
"fun": "test.ping",
"jid": "20170411144701244715",
"minions": [
"minion01"
],
"tgt": "minion0",
"tgt_type": "glob",
"user": "root"
}
salt/job/20170411144701244715/ret/minion01 {
"_stamp": "2017-04-11T18:47:01.351686",
"cmd": "_return",
"fun": "test.ping",
"fun_args": [],
"id": "minion0",
"jid": "20170411144701244715",
"retcode": 0,
"return": true,
"success": true
}
Send a custom event on the bus from minion: salt-call event.send /my/custom/event '{"data": "JDL"}'
/my/custom/event {
"_stamp": "2017-04-13T10:03:35.854050",
"cmd": "_minion_event",
"data": {
"__pub_fun": "event.send",
"__pub_jid": "20170413060330447842",
"__pub_pid": 3405,
"__pub_tgt": "salt-call",
"data": "JDL"
},
"id": "minion01",
"tag": "/my/custom/even"
}
Master conf /etc/salt/master:
reactor:
- 'salt/job/*/ret/minion01':
- /srv/salt/reactors/touch-minion02.sls
--> Reactor on minion02 when minion0 returns something
/srv/salt/reactors/touch.sls
command_run:
cmd.cmd.run:
- tgt: minion02
- arg:
- "touch /tmp/touch.txt"
salt 'minion01' test.ping
See on minion02 /tmp/touch.txt
yum install python-inotify
on minions (dep)
/etc/salt/minion.d/beacons.conf
beacons:
inotify:
/etc/passwd: {}
interval: 5
Run adduser jdl
on salt-minion and see event bus
salt/beacon/minion0/inotify//etc/passwd {
"_stamp": "2017-04-11T19:08:18.505247",
"change": "IN_IGNORED",
"id": "minion01",
"path": "/etc/passwd"
}
- custom grain from API (httpd)
/var/www/html/index.html
{"custom-grain":"custom-value"}
/srv/salt/states/_grains/custom.py
#!/usr/bin/env python
import requests
def custom_grain():
grains = {}
r = requests.get("http://jdl-master")
grains['zzz_custom'] = r.json()
return grains
salt '*' saltutil.sync_all
Show minion logs
[INFO ] Starting new HTTP connection (1): 10.1.1.17
[DEBUG ] "GET / HTTP/1.1" 200 34
salt '*' grains.item zzz_custom
Use custom-grain in motd.jinja
{{ grains['zzz_custom']['custom-grain'] }}
or better
{{ salt['grains.get']('zzz_custom:custom-grain') }}
/srv/salt/states/_modules/jdl.py
#!/usr/bin/env python
import requests
def public_ip():
r = requests.get('https://ip.wains.be')
out = r.content
return out
salt '*' saltutil.rsync_all
[root@master01 _modules]# salt '*' jdl.public_ip
minion01:
8.8.8.8
minion02:
8.8.4.4
Use custom module in motd.jinja {{ salt['jdl.public_ip']() }}
yum install -y salt-api
Create local account testapi
+ pwd
Set up API
external_auth:
pam:
testapi:
- .*
- '@wheel'
- '@runner'
- '@jobs'
rest_cherrypy:
port: 8080
host: 0.0.0.0
disable_ssl: True
webhook_url: /hook
webhook_disable_auth: True
systemctl restart salt-master
(for auth) and salt-api -l debug
URL: http://master01:8080/run
headers:
- Content-Type: application/x-www-form-urlencoded
body: client=local&tgt=*&fun=test.ping&username=testapi&password=xxx&eauth=pam
- test.ping (runner)
- state.highstate (runner)
(no API event on the bus for runners)
URL: http://master01:8080/hook/test/jdl
No headers, no auth
Create reactor:
/etc/salt/master
reactor:
- 'salt/netapi/hook/test/jdl':
- /srv/salt/reactors/api-hook-jdl.sls
/srv/salt/reactors/api-hook-jdl.sls
command_run:
cmd.cmd.run:
- tgt: minion02
- arg:
- "touch /tmp/api-works.txt"