173SaltStack 运维通关手册--SaltStack State 框架

配置和启用 Salt State Tree

Salt State 系统的核心是 SLS 或 Salt State 文件。 SLS 表示系统应处于的状态,并设置为使用一种简单的格式包含此数据。 这通常称为配置管理。
States 状态存储在 master 上的文本文件中,并通过 master 文件服务器按需传送到 minions。 状态文件的集合即构成 State Tree。
要在 Salt 中开始使用集中式的状态系统,必须首先设置 Salt File Server。 编辑 master 配置文件(file_roots)并添加以下内容:

file_roots:
  base:
    - /etc/salt/srv/salt/base

重启 salt-master

pkill salt-master
salt-master -d

准备 top file 文件
我们之前在 master 上配置的 file_roots 目录下(如果没有配置,默认为 /srv/salt)创建了一个名为 top.sls 的文件,现在修改这个文件。
修改文件: /etc/salt/srv/salt/base/top.sls

base:
  "*":
    - webserver

top file 文件被划分为多个不同的环境(稍后讨论)。默认环境是 base。 在 base 环境下,定义了一系列 minion 匹配映射关系; 现在只需简单的指定为所有主机 *。
创建一个 sls 文件,路径 /etc/salt/srv/salt/base/webserver.sls

nginx:
  pkg:
    - installed

第一行称为 ID 声明,可以是一个任意标识符。 在上面的这种情况下,它定义了要安装的包的名称。
第二行称为 state 声明,它定义了我们正在使用哪些 Salt States。 在此示例中,我们使用 pkg state 来确保安装了给定的包。
第三行称为 Function 声明,它定义了在 pkg state 模块中要调用的函数。
Salt 会将文件以指定的渲染器进行渲染格式化数据,默认为 Jinja2 模板中使用 YAML。
接下来,让我们运行我们创建的状态文件。 在 master 服务器上打开终端并运行:

salt '*' state.apply
www.sublimetext.com:
----------
          ID: nginx
    Function: pkg.installed
      Result: True
     Comment: Package nginx is already installed
     Started: 22:57:25.504406
    Duration: 65.675 ms
     Changes:

Summary for www.sublimetext.com
------------
Succeeded: 1
Failed:    0
------------
Total states run:     1
Total run time:  65.765 ms

我们的 Master 指示所有目标 minions 运行 state.apply。 在没有任何 SLS 目标的情况下执行此功能时,minion 将下载 top file 文件并尝试匹配其中的表达式。 当 minion 与表达式匹配时,将为其列出的模块下载、编译和执行。
在没有指定任何 SLS 文件名称的情况下调用 state.apply 的效果相当于运行 state.highstate
在指定了 SLS 文件名称的情况下调用的 state.apply 将运行 state.sls

命令执行完成后,minion 将报告所有采取的行动和做的所有更改的摘要。
state 文件目录结构规则
如上面的示例中,SLS 文件 webserver.sls 简称为 webserver。 在 top.sls 或 Include 声明中引用时,SLS 文件的命名空间遵循一些简单的规则:
.sls 的后缀被丢弃了 (也就是说 webserver.sls 变成了 webserver)。

子目录被做了更好的组织,其中:
每个子目录在 Salt 状态和命令行中用点(遵循 Python 导入模型)表示。 文件系统上的 webserver/dev.sls,在 Salt 中称为 webserver.dev。由于斜杠表示为点,因此 SLS 文件不能在名称中包含点(SLS 文件后缀的点除外)。 你将无法正确匹配 SLS 文件 webserver_1.0.sls,因为 webserver_1.0 将与目录 /webserver_1/0.sls 匹配
子目录中名为 init.sls 的文件将通过目录的路径引用。 因此,webserver/init.sls 称作 webserver。
如果 webserver.sls 和 webserver/init.sls 都存在,webserver/init.sls 将被忽略,webserver.sls 将被作为合法的 webserver 使用。
更复杂的 STATES、REQUISITES
require 关键字用以进行 state 依赖,表明多个 state 直接的依存关系。编辑 /etc/salt/srv/salt/base/webserver.sls 文件:

nginx:
  pkg.installed: [] # 安装 nginx 软件包

  file.absent:  # 删除影响 service 模块的文件
    - names:
      - /etc/init/nginx.conf
      - /etc/nginx/conf.d/default.conf

/etc/nginx/nginx.conf:
  file.managed:
    - source: salt://webserver/nginx.conf
    - require:
      - pkg: nginx

nginx service:
  service.running:
    - name: nginx
    - watch:
      - file: /etc/nginx/nginx.conf
      - file: /etc/nginx/conf.d/default.conf
    - require:
      - pkg: nginx

/var/www/index.html:
  file:
    - managed
    - source: salt://webserver/index.html
    - require:
      - pkg: nginx

/var/www/index.html 是一个 ID 声明,在实例中同样作为 file state 的目标位置存在。
file 表示该示例使用 Salt 的 file state。
managed 表示使用 managed function 将从 master 服务器下载文件并将其安装在指定的位置。
absent 表示使用 absent function 来删除指定的文件。
source 作为 managed 函数的参数传递进去。
watch 表示监听某个任务,当任务状态为 Changes 时,会操作重启 nginx。
require 表示这个 state 任务依赖别的 state 任务。
pkg 作为 一个 require 依赖,指向了一个 state 和一个 ID,及我们声明的 pkg 安装 nginx 部分,该声明告诉 Salt 在安装 nginx 服务之前不要安装 HTML 文件。

创建目录 /etc/salt/srv/salt/base/webserver/,然后创建 source 源文件 index.html,输入如下内容:

Hello,SaltStack

编辑 Nginx 主配置文件 /etc/salt/srv/salt/base/webserver/nginx.conf:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    server {
        listen       80;
        location / {
            root   /var/www;
            index  index.html index.htm;
        }
    }
    include /etc/nginx/conf.d/*.conf;
}

执行命令 salt '*' state.apply webserver


image.png

测试是否正常访问 http 服务

curl http://127.0.0.1

image.png

模板、包含和扩展
在处理复杂业务逻辑时,SLS 模块可能需要编程逻辑或内联执行。 这是通过模块模板实现的。 使用的默认模块模板系统是 Jinja2,这可以通过更改 master 配置中的 renderer 值来配置。
测试文件 /etc/salt/srv/salt/base/test_temp.sls

{% for usr in 'moe','larry','curly' %}
{{ usr }}:
  group:
    - present
  user:
    - present
    - groups:
      - {{ usr }}
    - require:
      - group: {{ usr }}
{% endfor %}

示例中,我们使用 for 循环遍历一个列表,分别调用 group 和 user 模块创建对应的组和用户。
sls 文件写法遵循 yaml 和 jinjia2 语法即可。
执行 salt '*' state.apply test_temp


image.png

查看任务是否确实完成。

cat /etc/group | tail -3

image.png

在 SLS 模块中使用环境变量
测试文件 /etc/salt/srv/salt/base/test_env_var.sls

{% set myenvvar = salt['environ.get']('MYENVVAR') %}
{% if myenvvar %}

创建一个从环境变量获取内容的文件:
  file.managed:
    - name: /tmp/hello
    - contents: {{ salt['environ.get']('MYENVVAR') }}

{% else %}

Fail - 没有指定环境输入:
  test.fail_without_changes

{% endif %}

进入目录 /etc/salt/srv/salt/base/,然后执行命令 MYENVVAR="world" salt-call state.template test_env_var

image.png

如果不执行环境变量,salt-call state.template test_env_var
image.png

在 SLS 模块中使用 grains
测试文件 /etc/salt/srv/salt/base/test_grains.sls

echo 服务器ID:{{ grains.id }}: cmd.run
salt '*' state.apply test_grains

image.png

在模板中调用 Salt 模块的方法
由 minion 加载的所有 Salt 模块都可在模板系统中使用。 这允许在目标系统上实时收集数据。 它还允许从 sls 模块中轻松运行 shell 命令。
测试示例 /etc/salt/srv/salt/base/test_salt_mod.sls:

调用 Salt module:
  cmd.run:
    - name: echo "Salt State 调用 module {{ salt['network.hw_addr']('eth0') }}"

示例中,我们只想 cmd.run 模块,但传入参数中调用了 network.hw_addr 获取 eth0 的 mac 地址。
执行 salt '*' state.apply test_salt_mod

image.png

include 导入其他文件中的任务
新建目录 /etc/salt/srv/salt/base/python
创建文件 /etc/salt/srv/salt/base/python/python-libs.sls

python-dateutil: pkg.installed

调用 pkg.installed 安装 python-dateutil 软件
创建文件 /etc/salt/srv/salt/base/python/django.sls

include:
  - python.python-libs

django:
  pip.installed:
    - require:
        - pkg: python-dateutil

示例中,我们通过 include 包含了 python/python-libs.sls 声明的任务,并通过 require 依赖了它。
include 的方法与 python 导入模块类似,忽略 .sls 后缀,并通过 . 作为连接符
执行 salt '*' state.apply python.django

image.png

extend 用来修改已经定义的任务
我们可以使用 Extend 修改以前的声明。
首先创建目录 /etc/salt/srv/salt/base/web/,然后定义一个 nginx 安装的 state 文件 /etc/salt/srv/salt/base/web/nginx.sls

nginx:
  pkg.installed: []
  file.absent:
  - name: /etc/init/nginx.conf

定义一个推送 nginx 文件,扩展 nginx 重启的 state 文件 /etc/salt/srv/salt/base/web/web.sls

include:
  - web.nginx

extend:
  nginx:
    service:
      - running
      - watch:
          - file: nginx_config

nginx_config:
  file.managed:
    - source: salt://fileroot/nginx/a.conf
    - name: /etc/nginx/conf.d/a.conf

新建存放 nginx 配置文件的目录,mkdir -p /etc/salt/srv/salt/base/fileroot/nginx/
新建 nginx 配置文件 touch /etc/salt/srv/salt/base/fileroot/nginx/a.conf
执行 state 文件

salt '*' state.apply web.web

image.png

我们可以看到 nginx 执行了重启操作。
开发自定义的 State 模块
在使用状态模块之前,必须将其分发给各 minions。 可以通过将它们放入 salt://_states/ 中来完成。 然后可以通过运行 saltutil.sync_states 或 saltutil.sync_all 将它们手动分发给 minions。 或者,在运行 highstate 状态时,自定义状态类型将自动同步。
注意:使用文件名中携带连字符的状态模块会导致 !pyobjects 例程出现问题。 请坚持使用下划线的最佳做法。
任何已与 minions 同步的自定义状态(与 Salt 的默认状态集中的名称之一相同的)将替换具有相同名称的默认状态。 请注意,状态模块的名称根据其文件名默认为同一个(即 foo.py 变为状态模块 foo ),但是可以使用 virtual 函数覆盖其名称。
自定义 state 模块遵循的步骤
设置返回数据字典并执行任何必要的输入验证(类型检查,寻找互斥参数的使用等)。
检查是否需要进行更改。 最好通过附带的执行模块中的信息收集功能来完成此操作。 该状态应该能够使用该函数的返回值来判断该 minion 是否已经处于所需状态。
如果步骤 2 发现 minion 已经处于所需状态,则立即退出,并返回 True 结果,而无需进行任何更改。
如果步骤 2 发现确实需要进行更改,请检查状态是否在测试模式下运行(即使用 test=True)。 如果是这样,则退出并返回值为 None 的结果、相关注释和(如有可能)将进行哪些更改的描述。
执行所需的更改。 应该再次使用附带的执行模块中的函数来完成此操作。 如果该函数的结果足以告诉您是否发生了错误,则可以退出并返回 False 结果和相关注释以说明发生了什么。
再次从步骤 2 执行相同的检查,以确认 minion 是否处于所需状态。 就像在第 2 步中一样,此函数应该能够通过其返回数据来告诉您是否需要进行更改。
设置返回数据并返回。
自定义 state 调用 salt 组件
状态模块可以使用 __salt__ 调用 salt 执行模块。 使用 __grains__ 调用 salt 数据。 使用 __states__ 调用 salt state 模块。 类似 ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile')
自定义 state 返回数据
name:与传递给状态的 name 参数值相同。
changes:一个描述变更的字典。 每个被更改的事物都应该是一个键,其值则作为另一个字典,其中包含旧/新值的键称为 old 和 new。
result: 取值为三个值之一。 如果操作成功,则为 True;否则为 False;如果状态以测试模式运行,则为 None,即 test=True;如果状态未以测试模式运行,则将进行更改。
自定义 state 模块示例
自定义执行模块文件:/etc/salt/srv/salt/base/_modules/my_custom_module.py:

import random
def current_state(*k, **kw):
    return random.choice([True, False])

def change_state(*k, **kw):
    return random.choice([True, False])

创建目录 /etc/salt/srv/salt/base/_states/,然后添加自定义 state 模块文件:/etc/salt/srv/salt/base/_states/custom_state.py

import salt.exceptions

def enforce_custom_thing(name, foo, bar=True):
    '''
    定义一个 state 状态

    这个状态模块做一个定制的事情。它调用执行模块
    `my_custom_module` 来检查当前系统并执行任何
    需要更改。

    name
        The thing to do something to
    foo
        一个请求参数
    bar : True
        一个带默认值的参数
    '''
    ret = {
        'name': name,
        'changes': {},
        'result': False,
        'comment': '',
        }

    # 做基本的数据确认,参数核对.
    if bar == True and foo.startswith('Foo'):
        raise salt.exceptions.SaltInvocationError(
            'Argument "foo" cannot start with "Foo" if argument "bar" is True.')

    # 核对当前的系统状态,是否需要做修改
    current_state = __salt__['my_custom_module.current_state'](name)

    if current_state == foo:
        ret['result'] = True
        ret['comment'] = 'System already in the correct state'
        return ret

    # 如果我们运行在 `test=true` 状态下, 将不做任何更改.
    if __opts__['test'] == True:
        ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
        ret['changes'] = {
            'old': current_state,
            'new': 'Description, diff, whatever of the new state',
        }

        #  如果运行在 test 模式,返回 `None`
        ret['result'] = None

        return ret

    # 最后,执行修改并返回结果
    new_state = __salt__['my_custom_module.change_state'](name, foo)

    ret['comment'] = 'The state of "{0}" was changed!'.format(name)

    ret['changes'] = {
        'old': current_state,
        'new': new_state,
    }

    ret['result'] = True

    return ret

调用自定义 state 模块文件:/etc/salt/srv/salt/base/call_custom_state.sls

测试自定义 state 模块:
  custom_state:
    - enforce_custom_thing      # 调用自定义 state 模块中的函数
    - name: a_value
    - foo: Foo
    - bar: False

执行:

salt '*' saltutil.sync_all
salt '*' state.apply call_custom_state
image.png

你可能感兴趣的:(173SaltStack 运维通关手册--SaltStack State 框架)