安装和配置
curl -L https://bootstrap.saltstack.com -o bootstrap-salt.sh sudo sh bootstrap-salt.sh -M # 启动 master 节点 sudo service salt-master start # 启动 minion 节点 sudo service salt-minion start # 查看启动日志 sudo tail -f /var/log/salt/minion
salt 会在本地监听两个端口: 4505 和 4506。
master通过 4505 端口向所有 minion 发送消息,minion执行完后再通过4506端口把结果发回给master,通讯采用ZeroMQ,数据使用msgpack压缩。
salt-minion 会在本机生成一个用于跟 master 通讯的 id,保存在 /etc/salt/minion_id 文件中,生成 id 的方法如下:
- 调用 Python 函数 socket.getfqdn()
- 读取文件 /etc/hostname
- 读取 hosts /etc/hosts
以上三个步骤哪个返回不是 localhost,就会被用做 id 值 ,否则就会使用外网的 ip 地址做为 id。最终获取的 ID 将记录在 /etc/salt/minion_id 文件中,该文件可以手动更改,重启服务后不会被重新覆盖。 但如果 minion 的配置文件 /etc/salt/minion 中设置了 id: xxxx,那么这个 id 值将取代 /etc/salt/minion_id 中记录的 ID 数值。
minion 会主动跟 master 通讯,通过配置文件 /etc/salt/minion 中的配置项来指定 master 的地址:
# 后面可填 hostname, ip, domain 等 master: saltmaster.example.com
在 master 节点上,可以通过 salt-key 命令管理所有 minion 节点的连接情况,包括接受、拒绝、删除连接。
# 查看所有 keys salt-key -L # 查看单一 类型的key,后面参数可以是 unaccepted / accepted / rejected / denied salt-key -l unaccepted # 接受一个key的连接 salt-key -a PUBLIC_KEY # 接受所有key salt-key -A # 拒绝一个key的连接 salt-key -r PUBLIC_KEY # 拒绝所有key salt-key -R # 打印出指定key节点的指纹串 salt-key -f FINGER # 获取 master 节点的finger salt-key -f master # 打印所有 finger salt-key -F
默认情况下,minion 跟 master 连接的时候,会使用自己的 id 做为 key,这样在 master 上查看所有 public keys 时,列出的是id,除了基本的id之外,可以使用 finger print 的值来精准校验。
在 salt-key -F 命令输出结果的 local keys 结点,找到 master.pub 值
$ salt-key -F Local Keys: master.pem: 2d:f0:d4:d4:ba:0f:a6:e6:3d:e1:6a:e2:90:53:50:cb:38:73:1d:b0:f6:09:e8:56:14:ea:57:d8:18:22:46:09 master.pub: 5b:9d:77:12:d8:79:f9:23:12:87:9c:63:aa:47:c9:ff:31:36:b3:c5:00:ef:83:c0:fe:be:f7:29:de:15:d9:0a
然后把这个值复制下来,并在 minion 节点中设置配置文件 /etc/salt/minion :
master_finger: '5b:9d:77:12:d8:79:f9:23:12:87:9c:63:aa:47:c9:ff:31:36:b3:c5:00:ef:83:c0:fe:be:f7:29:de:15:d9:0a'
这样 minion 启动后,通过判断设置的 master_finger 值,跟连接上的 master 信息比对,就可以校验出 master 是否正确。
在 master 节点也需要查看连接上来的 minion 节点的 finger 值,通过 finger 比对而不是 key,才能更加准确。
# 在minion中查看自己的finger salt-call key.finger --local # 在 master 上查看某个id的minionr的finger sudo salt-key -f ubuntu # 比对过finger正确无误后,就要可以接受它的连接了 sudo salt-key -a ubuntu
minion与master所有key finger信息,保存在目录 /etc/salt/pki/ 里面,如果是master节点,那么 /etc/salt/pki/master/ 下面的 minions、minions_pre、minions_rejected分别保存着接受、待接受和已拒绝的finger key。
现在已经配置了 minion 与 master 的连接,并且授权了,下面就可以在 master 上面通过发送一些命令,来批量操作 minion 机器了。
常用命令
salt 命令的格式如下:
salt '<target>' <function> [arguments] # 如 salt '*' pkg.install vim # 普通 (按 minion-id 过滤) salt '*.example.org' test.ping # 正则表达 (regular expression) salt -E 'virtmach[0-9]' test.ping # 列表 (list) salt -L 'foo,bar,baz,quo' test.ping # 混合型 (combined) salt -C 'G@os:Ubuntu and webser* or E@database.*' test.ping | Letter | Match Type | Example | | ------ | ------------------ | ---------------------------------- | | G | Grains glob | G@os:Ubuntu | | E | 正则匹配 Minion ID | E@web\d+\.(dev\|qa\|prod)\.loc | | P | Grains 正则匹配 | P@os:(RedHat\|Fedora\|CentOS) | | L | List of minions | L@minion1,minion3 or bl*.domain.com | | I | Pillar glob | I@pdata:foobar | | S | Subnet/IP address | S@192.168.1.0/24 or S@192.168.1.100 | | R | Range cluster | R@%foo.bar |
所有 salt 可用的模块见官方文档:http://docs.saltstack.com/en/latest/ref/modules/all/
管理正在执行的任务:
sudo salt master.example cmd.run 'sleep 100' # 这时打开另一个shell查,看运行中的任务 sudo salt master.example saltutil.running master.example: |_ ---------- arg: - sleep 100 fun: cmd.run jid: 20180507025356063697 pid: 4818 ret: tgt: master.example tgt_type: glob user: sudo_vagrant # 终止掉这个任务 sudo salt master.example saltutil.kill_job 20180507025356063697
salt-call用于在minion上执行命令,如果想要执行的结果不发送到master那里去,则可以添加 –local 参数。
# 这个命令执行结果还是会通过4506端口发送给master sudo salt-call sys.state_doc user.present # 加了--local后,结果不会发给master sudo salt-call --local test.ping
salt-run命令只在master运行
# 查看哪些minion处理上线状态 sudo salt-run manage.up (manage.down / manage.status) # 查看历史运行过的jobs sudo salt-run jobs.list_jobs # 查看指定job执行情况 $ sudo salt-run jobs.lookup_jid 20180507113234037412 # 查看所有runner功能模块文档 $ sudo salt-run doc.runner
自己编写state
在配置文件 /etc/salt/master.d/下面新建配置文件,指定salt系统配置文件根目录:
$ cat /etc/salt/master.d/file-roots.conf file_roots: base: - /srv/salt/file/base
然后在 /srv/salt/file/base 目录下面新建 sls 配置文件:
$ cat user-wilma.sls user_wilma: user.present: - name: wilma - fullname: Wilma Flintstone - uid: 2001 - home: /home/wilma
# 查看并检验一下sls文件 sudo salt master.example state.show_sls user-wilma # 应用state sudo salt minion2.example state.sls user-wilma # 上面命令的效果和这条语句一致 sudo salt minion2.example state.single user.present \ name=wilma fullname='Wilma Flintstone' uid=2001 home=/home/wilma
sls文件可以互相include,目录与文件名用.连接,如有这样的文件结构:
tree /srv/salt/file/base roles webserver - init.sls - packages.sls - start.sls users - barney.sls - fred.sls - wilma.sls - betty.sls - dba.sls - all.sls sites - init.sls src - first.html - top.sls
其中 users/dba.sls 可以include其它:
include: - users.wilma
目录 users 与 文件名wilma通过.连接,也可以使用相对路径,比如 users/all.sls
include: - .wilma - .betty - .barney - .fred
如果目录下面有个 init.sls文件,那么直接引用目录名,就相当于引用了这个init.sls:
include: - sites
在根目录下面,可以有一个 top.sls 文件,叫做 highstate,这个文件中可以定义哪些目标minion,执行哪些state,通过命令 salt \* state.highstate 可以执行这个 top.sls
例如:
$ cat top.sls base: 'os:Ubuntu': - match: grain - default.vim 'os:CentOS': - match: grain - default.vim-enhanced 'minion1.example': - roles.webserver - sites 'minion2.example': - users.dba 'minion3.example': - users.dba - users.qa 'minion4.example': - users.all $ sudo salt minion1\* state.highstate
如果一个sls文件需要依赖其它state先执行,可以用 require 语法实现,如:
roles_webserver_start: service.running: - name: nginx - require: - pkg: nginx
还可以监控其它对象的修改,如果修改了再次再执行时就重新运行,比如监控一个html文件,当文件有修改时,就重新启动nginx:
$ cat /srv/salt/file/base/sites/init.sls sites_first: file.managed: - name: /usr/share/nginx/html/first.html - source: salt://sites/src/first.html - user: www - mode: 0644 service.running: - name: nginx - watch: - file: /usr/share/nginx/html/first.html
sls文件中还可以定义 order 值,以指定该sate执行的顺序,例如可以设置成 1 或 last
$ cat /srv/salt/file/base/run_first.sls run_first: cmd.run: - name: 'echo "I am run first."' - order: 1
还有另外一个非常有用的属性:failhard,如果在state中定义了 failhard=true ,那么这个state一旦失败了,整个highstate或其后的state会终止执行。
如果只是想测试一下state,不想在minion真正的运行,则可以添加 test=true参数
$ sudo salt minion1.example state.show_sls sites $ sudo salt minion1.example state.sls sites test=true $ sudo salt minion1.example state.show_highstate $ sudo salt minion\* state.highstate test=true
Grains和Pillar
grains就是所有关于minion属性的数据,一般是minion在启动的时候主动上报给master的。
# 查看主机有哪些grains属性 $ sudo salt master.example grains.ls # 列出minion的os和ip属性 $ sudo salt \* grains.item os ipv4 --out=text # 给minion设置一个myevn=prod的grains $ sudo salt -E 'minion(1|2).*' grains.setval myenv prod # 设置的属性值也可以是数组 $ sudo salt 'minion1.*' grains.setval roles '[webserver, appserver]'
在minion机器上,通过 grains.setval 命令设置的grains会保存在配置文件 /etc/salt/grains里
[vagrant@minion1 ~]$ sudo cat /etc/salt/grains myenv: prod roles: - webserver - appserver
grains是保存在minion上面的一些静态属性数据,而pillar则相反,是保存在master上面的动态可以修改的数据,并且可以控制哪些minion可以看到哪些数据。默认情况下,master配置文件中的所有数据都添加到Pillar中,且对所有minion可用。master上配置文件中定义pillar_roots,用来指定pillar的数据存储在哪个目录
pillar_roots: base: - /srv/salt/pillar/base
和state一样,pillar目录下面也有一个top.sls,作为入口,里面包含其它sls文件:
$ cat /srv/salt/pillar/base/top.sls base: '*': - default
引用的default.sls示例:
$ cat /srv/salt/pillar/base/default.sls ############IDC################ {% if grains['ip_interfaces'].get('eth0')[0].startswith('10.10') %} nameservers: ['10.10.9.31','10.10.9.135'] zabbixserver: ['10.10.9.234'] {% else %} nameservers: ['10.20.9.75'] zabbixserver: ['10.20.9.234'] {% endif %} ######## nginx ######## ngx_home_dir: /var/cache/nginx
在State文件中将可以引用Pillar数据,比如引用ngx_home_dir:
nginx: pkg: - installed user.present: - home: {{ pillar['ngx_home_dir'] }}
自定义模块
自己可编写py文件,放到 _modules 目录下面,就可以扩展salt能够运行的模块了,例如:
$ cat _modules/hello.py #coding=utf-8 import logging logger = logging.getLogger(__name__) def id(): ''' 这是docstring,可以用命令查看: sudo salt-call sys.doc hello.id ''' id = __grains__['id'] logger.debug('Found grain id: {0}'.format(id)) return 'Hello, {0}.'.format(id)
自定义的可执行模块在master上编写好后,需要同步到minion上去,才能在minion上执行,同步完成后,需要有点延迟才能被salt-minion加载进去。
$ sudo salt \* saltutil.sync_modules $ sudo salt \* hello.id
自定义states相应的py文件放到 _states目录下面,其中实现的函数需要返回一个包含 name, changes, result, comment这个四键值的字典,并且实现的时候需要考虑到 test=true时的情况,并做相应处理
$ cat _states/custom.py import os import logging def enforce_tmp(name, contents=None): return_dict = { 'name': name, 'changes': {}, 'result': False, 'comment': '' } # do your own stuff ... # Check if this is a test run, if so do not change anything. if __opts__['test'] == True: logging.info('this is a test, skip changes...') return_dict['result'] = True return return_dict
接着在sls文件中用到我们上面编写的自定义state函数
$ cat custom.sls custom_state: custom.enforce_tmp: - name: foo - contents: bar
# 先同步 $ sudo salt \* saltutil.sync_states $ sudo salt minion1.example state.sls custom test=true
与自定义module和states不同,自定义grains不需要显式地用salt命令来运行,而是一旦同步到minion,相应自定义的函数会自动执行,并上报grains值给master。
在自定义grains文件中,定义的函数不是以 _ 开头的都被认为是需要执行的,函数返回的字典值会被合并到全局grains变量中
$ cat _grains/my_grains.py """ Custom grains for the example hosts. """ import platform import logging logger = logging.getLogger(__name__) def _get_hostname(): hostname = platform.node() logger.debug('Using hostname: {0}'.format(hostname)) return hostname def set_myenv(): """ Set the 'myenv' grain based on the host name. """ grains = {} hostname = _get_hostname() if hostname.startswith('minion1'): grains['myenv'] = 'prod' elif hostname.startswith('minion2'): grains['myenv'] = 'prod' elif hostname.startswith('minion3'): grains['myenv'] = 'stage' elif hostname.startswith('minion4'): grains['myenv'] = 'dev' return grains def set_roles(): """ Set the 'roles" grain based on the host name. """ grains = {} hostname = _get_hostname() if hostname.startswith('minion1'): grains['roles'] = ['webserver', 'appserver'] elif hostname.startswith('minion2'): grains['roles'] = ['database'] elif hostname.startswith('minion3'): grains['roles'] = ['webserver', 'appserver', 'database'] elif hostname.startswith('minion4'): grains['roles'] = ['webserver', 'appserver', 'database'] return grains
同步后,好面自定义的grains方法 set_myenv和set_roles 会自动执行,函数的返回值会保存到 minion 的配置文件 /etc/salt/grains 中,并上报给master。
# 先删除掉老的grains $ sudo salt \* cmd.run 'rm /etc/salt/grains' $ sudo salt \* service.restart salt-minion $ sudo salt \* saltutil.sync_grains