公式是预先编写的Salt States状态。 它们与Salt State一样可以开放的使用,可用于诸如安装软件包、配置和启动服务,设置用户或权限以及许多其他常见任务之类的任务。这些Formulas资源,是Salt社区成员们所共享并在共同更新维护的Salt States配置模板资源,可能你还在考虑怎么解决一类事件中某一个点的问题,但在Formulas资源中也许已经提供了经由社区提供的相关最佳配置实践了。
请尽快阅读本文并掌握专家级别的Salt States状态配置资源使用方法吧。
您也可以参考在Github上维护的一份相同的技术资料:Salt Formulas
在GitHub的“saltstack-formulas”组织中,所有正式的Salt Formulas都可以作为单独的Git存储库找到:https://github.com/saltstack-formulas
作为一个简单的示例,要安装流行的Apache Web服务器(使用基础发行版的defaults默认设置),只需在topfile文件中包含apache-formula:
base:
'web*':
- apache
每个Salt Formula是一个单独的Git存储库,被设计为现有的Salt State树的一个插件。 可以通过以下方式安装公式资源。
Salt的GitFS文件服务器后端的一个设计目标是促进可重用的状态。 GitFS是使用公式的一种快速而自然的方法。
gitfs_remotes:
- https://github.com/saltstack-formulas/apache-formula
- https://github.com/saltstack-formulas/memcached-formula
强烈建议将公式存储库forking到您自己的GitHub帐户中,以避免对基础结构进行了意外的更改。
许多Salt Formulas都是高度活跃的存储库,因此请谨慎地进行新的更改。 加上您对fork所做的任何添加,都可以通过快速pull request轻松地将其发送回上游!
从2018.3.0版本开始,将公式与GitFS一起使用相对使用许多不同文件服务器环境(即saltenvs)的部署会更加方便。 使用all_saltenvs参数时,来自单个git branch/tag的文件将出现在所有Salt环境中。 有关此功能的更多信息,请参见 此处。
公式只是目录,可以通过使用Git克隆存储库或通过下载和扩展存储库的tarball或zip文件将其复制到本地文件系统中。 目录结构旨在与Saltmaster配置中的 file_roots 一起使用。
mkdir -p /srv/formulas
cd /srv/formulas
git clone https://github.com/saltstack-formulas/apache-formula.git
# or
mkdir -p /srv/formulas
cd /srv/formulas
wget https://github.com/saltstack-formulas/apache-formula/archive/master.tar.gz
tar xf apache-formula-master.tar.gz
file_roots:
base:
- /srv/salt
- /srv/formulas/apache-formula
每个公式都可以立即使用默认值而无需任何其他配置。 通过在Pillar中包含数据,还可以配置许多公式。 有关可用选项,请参见每个公式存储库中的pillar.example
文件。
公式可能包含在现有的sls
文件中。 当您正在编写的状态需要要求或扩展公式中定义的状态时,这通常很有用。
这是在require
声明中使用epel-formula的状态示例,该状态指示Salt直到还安装了EPEL存储库后才安装python26
软件包:
include:
- epel
python26:
pkg.installed:
- require:
- pkg: epel
某些Formula执行完全独立的安装,而其他状态文件未引用该安装。 直接从顶级topfile文件中包含这些公式通常是最干净的使用方法。
例如,在单台计算机上设置OpenStack部署的最简单方法是直接从top.sls
文件包含 openstack-standalone-formula:
base:
'myopenstackmaster':
- openstack
也可以直接从顶级topfile文件中完成在多台专用计算机上快速部署OpenStack的过程,如下所示:
base:
'controller':
- openstack.horizon
- openstack.keystone
'hyper-*':
- openstack.nova
- openstack.glance
'storage-*':
- openstack.swift
Salt Formulas无需额外配置即可直接使用。 但是,许多Formula支持通过Pillar进行其他配置和自定义。 可用选项的示例可以在每个公式存储库的根目录中的名为pillar.example
的文件中找到。
请记住,公式是常规的Salt状态,可以与所有Salt的正常状态机制一起使用。 可以使用require
声明从其他States要求使用公式,可以使用扩展对其进行修改,也可以使用_in版本的Requires使其监视其他状态。
以下示例将stock apache-formula与自定义状态一起使用,以在Debian/Ubuntu系统上创建虚拟主机,并在更改虚拟主机时重新加载Apache服务。
# Include the stock, upstream apache formula.
include:
- apache
# Use the watch_in requisite to cause the apache service state to reload
# apache whenever the my-example-com-vhost state changes.
my-example-com-vhost:
file:
- managed
- name: /etc/apache2/sites-available/my-example-com
- watch_in:
- service: apache
请不要拒绝去阅读每个公式的源代码!
每个公式都是在GitHub上的单独存储库。 如果您遇到公式错误,请在各自的存储库中提交问题! 作为pull request请求发送修订和补充。 向存储库Wiki添加提示和技巧。
每个公式都是GitHub上的saltstack-formulas组织中的单独存储库。
现在,创建新公式存储库的最佳方法是在自己的GitHub帐户中创建存储库,并在准备就绪时通知SaltStack员工。我们将把您添加到saltstack-formulas组织的Contributors团队中,并帮助您转移存储库。在IRC(在Freenode上的#salt)上对SaltStack员工执行Ping操作,加入salt-slack上的#formulas频道,或将电子邮件发送到salt-users邮件列表。
该组织中有很多存储库!团队成员可以在GitHub的观看页面(https://github.com/watching)上管理他们所订阅的存储库。
欢迎贡献者团队的成员参与审查整个组织的pull requests请求。一些存储库将有定期的贡献者,而一些存储库则没有。当您参与存储库时,请确保在请求很大或发生重大更改的拉取请求中与该库中的其他任何贡献者进行沟通。
通常,最好让另一个Contributor审查并合并您打开的任何拉取请求。随时艾特通知其他常规贡献者到存储库并请求进行审查。但是,由于有很多公式存储库,因此,如果存储库尚无常规参与者,或者您的拉取请求保持开放状态已经超过两天,则可以“selfie-merge”自行合并您自己的拉取请求。
可维护性,可读性和可重用性都是良好的Salt sls文件的标志。 本节包含一些建议和示例。
# Deploy the stable master branch unless version overridden by passing
# Pillar at the CLI or via the Reactor.
deploy_myapp:
git.latest:
- name: [email protected]/myco/myapp.git
- version: {{ salt.pillar.get('myapp:version', 'master') }}
状态的ID用作唯一标识符,可以在必要时通过其他状态进行引用。 它在整个状态树中必须是唯一的(毕竟,它是字典中的键)。
另外,状态ID应该是描述性的,并作为其将执行、管理或更改的高级提示信息。 例如,deploy_webapp
或apache
或reload_firewall
。
引用状态模块和状态函数时,最好使用所谓的“短声明”符号。 它提供了在Salt State,Reactor,Salt Mine,Scheduler以及CLI之间共享的module.function
的一致模式。
# Do
apache:
pkg.installed:
- name: httpd
# Don't
apache:
pkg:
- installed
- name: httpd
当将人类易读的高级状态结构编译为机器友好的低级状态结构时,Salt的状态编译器会将“short-decs”转换为较长的格式。
对状态ID使用唯一的永久标识符,对具有可变性的数据使用保留的 name
参数。
名称声明是所有状态功能函数的必需参数。 如果未在状态中明确设置状态名称,则它将state ID隐式用作name
名称。
在许多状态函数中,name
参数用于变化的数据,例如特定于OS的软件包名称,特定于OS的文件系统路径,存储库地址等。每当状态ID更改时,也必须更改对该ID的所有引用。 在需要将状态写成面向未来的状态时,请使用永久性ID,以便在将来进行重构。
YAML允许以不同的缩进级别进行注释。 注释状态文件是一个好习惯。 使用垂直空格在视觉上分隔不同的概念或动作。
# Start with a high-level description of the current sls file.
# Explain the scope of what it will do or manage.
# Comment individual states as necessary.
update_a_config_file:
# Provide details on why an unusual choice was made. For example:
#
# This template is fetched from a third-party and does not fit our
# company norm of using Jinja. This must be processed using Mako.
file.managed:
- name: /path/to/file.cfg
- source: salt://path/to/file.cfg.template
- template: mako
# Provide a description or explanation that did not fit within the state
# ID. For example:
#
# Update the application's last-deployed timestamp.
# This is a workaround until Bob configures Jenkins to automate RPM
# builds of the app.
cmd.run:
# FIXME: Joe needs this to run on Windows by next quarter. Switch these
# from shell commands to Salt's file.managed and file.replace state
# modules.
- name: |
touch /path/to/file_last_updated
sed -e 's/foo/bar/g' /path/to/file_environment
- onchanges:
- file: a_config_file
请谨慎使用Jinja注释来注释Jinja代码,并使用YAML注释来注释YAML代码。
# BAD EXAMPLE
# The Jinja in this YAML comment is still executed!
# {% set apache_is_installed = 'apache' in salt.pkg.list_pkgs() %}
# GOOD EXAMPLE
# The Jinja in this Jinja comment will not be executed.
{# {% set apache_is_installed = 'apache' in salt.pkg.list_pkgs() %} #}
Jinja模板在构建Salt sls文件时提供了极大的灵活性和功能。 它还可能导致逻辑和数据无法维持的困境。 概括地说,Jinja是最好与states分开(尽可能多)的地方。
以下是如何有效使用Jinja的指南和示例。
编写状态时,有关如何编译和运行Salt状态的高级知识非常有用。
Salt中的默认渲染器设置是通过管道传输到YAML的Jinja。每个步骤都是单独的步骤。每个步骤都不知道上一个或下一个步骤。 Jinja不了解YAML,YAML不了解Jinja;他们不能共享变量或进行交互。
完整的评估和执行顺序如下:
Jinja -> YAML -> Highstate -> low state -> execution
避免从Jinja调用更改底层系统的命令。 通过Jinja运行的命令不遵守Salt的dry-run模式(test=True
)! 除非运行的命令也是幂等的,否则这通常与Salt状态的幂等性冲突。
在Salt States中,Jinja的常见用法是收集有关基础系统的信息。 Jinja上下文中提供的grains字典是Salt本身已经收集的常见数据点的一个很好的例子。 而关于本地系统属性的一些不太常见的值通常是通过运行命令找到的。 例如:
{% set is_selinux_enabled = salt.cmd.run('sestatus') == '1' %}
通常最好通过变量分配来完成,以便将数据与将使用数据的状态分开。
Jinja最常见的用途之一是将外部数据拉入状态文件。 外部数据可以来自API调用或数据库查询之类的任何地方,但最常见的是来自文件系统上的文本文件或Salt Master的Pillar数据。 例如:
{% set some_data = salt.pillar.get('some_data', {'sane default': True}) %}
{# or #}
{% import_yaml 'path/to/file.yaml' as some_data %}
{# or #}
{% import_json 'path/to/file.json' as some_data %}
{# or #}
{% import_text 'path/to/ssh_key.pub' as ssh_pub_key %}
{# or #}
{% from 'path/to/other_file.jinja' import some_data with context %}
通常最好通过变量分配来完成,以便将数据与将使用数据的状态分开。
Jinja对于以编程方式生成Salt状态非常强大。 也容易过度使用。 根据经验,如果很难阅读,将很难维护!
尽可能将Jinja控制流语句与状态分开,以创建可读状态。 将状态内的Jinja限制为简单的变量查找。
以下是可读循环的简单示例:
{% for user in salt.pillar.get('list_of_users', []) %}
{# Ensure unique state IDs when looping. #}
{{ user.name }}-{{ loop.index }}:
user.present:
- name: {{ user.name }}
- shell: {{ user.shell }}
{% endfor %}
避免在可能的情况下在Salt states内放置Jinja条件句。 可读性受到影响,并且在周围的视觉噪声中很难看到正确的YAML缩进。 参数化(下面讨论)和变量都是避免这种情况的有用技术。 例如:
{# ---- Bad example ---- #}
apache:
pkg.installed:
{% if grains.os_family == 'RedHat' %}
- name: httpd
{% elif grains.os_family == 'Debian' %}
- name: apache2
{% endif %}
{# ---- Better example ---- #}
{% if grains.os_family == 'RedHat' %}
{% set name = 'httpd' %}
{% elif grains.os_family == 'Debian' %}
{% set name = 'apache2' %}
{% endif %}
apache:
pkg.installed:
- name: {{ name }}
{# ---- Good example ---- #}
{% set name = {
'RedHat': 'httpd',
'Debian': 'apache2',
}.get(grains.os_family) %}
apache:
pkg.installed:
- name: {{ name }}
字典对于有效地“命名空间”变量集合很有用。 这对于参数化(在下面讨论)很有用。 字典也很容易合并和合并。 而且可以将它们直接序列化为YAML,这通常比尝试通过模板创建有效的YAML要容易。 例如:
{# ---- Bad example ---- #}
haproxy_conf:
file.managed:
- name: /etc/haproxy/haproxy.cfg
- template: jinja
{% if 'external_loadbalancer' in grains.roles %}
- source: salt://haproxy/external_haproxy.cfg
{% elif 'internal_loadbalancer' in grains.roles %}
- source: salt://haproxy/internal_haproxy.cfg
{% endif %}
- context:
{% if 'external_loadbalancer' in grains.roles %}
ssl_termination: True
{% elif 'internal_loadbalancer' in grains.roles %}
ssl_termination: False
{% endif %}
{# ---- Better example ---- #}
{% load_yaml as haproxy_defaults %}
common_settings:
bind_port: 80
internal_loadbalancer:
source: salt://haproxy/internal_haproxy.cfg
settings:
bind_port: 8080
ssl_termination: False
external_loadbalancer:
source: salt://haproxy/external_haproxy.cfg
settings:
ssl_termination: True
{% endload %}
{% if 'external_loadbalancer' in grains.roles %}
{% set haproxy = haproxy_defaults['external_loadbalancer'] %}
{% elif 'internal_loadbalancer' in grains.roles %}
{% set haproxy = haproxy_defaults['internal_loadbalancer'] %}
{% endif %}
{% do haproxy.settings.update(haproxy_defaults.common_settings) %}
haproxy_conf:
file.managed:
- name: /etc/haproxy/haproxy.cfg
- template: jinja
- source: {{ haproxy.source }}
- context: {{ haproxy.settings | yaml() }}
在以上示例中,仍有改进的空间。 例如,提取到外部文件中或将if-elif条件替换为函数调用,以更简洁地过滤正确的数据。 但是,状态本身是简单易读的,数据是独立的,也是简单易读的。 那些建议的改进可以在将来的某个日期完成,而无需改变状态!
Jinja不是Python。 它是由Python程序员制作的,具有许多语义和某些语法,但它不允许任意的Python函数调用或Python导入。 Jinja是一种快速高效的模板语言,但语法可能很冗长且视觉上很杂乱。
一旦在sls文件中使用Jinja变得有点复杂,例如较长的if-elif-elif-else语句链,嵌套条件,复杂的字典合并,想要使用sets集合等。此时可以考虑使用其他的Salt渲染器,例如Python渲染器。 根据经验,如果难以阅读,将很难维护。切换到易于阅读的格式很重要。
使用备用渲染器非常简单,只需在文件顶部使用Salt的“she-bang”语法即可。 Python渲染器必须简单地返回正确的高级状态数据结构。 以下示例是一个包含两个sls文件的状态树,一个简单文件,一个复杂文件。
/srv/salt/top.sls:
base:
'*':
- common_configuration
- roles_configuration
/srv/salt/common_configuration.sls:
common_users:
user.present:
- names:
- larry
- curly
- moe
/srv/salt/roles_configuration:
#!py
def run():
list_of_roles = set()
# This example has the minion id in the form 'web-03-dev'.
# Easily access the grains dictionary:
try:
app, instance_number, environment = __grains__['id'].split('-')
instance_number = int(instance_number)
except ValueError:
app, instance_number, environment = ['Unknown', 0, 'dev']
list_of_roles.add(app)
if app == 'web' and environment == 'dev':
list_of_roles.add('primary')
list_of_roles.add('secondary')
elif app == 'web' and environment == 'staging':
if instance_number == 0:
list_of_roles.add('primary')
else:
list_of_roles.add('secondary')
# Easily cross-call Salt execution modules:
if __salt__['myutils.query_valid_ec2_instance']():
list_of_roles.add('is_ec2_instance')
return {
'set_roles_grains': {
'grains.present': [
{'name': 'roles'},
{'value': list(list_of_roles)},
],
},
}
在Salt sls文件中,Jinja宏仅可用于一件事:创建可按需重用和呈现的微型模板。 不要陷入将宏视为函数的陷阱; Jinja不是Python(请参见上文)。
宏对于创建可重用的参数化状态很有用。 例如:
{% macro user_state(state_id, user_name, shell='/bin/bash', groups=[]) %}
{{ state_id }}:
user.present:
- name: {{ user_name }}
- shell: {{ shell }}
- groups: {{ groups | json() }}
{% endmacro %}
{% for user_info in salt.pillar.get('my_users', []) %}
{{ user_state('user_number_' ~ loop.index, **user_info) }}
{% endfor %}
宏对于创建可以接受数据结构并将其写出为特定于域的配置文件的一次性“序列化器”也很有用。 例如,以下宏可用于编写php.ini配置文件:
/srv/salt/php.sls:
php_ini:
file.managed:
- name: /etc/php.ini
- source: salt://php.ini.tmpl
- template: jinja
- context:
php_ini_settings: {{ salt.pillar.get('php_ini', {}) | json() }}
/srv/pillar/php.sls:
php_ini:
PHP:
engine: 'On'
short_open_tag: 'Off'
error_reporting: 'E_ALL & ~E_DEPRECATED & ~E_STRICT'
/srv/salt/php.ini.tmpl:
{% macro php_ini_serializer(data) %}
{% for section_name, name_val_pairs in data.items() %}
[{{ section_name }}]
{% for name, val in name_val_pairs.items() -%}
{{ name }} = "{{ val }}"
{% endfor %}
{% endfor %}
{% endmacro %}
; File managed by Salt at <{{ source }}>.
; Your changes will be overwritten.
{{ php_ini_serializer(php_ini_settings) }}
状态使用的数据与状态本身分开,以增加状态的灵活性和可重用性。
一个明显且常见的示例是特定于平台的程序包名称和文件系统路径。另一个示例是应用程序的defaults默认设置,或公司或组织内的通用设置。将此类数据组织为字典(又名哈希图,查找表,关联数组)通常可提供轻量级的命名空间,并允许快速轻松地进行查找。此外,使用字典可以轻松地合并和覆盖查找表中的静态值和从Pillar获取的动态值。
Salt Formulas中的一个惯例是将特定于平台的数据(例如包名称和文件系统路径)放入状态文件旁边放置的名为map.jinja
的文件中。
以下是来自MySQL Formula公式的示例。 grains.filter_by函数使用os_family
grain(默认)在该表上执行查找。
结果是将mysql
变量分配给当前平台的查找表的子集。这样,各states就可以引用软件包名称,而不必担心基础操作系统。引用值的语法是Jinja中的常规字典查找,例如{{mysql ['service']}}
或简写{{mysql.service}}
。
map.jinja:
{% set mysql = salt['grains.filter_by']({
'Debian': {
'server': 'mysql-server',
'client': 'mysql-client',
'service': 'mysql',
'config': '/etc/mysql/my.cnf',
'python': 'python-mysqldb',
},
'RedHat': {
'server': 'mysql-server',
'client': 'mysql',
'service': 'mysqld',
'config': '/etc/my.cnf',
'python': 'MySQL-python',
},
'Gentoo': {
'server': 'dev-db/mysql',
'client': 'dev-db/mysql',
'service': 'mysql',
'config': '/etc/mysql/my.cnf',
'python': 'dev-python/mysql-python',
},
}, merge=salt['pillar.get']('mysql:lookup')) %}
可以使用以下语法在任何状态文件中为当前平台获取映射文件中定义的值:
{% from "mysql/map.jinja" import mysql with context %}
mysql-server:
pkg.installed:
- name: {{ mysql.server }}
service.running:
- name: {{ mysql.service }}
最佳做法是使formulas 公式将所有与公式相关的参数放在第二级lookup
关键字下,位于指定用于保存特定service/software/等数据的主要命名空间中,该名称空间由公式管理:
mysql:
lookup:
version: 5.7.11
可以将公共值收集到一个base字典中。 这样可以最大程度地减少每个lookup_dict
子词典中相同值的重复。 现在,备用值只能指定与基数不同的值:
map.jinja
:
{% set mysql = salt['grains.filter_by']({
'default': {
'server': 'mysql-server',
'client': 'mysql-client',
'service': 'mysql',
'config': '/etc/mysql/my.cnf',
'python': 'python-mysqldb',
},
'Debian': {
},
'RedHat': {
'client': 'mysql',
'service': 'mysqld',
'config': '/etc/my.cnf',
'python': 'MySQL-python',
},
'Gentoo': {
'server': 'dev-db/mysql',
'client': 'dev-db/mysql',
'python': 'dev-python/mysql-python',
},
},
merge=salt['pillar.get']('mysql:lookup'), base='default') %}
允许覆盖查询表中的静态值。 这是一个简单的模式,再次增加了状态文件的灵活性和可重用性。
filter_by中的merge
参数指定Pillar中词典的位置,该词典可用于覆盖从查找表返回的值。 如果该值存在于“Pillar”中,则将具有优先权。
当软件或配置文件安装在非标准位置或不支持的平台上时,此功能很有用。 例如,以下Pillar将替换上面调用中的config
值。
mysql:
lookup:
config: /usr/local/etc/mysql/my.cnf
注
用特殊字符保护内容的扩展
进行模板制作时,请记住,YAML确实具有用于引用、流处理以及其他特殊结构和内容的特殊字符。 当Jinja替换项可能包含特殊字符时,YAML将无法正确解析这些特殊字符。 使用
yaml_encode
或yaml_dquote
Jinja过滤器是一个好策略:{%- set foo = 7.7 %} {%- set baz = true %} {%- set zap = 'The word of the day is "salty".' %} {%- set zip = '"The quick brown fox . . ."' %} foo: {{ foo|yaml_encode }} bar: {{ bar|yaml_encode }} baz: {{ baz|yaml_encode }} zap: {{ zap|yaml_encode }} zip: {{ zip|yaml_dquote }}
上面的配置将被渲染为:
foo: 7.7 bar: null baz: true zap: "The word of the day is \"salty\"." zip: "\"The quick brown fox . . .\""
filter_by函数执行简单的字典查找,但也允许从Pillar中获取数据并覆盖存储在查找表中的数据。 无需使用filter_by也可轻松执行相同的工作流程; 除了来自Pillar的数据,其他字典也可以使用。
{% set lookup_table = {...} %}
{% do lookup_table.update(salt.pillar.get('my:custom:data')) %}
map.jinja
文件只是Salt Formulas中的一个约定。 这种更大的模式对于各种工作流程中的各种数据很有用。 此模式不限于从单个文件或数据源提取数据。 例如,这种模式在States、 Pillar 和 Reactor中很有用。
使用数据结构而不是配置文件,可以从多个源(本地文件,远程Pillar,数据库查询等)将数据拼凑在一起,进行合并,覆盖和搜索。
以下是一些查找表可能有用以及如何使用和表示的示例。
Salt Formulas 公式中一个很明显的模式(在Salt公式中经常使用)是在名为map.jinja
的文件中提取特定于平台的信息,例如程序包名称和文件系统路径。 该模式已在上面详细说明。
应用程序设置非常适合使用此模式。 将默认设置与状态本身一起存储,并在Pillar中保留替代设置和敏感设置。 将两者合并为一个字典,然后编写应用程序配置或设置文件。
下面的示例将大多数Apache Tomcat server.xml文件与Tomcat状态存储在一起,然后允许通过Pillar更新或扩充值。 (此示例使用BadgerFish格式将JSON转换为XML。)
/srv/salt/tomcat/defaults.yaml
:
Server:
'@port': '8005'
'@shutdown': SHUTDOWN
GlobalNamingResources:
Resource:
'@auth': Container
'@description': User database that can be updated and saved
'@factory': org.apache.catalina.users.MemoryUserDatabaseFactory
'@name': UserDatabase
'@pathname': conf/tomcat-users.xml
'@type': org.apache.catalina.UserDatabase
# <...snip...>
/srv/pillar/tomcat.sls
:
appX:
server_xml_overrides:
Server:
Service:
'@name': Catalina
Connector:
'@port': '8009'
'@protocol': AJP/1.3
'@redirectPort': '8443'
# <...snip...>
/srv/salt/tomcat/server_xml.sls
:
{% import_yaml 'tomcat/defaults.yaml' as server_xml_defaults %}
{% set server_xml_final_values = salt.pillar.get(
'appX:server_xml_overrides',
default=server_xml_defaults,
merge=True)
%}
appX_server_xml:
file.serialize:
- name: /etc/tomcat/server.xml
- dataset: {{ server_xml_final_values | json() }}
- formatter: xml_badgerfish
file.serialize
状态可以为从数据结构创建某些文件提供捷径。 Salt公式中还有许多创建一次性“serializers”(通常称为Jinja macros)的示例,这些序列化器将数据结构重新格式化为特定的配置文件格式。 例如,查看Nginx vhosts
_ states 或 php.ini 文件模板。
如下所述,当对单个状态进行参数化时,可以通过将状态将使用的数据与执行工作的状态分开来重用单个状态。 这可能是部署应用程序X和应用程序Y之间的差异,也可能是生产和开发之间的差异。 例如:
/srv/salt/app/deploy.sls
:
{# Load the map file. #}
{% import_yaml 'app/defaults.yaml' as app_defaults %}
{# Extract the relevant subset for the app configured on the current
machine (configured via a grain in this example). #}
{% app = app_defaults.get(salt.grains.get('role')) %}
{# Allow values from Pillar to (optionally) update values from the lookup
table. #}
{% do app_defaults.update(salt.pillar.get('myapp', {})) %}
deploy_application:
git.latest:
- name: {{ app.repo_url }}
- version: {{ app.version }}
- target: {{ app.deploy_dir }}
myco/myapp/deployed:
event.send:
- data:
version: {{ app.version }}
- onchanges:
- git: deploy_application
/srv/salt/app/defaults.yaml
:
appX:
repo_url: [email protected]/myco/appX.git
target: /var/www/appX
version: master
appY:
repo_url: [email protected]/myco/appY.git
target: /var/www/appY
version: v1.2.3.4
公式中的每个sls文件都应努力做一件事情。 通过避免无关的任务耦合在一起,可以提高该文件的可重用性。
例如,基本的Apache公式应仅安装Apache httpd服务器并启动httpd服务。 这是安装Apache时的基本预期行为。 它不应执行其他更改,例如设置Apache配置文件或创建虚拟主机。
如果公式如上面的示例所示是单一用途的,则其他公式以及其他状态也可以include
该公式,并将其与 Requisites and Other Global State Arguments 参数一起使用,而又不包括不良或意外的副作用。
以下是可重用的Apache公式的最佳实践示例。 (为简便起见,这跳过了特定于平台的选项。有关更多信息,请参见完整的Apache公式)
# apache/init.sls
apache:
pkg.installed:
[...]
service.running:
[...]
# apache/mod_wsgi.sls
include:
- apache
mod_wsgi:
pkg.installed:
[...]
- require:
- pkg: apache
# apache/conf.sls
include:
- apache
apache_conf:
file.managed:
[...]
- watch_in:
- service: apache
为了说明一个不好的例子,说上面的Apache公式安装了Apache,并创建了一个默认的虚拟主机。 如果不安装不需要的默认虚拟主机,则mod_wsgi状态将无法包含Apache公式来创建该依赖关系树。
公式应该是可重用的。 避免将无关的动作耦合在一起。
参数化是Salt Formulas 以及Salt States的关键功能。参数化允许单个公式在多个操作系统之间重用;可以在生产、开发或staging环境中重用;并且可以被目标各异的许多人重复使用。
编写状态,指定顺序和依赖关系是花费最长的时间来编写和测试的部分。用users或程序包名称或文件位置等数据填写这些状态是容易的部分。有多少用户,这些用户的名字或文件的存放位置都是应该参数化的实现细节。状态与填充状态的数据之间的这种分隔创建了可重用的公式。
在下面的示例中,填充状态的数据可以来自任何地方-可以在状态top file进行硬编码,可以来自外部文件,可以来自Pillar,也可以来自执行函数。调用,也可以来自数据库查询。无论数据来自何处,状态本身都不会改变。生产数据与开发数据会有所不同,一家公司与另一家公司的数据也会有所不同,但是状态本身保持不变。
{% set user_list = [
{'name': 'larry', 'shell': 'bash'},
{'name': 'curly', 'shell': 'bash'},
{'name': 'moe', 'shell': 'zsh'},
] %}
{# or #}
{% set user_list = salt['pillar.get']('user_list') %}
{# or #}
{% load_json "default_users.json" as user_list %}
{# or #}
{% set user_list = salt['acme_utils.get_user_list']() %}
{% for user in list_list %}
{{ user.name }}:
user.present:
- name: {{ user.name }}
- shell: {{ user.shell }}
{% endfor %}
公式应努力使用基础平台的默认值,然后使用上游项目的默认值,然后使用公式本身的合理默认值。
例如,安装Apache的公式不应更改OS软件包安装的默认Apache配置文件。 但是,Apache公式应包含用于更改或覆盖默认配置文件的状态。
Pillar查找必须使用安全的get()
并且必须提供默认值。 使用Jinja set集合构造来创建局部变量,以提高可读性并避免在大型状态树上潜在地进行数百或数千个函数调用。
{% from "apache/map.jinja" import apache with context %}
{% set settings = salt['pillar.get']('apache', {}) %}
mod_status:
file.managed:
- name: {{ apache.conf_dir }}
- source: {{ settings.get('mod_status_conf', 'salt://apache/mod_status.conf') }}
- template: {{ settings.get('template_engine', 'jinja') }}
公式中使用的任何默认值也必须记录在存储库根目录的pillar.example
文件中。 应该自由地使用注释来解释每个配置值的意图。 此外,用户应能够将此文件的内容复制并粘贴到自己的“pillar”中,以进行所需的更改。
请记住,状态文件和pillar文件都可以轻松调出Salt执行模块,也可以访问所有系统grains。
{% if '/storage' in salt['mount.active']() %}
/usr/local/etc/myfile.conf:
file:
- symlink
- target: /storage/myfile.conf
{% endif %}
不鼓励使用Jinja macros封装逻辑或条件,而建议使用Python编写自定义执行模块。
一个基本的 Formula repository 应该包含以下的目录结构:
foo-formula
|-- foo/
| |-- map.jinja
| |-- init.sls
| `-- bar.sls
|-- CHANGELOG.rst
|-- LICENSE
|-- pillar.example
|-- README.rst
`-- VERSION
参见 template-formula
templateformula 存储库具有预先构建的布局,该布局用作新公式存储库的基本结构。 只需从那里复制文件并进行编辑。
README 文件应详细解释每个可用的.sls
文件的作用,是否对其他公式有依赖性,是否具有目标平台以及任何其他安装或使用说明或技巧。
README.rst文件的样本框架:
===
foo
===
Install and configure the FOO service.
**NOTE**
See the full `Salt Formulas installation and usage instructions
`_.
Available states
================
.. contents::
:local:
``foo``
-------
Install the ``foo`` package and enable the service.
``foo.bar``
-----------
Install the ``bar`` package.
CHANGELOG.rst
文件应详细列出各个版本,其发布日期以及每个版本的一组项目符号,以突出显示公式的给定版本中的总体更改。
CHANGELOG.rst文件的样例:
CHANGELOG.rst
:
foo formula
===========
0.0.2 (2013-01-01)
- Re-organized formula file layout
- Fixed filename used for upstart logger template
- Allow for pillar message to have default if none specified
公式根据语义版本进行版本控制, http://semver.org/.
注意
给定版本号MAJOR.MINOR.PATCH,增加:
当您进行不兼容的API更改时的MAJOR版本,
以向后兼容的方式添加功能时的MINOR版本,并且
进行向后兼容的错误修复时的PATCH版本。
预发布和构建元数据的其他标签可作为MAJOR.MINOR.PATCH格式的扩展名使用。
使用Git标签以及公式存储库中的VERSION文件来跟踪公式版本。 VERSION文件应包含特定公式的当前发布版本。
可以使用state.show_sls
函数对无效的Jinja,无效的YAML或无效的Salt状态结构进行冒烟测试:
salt '*' state.show_sls apache
然后可以通过state.apply
运行每个.sls
文件并检查输出中公式中每个状态的成功或失败,来测试Salt公式。 应该针对每个受支持的平台执行此操作。