SaltStack State Modules - 狀態模塊開發技巧

狀態模塊是映射到Salt狀態的實際執行和管理的組件。

您也可以參考在Github上維護的這一份技術資料:State Modules

States are Easy to Write! - 開發一個狀態模塊很容易

狀態模塊易於編寫且簡單明瞭。 傳遞到SLS數據結構的信息將直接映射到狀態模塊。

從SLS數據映射信息很簡單,此示例可以說明:

/etc/salt/master: # maps to "name", unless a "name" argument is specified below
  file.managed: # maps to <filename>.<function> - e.g. "managed" in https://github.com/saltstack/salt/tree/develop/salt/states/file.py
    - user: root # one of many options passed to the manage function
    - group: root
    - mode: 644
    - source: salt://salt/master

因此,此SLS數據可以直接鏈接到模塊、函數以及傳遞給該函數的參數。

這確實帶來了一點負擔,因爲函數名稱、狀態名稱和函數參數直接定義用戶接口,因此它們在狀態模塊內部應該非常易於閱讀。

Keyword Arguments - 關鍵字參數

Salt在編譯、渲染狀態時將許多關鍵字參數傳遞給狀態,包括環境、狀態的唯一標識符等。 此外,請記住,狀態的必要條件是關鍵字參數的一部分。 因此,如果您需要在狀態中遍歷關鍵字參數,則必須適當考慮和處理這些參數。 一個這樣的示例是關於pkgrepo.managed狀態的使用,該狀態需要能夠處理任意關鍵字參數並將其傳遞給模塊執行功能。 可以在此處找到如何處理這些關鍵字參數的示例。

Best Practices - 最佳實踐

編寫良好的狀態函數將遵循以下步驟:

注意:這是一個極其簡化的示例。 可以通過瀏覽Salt狀態模塊的源代碼以查看其他示例。

  1. 設置返回數據字典並執行任何必要的輸入驗證(類型檢查,尋找互斥參數的使用等)。

    ret = {'name': name,
           'result': False,
           'changes': {},
           'comment': ''}
    
    if foo and bar:
        ret['comment'] = 'Only one of foo and bar is permitted'
        return ret
    
  2. 檢查是否需要進行更改。 最好通過附帶的執行模塊中的信息收集功能來完成此操作。 該狀態應該能夠使用該函數的返回值來判斷該minion是否已經處於所需狀態。

    result = __salt__['modname.check'](name)
    
  3. 如果步驟2發現minion已經處於所需狀態,則立即退出,並返回True結果,而無需進行任何更改。

    if result:
        ret['result'] = True
        ret['comment'] = '{0} is already installed'.format(name)
        return ret
    
  4. 如果步驟2發現確實需要進行更改,請檢查狀態是否在測試模式下運行(即使用test=True)。 如果是這樣,則退出並返回值爲“None”的結果、相關注釋和(如有可能)將進行哪些更改的描述。

    if __opts__['test']:
        ret['result'] = None
        ret['comment'] = '{0} would be installed'.format(name)
        ret['changes'] = result
        return ret
    
  5. 執行所需的更改。 應該再次使用附帶的執行模塊中的函數來完成此操作。 如果該函數的結果足以告訴您是否發生了錯誤,則可以退出並返回False結果和相關注釋以說明發生了什麼。

    result = __salt__['modname.install'](name)
    
  6. 再次從步驟2執行相同的檢查,以確認minion是否處於所需狀態。 就像在第2步中一樣,此函數應該能夠通過其返回數據來告訴您是否需要進行更改。

    ret['changes'] = __salt__['modname.check'](name)
    

    如您所見,我們將返回字典中的changes鍵設置爲modname.check函數的結果(就像我們在步驟4中所做的一樣)。 這裏的假設是,信息收集功能將返回一個字典,解釋需要進行哪些更改。 這可能適合您的用例,也可能不適合。

  7. 設置返回數據並返回!

    if ret['changes']:
        ret['comment'] = '{0} failed to install'.format(name)
    else:
        ret['result'] = True
        ret['comment'] = '{0} was installed'.format(name)
    
    return ret
    

Using Custom State Modules - 使用自定義的State模塊

在使用狀態模塊之前,必須將其分發給各minions。 可以通過將它們放入salt://_states/中來完成。 然後可以通過運行saltutil.sync_statessaltutil.sync_all將它們手動分發給minions。 或者,在運行highstate狀態時,自定義狀態類型將自動同步。

注意:使用用文件名中寫帶連字符的狀態模塊會導致!pyobjects例程出現問題。 請堅持使用下劃線的最佳做法。

任何已與minions同步的自定義狀態(與Salt的默認狀態集中的名稱之一相同的)將替換具有相同名稱的默認狀態。 請注意,狀態模塊的名稱根據其文件名默認爲同一個(即foo.py變爲狀態模塊foo),但是可以使用virtual函數覆蓋其名稱。

Cross Calling Execution Modules from States - 在States狀態文件中交叉調用執行模塊

與執行模塊一樣,狀態模塊也可以使用__salt____grains__數據。 請參閱[交叉調用執行模塊](https://github.com/watermelonbig/SaltStack-Chinese-ManualBook/blob/master/chapter06/06-3.Writing-Execution-Modules.md#Cross-Calling-Execution Modules—Salt執行模塊的交叉調用)。

重要的是要注意,除非需要,否則不應在狀態模塊中完成狀態管理的實際工作。 pkg狀態模塊就是一個很好的例子。 該模塊不執行任何軟件包管理工作,僅調用pkg執行模塊。 這使得pkg狀態模塊完全通用,這就是爲什麼只有一個pkg狀態模塊和許多後端pkg執行模塊的原因。

另一方面,某些模塊將要求將處理邏輯放置在狀態模塊中,文件模塊就是一個很好的例子。 但是在大多數情況下,這不是最佳方法,編寫特定的執行模塊來執行後端工作將是最佳解決方案。

Cross Calling State Modules - 在States狀態文件中交叉調用狀態模塊

所有的Salt狀態模塊對彼此可用,並且狀態模塊可以調用其他狀態模塊中可用的功能。

將變量__states__加載到模塊中後,將其包裝到模塊中。

__states__變量是一個包含所有狀態模塊的Python字典。 字典的key是代表模塊名稱的字符串,值是函數本身。

可以通過訪問__states__ dict中的值來交叉調用Salt狀態模塊:

ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile')

此代碼將在file狀態模塊中調用managed函數,並將參數名稱和源傳遞給它。

Return Data - 返回數據

一個狀態模塊必須返回包含以下鍵/值數據的字典:

  • name:與傳遞給狀態的name參數值相同。
  • changes:一個描述變更的字典。 每個被更改的事物都應該是一個鍵,其值則作爲另一個字典,其中包含舊/新值的鍵稱爲“old”和“new”。 例如,pkg狀態的changes字典對每個更改的軟件包都有一個鍵,其子字典中的“old”和“new”鍵包含該軟件包的舊版本和新版本。 例如,此方案的最終更改字典如下所示:
    ret['changes'].update({'my_pkg_name': {'old': '',
                                           'new': 'my_pkg_name-1.0'}})
    
  • result: 取值爲三個值之一。 如果操作成功,則爲True;否則爲False;如果狀態以測試模式運行,則爲None,即test=True;如果狀態未以測試模式運行,則將進行更改。
live mode test mode
no changes True True
successful changes True None
failed changes False False or None

注意:測試模式無法預測更改是否成功,因此未決更改的結果通常爲“None”。

但是,如果狀態將失敗並且可以在測試模式下確定而不應用更改,則可以返回False。

  • comment:字符串列表或總結結果的單個字符串。 請注意,自Salt 2018.3.0起已支持字符串列表。 字符串列表將與換行符一起形成最終註釋; 這對於允許來自一個狀態子部分的多個註釋很有用。 最好保持行適當長短(可根據需要使用多行),並以標點符號(例如句點)結尾以界定多個註釋。

注意:States不應返回無法序列化的數據,例如frozensets。

Test State - 測試狀態模塊

所有States都應檢查並支持通過選項的測試。 這將返回有關如果實際運行狀態會發生什麼變化的數據。 此類檢查的示例如下所示:

# Return comment of changes if test.
if __opts__['test']:
    ret['result'] = None
    ret['comment'] = 'State Foo will execute with param {0}'.format(bar)
    return ret

在對minions執行任何實際操作之前,請確保進行了測試並得到返回數據。

注意:編寫測試支持時,請務必參考上面列出的result表並顯示任何可能的更改。 尋找狀態變化對於test=true功能至關重要。 如果使用test=true預測狀態沒有變化(或在配置文件中test:true),則最終狀態的結果不應爲None

Watcher Function - watcher監視函數

如果寫入的狀態應支持watch監視條件,則需要聲明一個watcher監視程序的功能函數。 每當調用watch監視條件時,都會調用watcher監視程序函數,並且監視程序功能應對狀態本身的行爲通用。

watcher函數應接受正常狀態函數接受的所有選項(因爲它們將傳遞到watcher函數中)。

watcher函數通常用於執行特定於狀態的反應行爲,例如,服務模塊的watch監視程序重新啓動指定的服務,並使監視程序使服務對環境的變化做出反應。

watcher函數還需要返回與正常狀態函數返回的數據相同的數據。

Mod_init Interface - 初始化接口

某些states只需執行一次操作即可確保已建立環境,或者可以預定義該state行爲的全局某些條件。這是mod_init接口的領域。

狀態模塊可以具有一個名爲mod_init的函數,該函數在調用此類型的第一個狀態時執行。創建此接口主要是爲了改善pkg狀態。安裝軟件包時,需要刷新軟件包元數據,但是每次安裝軟件包時刷新軟件包元數據都是浪費的。 pkg狀態的mod_init函數將標誌設置爲down,以便第一次(只有第一次)軟件包安裝嘗試才能刷新軟件包數據庫(當然,可以在pkg狀態下通過refresh選項手動調用軟件包數據庫進行刷新) 。

mod_init函數必須接受給定執行狀態的Low State Data狀態數據作爲參數。低狀態數據是一個字典,可以通過執行state.show_lowstate函數看到。然後,mod_init函數必須返回布爾值。如果返回值爲True,則不會再次執行mod_init函數,這意味着已設置所需的行爲。否則,如果mod_init函數返回False,則下次調用該函數。

pkg狀態模塊中可以找到mod_init函數的一個很好的例子:

def mod_init(low):
    '''
    Refresh the package database here so that it only needs to happen once
    '''
    if low['fun'] == 'installed' or low['fun'] == 'latest':
        rtag = __gen_rtag()
        if not os.path.exists(rtag):
            open(rtag, 'w+').write('')
        return True
    else:
        return False

pkg狀態的mod_init函數將低狀態數據接受爲low,然後檢查被調用的函數是否要安裝軟件包,如果該函數不打算安裝軟件包,則無需刷新軟件包數據庫。 因此,如果軟件包數據庫準備刷新,則返回True且下次評估pkg狀態時不會調用mod_init,否則返回False且下次評估pkg狀態時將調用mod_init

Log Output - 日誌輸出

您可以在自定義模塊中調用logger記錄器,以將消息寫入minion的日誌。 以下代碼片段演示瞭如何編寫日誌消息:

import logging

log = logging.getLogger(__name__)

log.info('Here is Some Information')
log.warning('You Should Not Do That')
log.error('It Is Busted')

Strings and Unicode

狀態模塊的作者應始終假定輸入模塊的字符串已經從字符串解碼爲Unicode。 在Python 2中,它們的類型爲“Unicode”類型,而在Python 3中,它們的類型爲str。 從狀態到其他Salt子系統(例如執行模塊)的調用應傳遞Unicode(如果傳遞二進制數據,則傳遞字節)。 在極少數情況下,狀態需要直接寫入磁盤,應該在寫入磁盤之前立即將Unicode編碼爲字符串。 作者可以使用__salt_system_encoding__來學習系統的編碼類型。 例如,’ my_string’.encode(’__ salt_system_encoding__’)。

Full State Module Example - 一個完整的狀態模塊的使用示例

以下是完整狀態模塊和功能的簡化示例。 記住要通過調用執行模塊來執行所有實際工作。 狀態模塊應僅執行“之前”和“之後”檢查。

  1. 通過將代碼放入以下路徑的文件中來創建自定義狀態模塊:/srv/salt/_states/my_custom_state.py
  2. 將自定義狀態模塊分發到各minions:
    salt '*' saltutil.sync_states
    
  3. 創建一個新的狀態文件(例如/srv/salt/my_custom_state.sls),調用自定義的狀態。
  4. 將以下SLS配置添加到在步驟3中創建的文件中:
    human_friendly_state_id:        # An arbitrary state ID declaration.
      my_custom_state:              # The custom state module name.
        - enforce_custom_thing      # The function in the custom state module.
        - name: a_value             # Maps to the ``name`` parameter in the custom function.
        - foo: Foo                  # Specify the required ``foo`` parameter.
        - bar: False                # Override the default value for the ``bar`` parameter.
    

Example state module - 狀態模塊例子

import salt.exceptions

def enforce_custom_thing(name, foo, bar=True):
    '''
    Enforce the state of a custom thing

    This state module does a custom thing. It calls out to the execution module
    ``my_custom_module`` in order to check the current system and perform any
    needed changes.

    name
        The thing to do something to
    foo
        A required argument
    bar : True
        An argument with a default value
    '''
    ret = {
        'name': name,
        'changes': {},
        'result': False,
        'comment': '',
        }

    # Start with basic error-checking. Do all the passed parameters make sense
    # and agree with each-other?
    if bar == True and foo.startswith('Foo'):
        raise salt.exceptions.SaltInvocationError(
            'Argument "foo" cannot start with "Foo" if argument "bar" is True.')

    # Check the current state of the system. Does anything need to change?
    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

    # The state of the system does need to be changed. Check if we're running
    # in ``test=true`` mode.
    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',
        }

        # Return ``None`` when running with ``test=true``.
        ret['result'] = None

        return ret

    # Finally, make the actual change and return the result.
    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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章