返回文章列表

Ansible Playbook 引數化技巧與自定義模組開發

本文探討 Ansible Playbook 的引數化技巧,包含 vars_prompt、vars_files、角色變數、區塊變數、任務變數和額外變數等用法,同時講解如何利用 Jinja2 篩選器操作變數。此外,文章也涵蓋 Ansible 的事實收集機制、Fact 快取、hostvars 以及自定義 Fact

DevOps 自動化

Ansible Playbook 的引數化設計,讓使用者能更彈性地管理複雜的自動化任務。透過變數的運用,可以根據不同的環境和需求調整 Playbook 的執行邏輯,提升程式碼的可重複使用性並減少冗餘。事實收集和快取機制則能有效提升 Playbook 執行效率,同時 hostvars 提供了跨主機資訊存取的便利性。理解 Ansible 的變數哲學和 Jinja2 篩選器的使用,能更有效地操作和運用收集到的資料。最後,開發自定義模組則能針對特殊需求擴充套件 Ansible 的功能,例如透過 RPC API 與 WordPress 等第三方服務互動,展現 Ansible 的高度擴充套件性和客製化能力。

第5章:引數化Playbook

動態組態與變數使用

在Ansible中,引數化Playbook是實作動態組態的關鍵。透過使用變數(variables),可以使Playbook更加靈活且可重複使用。

使用vars_promptvars_files

vars_prompt允許在執行Playbook時提示使用者輸入變數值,而vars_files則允許從檔案中載入變數。例如:

---
- hosts: all
  vars_prompt:
    - name: "username"
      prompt: "Enter username"
      private: no
  vars_files:
    - "{{ username }}.yml"
    - default_user.yml
  tasks:
    - debug: msg="Hello {{ username }} from {{ location }}"

若使用者輸入“john”,而john.yml不存在,則會使用default_user.yml中的變數。

角色變數(Role Variables)

在Playbook中使用角色(roles)時,可以為角色指定特定的變數。例如:

---
- hosts: all
  become: true
  roles:
    - role: mheap.wordpress
      database_name: michaelwp
      database_user: michaelwp
      database_password: bananas18374

區塊變數(Block Variables)

區塊(block)允許將多個任務分組並應用相同的設定。例如:

---
- hosts: all
  tasks:
    - block:
        - apt: name=apache2 state=installed
        - copy: content="Example File" dest=/var/www/hello.html
      become: true
      when: do_something.rc == 0

也可以在區塊中定義變數:

---
- hosts: all
  tasks:
    - block:
        - debug: msg="Hello {{ your_name }}"
        - debug: msg="How are you {{ your_name }}?"
      vars:
        your_name: Michael

任務變數(Task Variables)

可以在特定任務中定義變數:

---
- hosts: all
  tasks:
    - debug: msg="Hello {{ your_name }}"
      vars:
        your_name: Michael

這對於減少重複程式碼很有幫助,例如:

---
- hosts: all
  tasks:
    - template: src=webserver.conf dest="/etc/{{ name }}/{{ name }}.conf"
      vars:
        name: apache2

額外變數(Extra Variables)

額外變數可以在執行Playbook時透過命令列指定,具有最高優先順序。例如:

ansible-playbook –i /path/to/inventory playbook.yml –e 'your_name=Fred'

也可以使用JSON格式或從檔案中讀取額外變數。

蒐集事實(Gathering Facts)

Ansible在執行Playbook前會使用setup模組蒐集目標主機的事實(facts)。這些事實可以作為變數在Playbook中使用。例如,可以使用ansible_memfree_mb來檢查目標主機的可用記憶體。

停用事實蒐集

如果不需要事實,可以在Playbook中設定gather_facts: false來停用事實蒐集:

---
- hosts: all
  gather_facts: false
  tasks:
    - debug: msg="Hello Michael"

自定義事實(Facts.d)

可以建立自定義事實檔案(以.fact結尾),放置在/etc/ansible/facts.d/目錄下,Ansible會讀取這些檔案並將其內容作為事實提供。例如:

/etc/ansible/facts.d/users.fact

[michael]
use_colours=1
passwordless_sudo=0

這些自定義事實可以在Playbook中透過ansible_local變數存取。

引數化 Playbook

Fact 快取

若在 Playbook 中使用 Fact,但不希望每次執行 Playbook 都重新收集 Fact,可以使用 Fact 快取來加速。需要在 ansible.cfg 中明確啟用 Fact 快取,並指定使用 Redis 或 JSON 檔案作為資料儲存。

啟用檔案型快取

ansible.cfg 中加入以下設定:

[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /path/to/cachedir
fact_caching_timeout = 86400

這將設定 Fact 收集機制為 smart,檢查 Fact 快取後再收集 Fact。然後,啟用 JSON 檔案快取,指定儲存檔案的路徑,並設定快取檔案的有效期為 86400 秒(1 天)。

hostvars

hostvars 是一個特殊的變數,用於儲存其他主機的資訊。這使得您可以查詢其他主機的詳細資訊,以幫助決定在 Playbook 中要執行的動作。例如,您可以查詢某台主機的內部 IP 位址:

hostvars['database.example.com']['ansible_eth0']['ipv4']['address']

hostvars 會在 Ansible 存取某台主機時填入相關資訊,這意味著您只能存取已經存取過的主機的 Fact。若需要所有主機的 Fact,可以啟用主機快取並執行一個 Playbook,每天登入每台主機並收集 Fact。

使用變數

Ansible 中的變數由 Jinja2 樣板引擎管理。Jinja2 提供變數替換,使用 {{double_brace_syntax}},但它的功能遠不止於此。它還提供數十種資料操作工具,稱為篩選器。

Jinja2 篩選器

Jinja2 內建超過 45 種篩選器。雖然大多數篩選器很少使用,但某些核心篩選器,如 mapreplacerejectattrselectattrfirstlast,在開始使用傳回需要操作和使用的資料的模組(例如 Amazon AWS 模組)時非常有用。

例子:使用 selectattr 篩選器

假設您有一個公司所有員工的列表,但只想為工程部門的員工提供機器帳號。以下 Playbook 使用 Jinja2 的 selectattr 篩選器將列表縮小到工程部門的員工:

---
- hosts: all
  vars:
    users:
      - name: Michael
        department: Engineering
      - name: John
        department: Engineering
      - name: Peter
        department: Finance
  tasks:
    - user: name="{{ item.name }}" groups=developers append=yes
      with_items: "{{ users | selectattr('department', 'equalto', 'Engineering') | list }}"

變數 users 可以來自任何地方,不一定要在 Playbook 中硬編碼。您可以編寫一個指令碼生成使用者列表,並與 vars_files 結合使用,或透過命令列使用 -e 標誌傳遞額外引數。

Ansible 的變數哲學

您可以在 Ansible Playbook 的多個層級定義變數,但 Ansible 的哲學是變數通常只應定義一次。您需要考慮變數應該放在哪裡,而不是應該在哪裡覆寫它。

編寫自定義模組

關於 Ansible 模組

Ansible 的核心模組都是用 Python 開發的。它們分為兩組:ansible-modules-coreansible-modules-extras

ansible-modules-core

ansible-modules-core 包含所有與 Ansible 一起發布的核心模組,如 apttemplatecopy。這些模組非常穩定,並由 Ansible 核心團隊維護。

ansible-modules-extras

其餘的模組儲存在 ansible-modules-extras 中。雖然它們與 Ansible 一起發布,但實際上是由社群維護的。extras 倉函式庫包含如 debconfbundlerpagerduty 等模組。在為 Ansible 做貢獻時,如果您的變更影響到 extras 模組,則由該模組的維護者負責審查您的變更,而不是 Ansible 核心團隊。

建立 wp_user 模組

本章將建立一個 wp_user 模組,用於透過 WordPress 的 RPC API 更新 WordPress 安裝中的使用者詳細資訊。這個例子將展示如何使用 Ansible 更新 WordPress 使用者。

撰寫自訂 Ansible 模組

在開始撰寫自訂 Ansible 模組之前,需要進行一些環境設定以便在撰寫過程中測試模組。這些工作與前幾章的內容無關,因此需要建立一個新的資料夾來進行工作。

環境設定

首先,建立一個新的資料夾並進入該資料夾:

mkdir ansible-module
cd ansible-module

接著,從 Github 複製 Ansible 儲存函式庫,並執行一個設定環境的指令碼:

git clone git://github.com/ansible/ansible.git --recursive
source ansible/hacking/env-setup
chmod +x ansible/hacking/test-module

這個過程確保了在執行 ansible 命令時,使用的是下載的版本而非全域安裝的版本。同時,也使 ansible/hacking/test-module 成為可執行檔,用於測試自訂模組。

可能還需要安裝 Ansible 的 pyyamljinja2 相依性,可以透過 Python 套件管理器 pip 進行安裝:

pip install pyyaml jinja2

使用 Bash 撰寫模組

如果自訂模組很簡單,可以使用 Bash 撰寫。雖然大多數情況下 Python 是更好的選擇,但 Bash 仍然是撰寫簡單模組的好工具。這裡將建立一個模組,將指設定檔案的內容轉換為大寫。

首先,建立一個名為 file_upper 的檔案,並使其成為可執行檔:

touch file_upper
chmod +x file_upper

接著,在 file_upper 中新增以下內容,以傳回一個 JSON 編碼的字串:

#!/bin/bash
cat <<EOF
{"content":"Hello World"}
EOF

儲存後,執行 ansible/hacking/test-module -m file_upper 以測試模組。輸出結果應該如下:

{
  "content": "Hello World"
}

這個結果表明已經成功撰寫並測試了第一個 Ansible 模組。

內容解密:

  1. cat <<EOFEOF 之間的內容會被當作標準輸出。
  2. JSON 格式的輸出是 Ansible 模組的標準輸出格式。
  3. ansible/hacking/test-module 是用於測試 Ansible 模組的工具。

讀取 Ansible 提供的引數

Ansible 會將引數寫入一個檔案,並將該檔案的路徑傳遞給模組。更新 file_upper 模組以讀取引數檔案:

#!/bin/bash
source $1
cat <<EOF
{"content":"$foo"}
EOF

執行 ansible/hacking/test-module -m file_upper -a foo=bar 以測試更新後的模組。輸出結果應該如下:

{
  "content": "bar"
}

內容解密:

  1. $1 代表傳遞給模組的第一個引數,即引數檔案的路徑。
  2. 使用 source 命令讀取引數檔案,並將引數設為環境變數。
  3. $foo 代表引數檔案中定義的變數 foo 的值。

實作模組邏輯

最後,需要實作將指設定檔案內容轉換為大寫的邏輯。使用 tr 命令可以實作大小寫轉換:

#!/bin/bash
source $1
tr '[:lower:]' '[:upper:]' < "$filename" > "$filename.upper"
cat <<EOF
{"content":"$(cat $filename.upper)"}
EOF

假設 $filename 是傳遞給模組的檔案名稱引數。

內容解密:

  1. tr '[:lower:]' '[:upper:]' 命令將輸入內容從小寫轉換為大寫。
  2. 將轉換後的內容寫入一個新的檔案(例如 $filename.upper)。
  3. 將轉換後的內容讀回並作為 JSON 輸出的一部分傳回。

最終程式碼

最終的 file_upper 模組應該類別似於以下內容:

#!/bin/bash
source $1
tr '[:lower:]' '[:upper:]' < "$filename" > "$filename.upper"
cat <<EOF
{"content":"$(cat $filename.upper)","changed":true}
EOF

這個模組讀取引數,將指設定檔案的內容轉換為大寫,並傳回轉換後的內容以及變更狀態。

內容解密:

  1. 使用 source $1 讀取引數檔案。
  2. 使用 tr 命令進行大小寫轉換。
  3. 傳回 JSON 格式的輸出,包括轉換後的內容和變更狀態。

自定義 Ansible 模組開發

開發自定義模組的重要性

Ansible 模組是 Ansible 自動化工具的核心組成部分,用於執行特定的任務。開發自定義模組可以擴充套件 Ansible 的功能,滿足特定的業務需求。

使用 Bash 開發 Ansible 模組

建立一個簡單的 Bash 模組

以下是一個簡單的 Bash 模組範例,用於將檔案內容轉換為大寫:

#!/bin/bash
source $1
content=$(cat $file | tr '[:lower:]' '[:upper:]')
echo $content > $file
cat << EOF
{"content":"$content"}
EOF

這個模組使用 tr 命令將檔案內容轉換為大寫,並將結果寫回檔案。

使模組具有冪等性

冪等性是指多次執行模組後,結果保持一致。以下程式碼檢查檔案內容是否已轉換為大寫,如果已轉換,則不進行任何操作:

#!/bin/bash
source $1
original=$(cat $file)
content=$(echo $original | tr '[:lower:]' '[:upper:]')
if [[ "$original" == "$content" ]]; then
  CHANGED="false"
else
  CHANGED="true"
  echo $content > $file
fi
cat <<EOF
{"changed":$CHANGED, "content":"$content"}
EOF

內容解密:

  1. source $1:載入 Ansible 提供的引數檔案。
  2. original=$(cat $file):讀取檔案原始內容。
  3. content=$(echo $original | tr '[:lower:]' '[:upper:]'):將原始內容轉換為大寫。
  4. if [[ "$original" == "$content" ]]; then:檢查原始內容是否已是大寫,如果是,則不進行任何操作。
  5. CHANGED="true":如果內容有變化,則設定 CHANGEDtrue
  6. echo $content > $file:將轉換後的大寫內容寫回檔案。
  7. cat <<EOF:輸出 JSON 格式的結果。

使用 Python 開發 Ansible 模組

建立一個簡單的 Python 模組

以下是一個簡單的 Python 模組範例,使用 ansible.module_utils.basic 函式庫:

#!/usr/bin/python
from ansible.module_utils.basic import *

def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(required=True)
        )
    )
    params = module.params
    module.exit_json(changed=True, name=params['name'])

if __name__ == '__main__':
    main()

內容解密:

  1. from ansible.module_utils.basic import *:匯入 Ansible 的基本模組工具函式庫。
  2. module = AnsibleModule(argument_spec=dict(...)):建立一個 Ansible 模組例項,並定義引數規範。
  3. params = module.params:取得模組引數。
  4. module.exit_json(changed=True, name=params['name']):輸出 JSON 格式的結果,並離開模組。

更新模組定義

以下程式碼更新了模組定義,需要 urlusernamepassword 和可選的 display_name 引數:

module = AnsibleModule(
    argument_spec=dict(
        url=dict(required=True),
        username=dict(aliases=['name'], required=True),
        password=dict(required=True),
        display_name=dict(required=False)
    )
)

內容解密:

  1. url=dict(required=True):定義 url 引數為必填。
  2. username=dict(aliases=['name'], required=True):定義 username 引數為必填,並設定別名 name
  3. password=dict(required=True):定義 password 引數為必填。
  4. display_name=dict(required=False):定義 display_name 引數為可選。