Salt (3) 核心所在:State
.sls
means S a L t State file.
它表示系统将会是什么状态。SLS 只是一些数据。
State 的完整参考手册: http://salt.readthedocs.org/en/latest/ref/states/index.html 一套完整的 state trees 可以称为
formular
,一些样例: https://github.com/saltstack-formulas
Salt States 文件的存放位置
默认在 /srv/salt
可通过 /etc/salt/master
中配置:
# /etc/salt/master
file_roots:
base:
- /srv/salt
- /mnt/salt-nfs/base
多环境配置
# /etc/salt/master
file_roots:
base:
- /srv/salt/prod
qa:
- /srv/salt/qa
- /srv/salt/prod
dev:
- /srv/salt/dev
- /srv/salt/qa
- /srv/salt/prod
使用 top.sls
http://docs.saltstack.com/en/latest/ref/states/top.html
top.sls
用来管理哪些 sls 在 state.highstate
时允许被应用到哪些 hosts。
默认的环境 (env) 叫 base
。下面的例子表示所有 host 都能使用 webserver.sls。
# /srv/salt/top.sls
base:
'*':
- webserver
dev:
'dev-*':
- vim
'db*dev*':
- db
qa:
'10.10.200.0/24':
- match: ipcidr
- deployments.qa.site1
如果想上面那样,环境不止一个,那 states 如何工作呢?
salt 寻找 states 文件是根据 /etc/salt/master
中的 file_roots
。
file_roots
根据 env 提供出各种 env 的文件路径(这就包括了告诉 salt top.sls
在哪儿),而 env 本身却是通过 top.sls
定义的。那么,这就陷入了“鸡生蛋,蛋生鸡”的问题了。env 等着靠 top.sls 去划分,而 top.sls 自己却躲在在某个/各个 env 的目录下。要是每个 env 里都放了个 top.sls,那 salt 怎么知道该听哪个目录里的 top.sls?
salt 为此定义了一套 top.sls 的寻找规则:
- 如果有名为
base
的 env,且它的文件目录里有top.sls
。(比如/srv/salt/base/top.sls
)这时其他任何的 top.sls 都将被无视,只取 base 里的这个 top.sls。 - 如果没有在
base
里配置top.sls
,那么,在剩下的几个 env 中,按 file_roots 中 env 名字的字母顺序,依次检查它们目录下的 top.sls,找第一个含有base
的那个 top.sls,一旦找到,其它 env 的 top.sls 都将被无视。 - 如果压根没有
base
这个 env。就找对应 env 下的 top.sls,各个 env 自己负责自己的。如果这样没找到,那就又去按字母顺序去别的 env 里找自己 env 的配置,找到为止。
创建一个 SLS
文件
SLS
文件的命名规则
- 使用时,忽略
.sls
。在 Salt 中使用xxx.sls
时,直接写xxx
就可以了。 - 用
.
连接多层目录。aaa/bbb/haha.sls
写成aaa.bbb.haha
- 如果目录下有
init.sls
,使用时只需写目录名称就可以了。aaa/init.sls
写成aaa
- 如果同时存在
aaa.sls
和aaa/init.sls
,aaa
将只指向aaa.sls
,忽略aaa/init.sls
- 建议不要跟 salt builtin state modules 有重名(比如,git)。否则在其它 sls 里 include 时会有报
The following requisites were not found
SLS
文件的内容
target_a: # ID declaration
state_a: # State declaration
- state_func_a: some_value # function declaration
- state_func_b: some_value # ...
- state_func_c: some_value
- require: # requisite declaration
- pkg: xxx # requisite reference
- file: xxx/xxx.xx
state_b: # Support multiple states
- state_parameter_a: some_value
- state_parameter_b: some_value
target_b:
state_x:
- state_parameter_a: some_value
- state_parameter_b: some_value
这里的 target_a
可以是一个文件路径,可以是一个 service 的名字,……,可以是一切你希望控制的目标。(Salt 官方称它为 ID Declaration
,但显然这个名称太误导人了。这可不是随便取取的什么 ID。不过,你的确可以随便写,然后在下面用 - name: xxx
来显式指定 ID Declaration
。
如果想指定多个 name
怎么办?用 names
:
python-pkgs:
pkg.installed:
- names:
- python-django
- python-crypto
- python-yaml
原生 State modules 列表
http://docs.saltstack.com/en/latest/ref/states/all/index.html
State 执行条件、执行顺序
http://docs.saltstack.com/en/latest/ref/states/requisites.html http://docs.saltstack.com/en/latest/ref/states/ordering.html
require:
在执行这一步之前需要满足的东西都列在下面。不满足就不执行。它不会去修复它们。 甚至可以 require 整个 sls:
include:
- foo
bar:
pkg.installed:
- require:
- sls: foo
require_in:
反过来指定 require。
watch / watch_in:
里面任何一个状态变化(有 change),就触发。
并不是所有 state 都支持 watch
。(service state
能支持。)
# 可以让某个 service 在配置文件被修改时,自动执行 reload / restart
# (Salt 会直接调用 restart,除非设置 reload: True)
ntpd:
service.running:
- watch:
- file: /etc/ntp.conf
file.managed:
- name: /etc/ntp.conf
- source: salt://ntp/files/ntp.conf
# 如何同时 watch 多个 state 呢?
nginx:
service.running:
- reload: True
- watch:
- file: foo
- file: bar
prereq
被要求在 xxx state 之前执行。
它是以 test=True
模式尝试去运行 xxx state,看是否有 “changes”。如果发现 xxx state 将会导致变化,那么立即赶在 xxx 之前执行当前 state。
onfail
另一个 state 执行失败了,执行这一个。
onchanges
另一个 state 执行成功(result=True),并且产生变化。
use
可以复用另一个 state 的参数。
unless
执行下面的内容,如果结果为 False
,才执行该 state。
enable_site:
cmd.run:
- name: a2ensite tc-upgrade-server
- unless: test -e /etc/apache2/sites-enabled/tc-upgrade-server
vim:
pkg.installed:
- unless:
- rpm -q vim-enhanced
- ls /usr/bin/vim
onlyif
执行下面的内容,如果结果为 True
,才执行该 state。
listen / listen_in
跟 watch / watch_in
相似,不过 listen / listen_in
不会改变其它 state 原来的执行顺序。
salt 会保证 所有 listen / listen_in
的动作在 所有 state 的最后才被执行。
check_cmd
用于检查 state 是否按预期成功执行。可以人工指定执行成功的标准。有了它,对于一些执行结果有 error,但实际上我们认为是执行正确的情况,我们就可以直接重新定义检查标准了。如果改成 /bin/true
那这个 sls 就永远执行成功。
order
可以指定第几个执行。也可指定它最后一个执行。
vim:
pkg.installed:
- order: last
include:
通过它可以组合利用多个 State。 Salt State 允许交叉引用,并会递归对需要的 State 进行引用。 include 了,就意味着这个 state 将会在这里执行一次。
include:
- ssh
sshd:
service.running:
- require:
- pkg: something_in_ssh...
extend:
include 后,对之前的内容扩展。
对 require
和 watch
执行的是 append,不会覆盖老的。
而对其它属性,执行的是覆盖操作。
include:
- ssh.server
extend:
/etc/ssh/banner:
file:
- source: salt://ssh/custom-banner
Short cuts
file.managed
与下面的内容等价。相当于是下面的一种 short cuts。
file:
- managed
...
jinja2
Salt 默认用 jinja2 作为 rederer。
基本使用: http://docs.saltstack.com/en/latest/ref/renderers/all/salt.renderers.jinja.html http://jinja.pocoo.org/docs jinja2 内置的 filters http://jinja.pocoo.org/docs/dev/templates/#builtin-filters
在 State 里可以用 jinja 的一些语法来动态生成 State,可以用:salt
、grains
和 pillar
。
把 jinja、salt modules、python 语法相结合可以组合出很多巧妙的设计。
除了 jinja 常规的语法,所有 salt 的 builtin execution modules (像是那些 cmd.run
, test.ping
, 最常用的比如 grains.filter_by
) 都是可以在 jinja 中通过以下形式来调用:
salt['xxx_module.xxx_func'](*args, **kwargs)
或
salt.xxx_module.xxx_func(*args, **kwargs)
pillar.get()
vssalt['pillar.get']()
It should be noted that within templating, the
pillar
variable is just a dictionary. This means that callingpillar.get()
inside of a template will just use the default dictionary.get()
function which does not include the extra:
delimiter functionality. It must be called using the above syntax (salt['pillar.get']('foo:bar:baz', 'qux')
) to get the salt function, instead of the default dictionary behavior.
注意到,用 :
可以快速地找 dict 中的值,注意参数要加 ''
引号。
例如:
# /srv/pillar/apache.sls
apache:
lookup:
name: httpd
config:
tmpl: salt://apache/files/httpd.conf
# /srv/salt/apache/conf.sls
{% from "apache/map.jinja" import apache with context %}
apache_conf:
file:
- managed
- name: {{ salt['pillar.get']('apache:lookup:name') }}
- source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }}
- ...
其它例子:
{% for mnt in salt['cmd.run']('ls /dev/data/moose*').split() %}
/mnt/moose{{ mnt[-1] }}:
mount.mounted:
- device: {{ mnt }}
- fstype: xfs
- mkmnt: True
file.directory:
- user: mfs
- group: mfs
- require:
- user: mfs
- group: mfs
{% endfor %}
apache:
pkg.installed:
{% if grains['os'] == 'RedHat'%}
- name: httpd
{% endif %}
常用方式:
- 取字典中的值
# 两种都可以
{{ foo.bar }}
{{ foo['bar'] }}
# 取值,如果为 None,用默认值
{{ foo.bar | default('zoo') }}
- 与
file.managed
合用,用来渲染文件,替换文件中的变量
# redis.sls
/etc/redis/redis.conf:
file.managed:
- source: salt://redis.conf
- template: jinja
- context:
redis: {{ redis }}
bind: 127.0.0.1
- jinja 另一种经常出现的妙用是:
map.jinja
map.jinja
放在 formula 中,通常为 state 提供变量。它可以整合 grains、默认值以及用户自定义的 pillar,为 state 提供数据。
一些最佳实践: http://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html#writing-formulas http://docs.saltstack.com/en/latest/topics/best_practices.html#variable-flexibility
运行 SLS
# Run the whole formula (salt-tree)
salt '*' state.highstate
# Run directly on minion
salt-call state.highstate -l debug
# Run state testing
# 这里的测试只是测:如果 State 运行,其第一步会运行什么。不是测整个 State 的过程。这在 State 内互相依赖条件较多时能看出。
salt '*' state.sls test=True
salt '*' state.highstate test=True
salt '*' state.single test=True
当 master 运行 state.highstate
,minion 会先下载 top.sls
,如果发现有符合它的匹配,就把相应的文件下载、编译并运行。运行完成后,minion 会返回结果的汇总。
comments powered by Disqus