SaltStack Formula是什麼?它是預先編寫的Salt States狀態,是社區分享的狀態配置模板資源

SaltStack Formula是什麼?

公式是預先編寫的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

Installation

每個Salt Formula是一個單獨的Git存儲庫,被設計爲現有的Salt State樹的一個插件。 可以通過以下方式安裝公式資源。

Adding a Formula as a GitFS remote

Salt的GitFS文件服務器後端的一個設計目標是促進可重用的狀態。 GitFS是使用公式的一種快速而自然的方法。

  1. 安裝任何必要的依賴項並配置GitFS
  2. 在Salt Master配置文件的gitfs_remotes列表中,添加一個或多個Formula庫URL作爲遠程對象:
  gitfs_remotes:
    - https://github.com/saltstack-formulas/apache-formula
    - https://github.com/saltstack-formulas/memcached-formula

強烈建議將公式存儲庫forking到您自己的GitHub帳戶中,以避免對基礎結構進行了意外的更改。

許多Salt Formulas都是高度活躍的存儲庫,因此請謹慎地進行新的更改。 加上您對fork所做的任何添加,都可以通過快速pull request輕鬆地將其發送回上游!

  1. 重新啓動 Salt master 服務。

從2018.3.0版本開始,將公式與GitFS一起使用相對使用許多不同文件服務器環境(即saltenvs)的部署會更加方便。 使用all_saltenvs參數時,來自單個git branch/tag的文件將出現在所有Salt環境中。 有關此功能的更多信息,請參見 此處

Adding a Formula directory manually

公式只是目錄,可以通過使用Git克隆存儲庫或通過下載和擴展存儲庫的tarball或zip文件將其複製到本地文件系統中。 目錄結構旨在與Saltmaster配置中的 file_roots 一起使用。

  1. 將存儲庫克隆或下載到目錄中:
  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
  1. 將新的路徑添加到 file_roots 定義中:
  file_roots:
    base:
      - /srv/salt
      - /srv/formulas/apache-formula
  1. 重新啓動 Salt master 服務。

Usage

每個公式都可以立即使用默認值而無需任何其他配置。 通過在Pillar中包含數據,還可以配置許多公式。 有關可用選項,請參見每個公式存儲庫中的pillar.example文件。

Including a Formula in an existing State tree

公式可能包含在現有的sls文件中。 當您正在編寫的狀態需要要求或擴展公式中定義的狀態時,這通常很有用。

這是在require聲明中使用epel-formula的狀態示例,該狀態指示Salt直到還安裝了EPEL存儲庫後才安裝python26軟件包:

include:
  - epel

python26:
  pkg.installed:
    - require:
      - pkg: epel

Including a Formula from a Top File

某些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

Configuring Formula using Pillar

Salt Formulas無需額外配置即可直接使用。 但是,許多Formula支持通過Pillar進行其他配置和自定義。 可用選項的示例可以在每個公式存儲庫的根目錄中的名爲pillar.example的文件中找到。

Using Formula with your own states

請記住,公式是常規的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

請不要拒絕去閱讀每個公式的源代碼!

Reporting problems & making additions

每個公式都是在GitHub上的單獨存儲庫。 如果您遇到公式錯誤,請在各自的存儲庫中提交問題! 作爲pull request請求發送修訂和補充。 向存儲庫Wiki添加提示和技巧。

Writing Formulas

每個公式都是GitHub上的saltstack-formulas組織中的單獨存儲庫。

Get involved creating new 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”自行合併您自己的拉取請求。

Style

可維護性,可讀性和可重用性都是良好的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') }}

Use a descriptive State ID

狀態的ID用作唯一標識符,可以在必要時通過其他狀態進行引用。 它在整個狀態樹中必須是唯一的(畢竟,它是字典中的鍵)。

另外,狀態ID應該是描述性的,並作爲其將執行、管理或更改的高級提示信息。 例如,deploy_webappapachereload_firewall

Use module.function notation

引用狀態模塊和狀態函數時,最好使用所謂的“短聲明”符號。 它提供了在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”轉換爲較長的格式。

Specify the name parameter

對狀態ID使用唯一的永久標識符,對具有可變性的數據使用保留的 name 參數。

名稱聲明是所有狀態功能函數的必需參數。 如果未在狀態中明確設置狀態名稱,則它將state ID隱式用作name名稱。

在許多狀態函數中,name參數用於變化的數據,例如特定於OS的軟件包名稱,特定於OS的文件系統路徑,存儲庫地址等。每當狀態ID更改時,也必須更改對該ID的所有引用。 在需要將狀態寫成面向未來的狀態時,請使用永久性ID,以便在將來進行重構。

Comment state files

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() %} #}

Easy on the Jinja!

Jinja模板在構建Salt sls文件時提供了極大的靈活性和功能。 它還可能導致邏輯和數據無法維持的困境。 概括地說,Jinja是最好與states分開(儘可能多)的地方。

以下是如何有效使用Jinja的指南和示例。

Know the evaluation and execution order

編寫狀態時,有關如何編譯和運行Salt狀態的高級知識非常有用。

Salt中的默認渲染器設置是通過管道傳輸到YAML的Jinja。每個步驟都是單獨的步驟。每個步驟都不知道上一個或下一個步驟。 Jinja不瞭解YAML,YAML不瞭解Jinja;他們不能共享變量或進行交互。

  • 無論Jinja步驟產生什麼,都必須是有效的YAML。
  • 無論YAML步驟產生什麼,都必須是有效的Highstate高級狀態數據結構。 (Salt中任何其他渲染器的最後一步也是如此。)
  • Highstate 高級狀態可以被認爲是人類友好的數據結構。易於編寫和易於閱讀。
  • Salt的狀態編譯器會驗證高級狀態並將其編譯爲低級狀態。
  • Lowstate 低級狀態可以認爲是機器友好的數據結構。這是字典的列表,每個字典都直接映射到一個函數調用。
  • Salt的狀態系統最終啓動並在處於低級狀態的每個“chunk”上執行。請記住,requisites 必備條件是在運行時評估的。
  • 每個函數調用的返回值將添加到“running”字典中,該字典是狀態運行結束時的最終輸出。

完整的評估和執行順序如下:

Jinja -> YAML -> Highstate -> low state -> execution

Avoid changing the underlying system with Jinja

避免從Jinja調用更改底層系統的命令。 通過Jinja運行的命令不遵守Salt的dry-run模式(test=True)! 除非運行的命令也是冪等的,否則這通常與Salt狀態的冪等性衝突。

Inspect the local system

在Salt States中,Jinja的常見用法是收集有關基礎系統的信息。 Jinja上下文中提供的grains字典是Salt本身已經收集的常見數據點的一個很好的例子。 而關於本地系統屬性的一些不太常見的值通常是通過運行命令找到的。 例如:

{% set is_selinux_enabled = salt.cmd.run('sestatus') == '1' %}

通常最好通過變量分配來完成,以便將數據與將使用數據的狀態分開。

Gather external data

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 %}

通常最好通過變量分配來完成,以便將數據與將使用數據的狀態分開。

Light conditionals and looping

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條件替換爲函數調用,以更簡潔地過濾正確的數據。 但是,狀態本身是簡單易讀的,數據是獨立的,也是簡單易讀的。 那些建議的改進可以在將來的某個日期完成,而無需改變狀態!

Avoid heavy logic and programming

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)},
            ],
        },
    }

Jinja Macros

在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) }}

Abstracting static defaults into a lookup table

狀態使用的數據與狀態本身分開,以增加狀態的靈活性和可重用性。

一個明顯且常見的示例是特定於平臺的程序包名稱和文件系統路徑。另一個示例是應用程序的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 }}

Organizing Pillar data

最佳做法是使formulas 公式將所有與公式相關的參數放在第二級lookup關鍵字下,位於指定用於保存特定service/software/等數據的主要命名空間中,該名稱空間由公式管理:

mysql:
  lookup:
    version: 5.7.11

Collecting common values

可以將公共值收集到一個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') %}

Overriding values in the lookup table

允許覆蓋查詢表中的靜態值。 這是一個簡單的模式,再次增加了狀態文件的靈活性和可重用性。

filter_by中的merge參數指定Pillar中詞典的位置,該詞典可用於覆蓋從查找表返回的值。 如果該值存在於“Pillar”中,則將具有優先權。

當軟件或配置文件安裝在非標準位置或不支持的平臺上時,此功能很有用。 例如,以下Pillar將替換上面調用中的config值。

mysql:
  lookup:
    config: /usr/local/etc/mysql/my.cnf

用特殊字符保護內容的擴展

進行模板製作時,請記住,YAML確實具有用於引用、流處理以及其他特殊結構和內容的特殊字符。 當Jinja替換項可能包含特殊字符時,YAML將無法正確解析這些特殊字符。 使用yaml_encodeyaml_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')) %}

When to use lookup tables

map.jinja文件只是Salt Formulas中的一個約定。 這種更大的模式對於各種工作流程中的各種數據很有用。 此模式不限於從單個文件或數據源提取數據。 例如,這種模式在States、 Pillar 和 Reactor中很有用。

使用數據結構而不是配置文件,可以從多個源(本地文件,遠程Pillar,數據庫查詢等)將數據拼湊在一起,進行合併,覆蓋和搜索。

以下是一些查找表可能有用以及如何使用和表示的示例。

Platform-specific information

Salt Formulas 公式中一個很明顯的模式(在Salt公式中經常使用)是在名爲map.jinja的文件中提取特定於平臺的信息,例如程序包名稱和文件系統路徑。 該模式已在上面詳細說明。

Sane defaults

應用程序設置非常適合使用此模式。 將默認設置與狀態本身一起存儲,並在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 文件模板。

Environment specific information

如下所述,當對單個狀態進行參數化時,可以通過將狀態將使用的數據與執行工作的狀態分開來重用單個狀態。 這可能是部署應用程序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

Single-purpose SLS files

公式中的每個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公式來創建該依賴關係樹。

公式應該是可重用的。 避免將無關的動作耦合在一起。

Parameterization

參數化是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 %}

Configuration

公式應努力使用基礎平臺的默認值,然後使用上游項目的默認值,然後使用公式本身的合理默認值。

例如,安裝Apache的公式不應更改OS軟件包安裝的默認Apache配置文件。 但是,Apache公式應包含用於更改或覆蓋默認配置文件的狀態。

Pillar overrides

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”中,以進行所需的更改。

Scripting

請記住,狀態文件和pillar文件都可以輕鬆調出Salt執行模塊,也可以訪問所有系統grains。

{% if '/storage' in salt['mount.active']() %}
/usr/local/etc/myfile.conf:
  file:
    - symlink
    - target: /storage/myfile.conf
{% endif %}

不鼓勵使用Jinja macros封裝邏輯或條件,而建議使用Python編寫自定義執行模塊。

Repository structure

一個基本的 Formula repository 應該包含以下的目錄結構:

foo-formula
|-- foo/
|   |-- map.jinja
|   |-- init.sls
|   `-- bar.sls
|-- CHANGELOG.rst
|-- LICENSE
|-- pillar.example
|-- README.rst
`-- VERSION

參見 template-formula

templateformula 存儲庫具有預先構建的佈局,該佈局用作新公式存儲庫的基本結構。 只需從那裏複製文件並進行編輯。

README.rst

README 文件應詳細解釋每個可用的.sls文件的作用,是否對其他公式有依賴性,是否具有目標平臺以及任何其他安裝或使用說明或技巧。

README.rst文件的樣本框架:

===
foo
===

Install and configure the FOO service.

**NOTE**

See the full `Salt Formulas installation and usage instructions
<https://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html>`_.

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文件的樣例:

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

Versioning

公式根據語義版本進行版本控制, http://semver.org/.

注意

給定版本號MAJOR.MINOR.PATCH,增加:

  1. 當您進行不兼容的API更改時的MAJOR版本,

  2. 以向後兼容的方式添加功能時的MINOR版本,並且

  3. 進行向後兼容的錯誤修復時的PATCH版本。

預發佈和構建元數據的其他標籤可作爲MAJOR.MINOR.PATCH格式的擴展名使用。

使用Git標籤以及公式存儲庫中的VERSION文件來跟蹤公式版本。 VERSION文件應包含特定公式的當前發佈版本。

Testing Formulas

可以使用state.show_sls函數對無效的Jinja,無效的YAML或無效的Salt狀態結構進行冒煙測試:

salt '*' state.show_sls apache

然後可以通過state.apply運行每個.sls文件並檢查輸出中公式中每個狀態的成功或失敗,來測試Salt公式。 應該針對每個受支持的平臺執行此操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章