JunJiang江骏 bio photo

JunJiang江骏

Staff Engineer @ Ant Group. Machine Learning GDE(Google Developer Expert). Focus on machine learning platform and training framework optimization.

Twitter Google+ LinkedIn Instagram Github Weibo

Salt (3) 核心所在:State

tags: salt

.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 的寻找规则

  1. 如果有名为 base 的 env,且它的文件目录里有 top.sls。(比如 /srv/salt/base/top.sls)这时其他任何的 top.sls 都将被无视,只取 base 里的这个 top.sls。
  2. 如果没有在 base 里配置 top.sls,那么,在剩下的几个 env 中,按 file_roots 中 env 名字的字母顺序,依次检查它们目录下的 top.sls,找第一个含有 base 的那个 top.sls,一旦找到,其它 env 的 top.sls 都将被无视。
  3. 如果压根没有 base 这个 env。就找对应 env 下的 top.sls,各个 env 自己负责自己的。如果这样没找到,那就又去按字母顺序去别的 env 里找自己 env 的配置,找到为止。

创建一个 SLS 文件

SLS 文件的命名规则

  1. 使用时,忽略 .sls。在 Salt 中使用 xxx.sls 时,直接写 xxx 就可以了。
  2. . 连接多层目录。aaa/bbb/haha.sls 写成 aaa.bbb.haha
  3. 如果目录下有 init.sls,使用时只需写目录名称就可以了。aaa/init.sls 写成 aaa
  4. 如果同时存在 aaa.slsaaa/init.slsaaa 将只指向 aaa.sls,忽略 aaa/init.sls
  5. 建议不要跟 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 后,对之前的内容扩展。 对 requirewatch 执行的是 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,可以用:saltgrainspillar。 把 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() vs salt['pillar.get']()

It should be noted that within templating, the pillar variable is just a dictionary. This means that calling pillar.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