Ansible Playbook 的引數化設計,讓使用者能更彈性地管理複雜的自動化任務。透過變數的運用,可以根據不同的環境和需求調整 Playbook 的執行邏輯,提升程式碼的可重複使用性並減少冗餘。事實收集和快取機制則能有效提升 Playbook 執行效率,同時 hostvars 提供了跨主機資訊存取的便利性。理解 Ansible 的變數哲學和 Jinja2 篩選器的使用,能更有效地操作和運用收集到的資料。最後,開發自定義模組則能針對特殊需求擴充套件 Ansible 的功能,例如透過 RPC API 與 WordPress 等第三方服務互動,展現 Ansible 的高度擴充套件性和客製化能力。
第5章:引數化Playbook
動態組態與變數使用
在Ansible中,引數化Playbook是實作動態組態的關鍵。透過使用變數(variables),可以使Playbook更加靈活且可重複使用。
使用vars_prompt和vars_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 種篩選器。雖然大多數篩選器很少使用,但某些核心篩選器,如 map、replace、rejectattr、selectattr、first 和 last,在開始使用傳回需要操作和使用的資料的模組(例如 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-core 和 ansible-modules-extras。
ansible-modules-core
ansible-modules-core 包含所有與 Ansible 一起發布的核心模組,如 apt、template 和 copy。這些模組非常穩定,並由 Ansible 核心團隊維護。
ansible-modules-extras
其餘的模組儲存在 ansible-modules-extras 中。雖然它們與 Ansible 一起發布,但實際上是由社群維護的。extras 倉函式庫包含如 debconf、bundler 和 pagerduty 等模組。在為 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 的 pyyaml 和 jinja2 相依性,可以透過 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 模組。
內容解密:
cat <<EOF和EOF之間的內容會被當作標準輸出。- JSON 格式的輸出是 Ansible 模組的標準輸出格式。
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代表傳遞給模組的第一個引數,即引數檔案的路徑。- 使用
source命令讀取引數檔案,並將引數設為環境變數。 $foo代表引數檔案中定義的變數foo的值。
實作模組邏輯
最後,需要實作將指設定檔案內容轉換為大寫的邏輯。使用 tr 命令可以實作大小寫轉換:
#!/bin/bash
source $1
tr '[:lower:]' '[:upper:]' < "$filename" > "$filename.upper"
cat <<EOF
{"content":"$(cat $filename.upper)"}
EOF
假設 $filename 是傳遞給模組的檔案名稱引數。
內容解密:
tr '[:lower:]' '[:upper:]'命令將輸入內容從小寫轉換為大寫。- 將轉換後的內容寫入一個新的檔案(例如
$filename.upper)。 - 將轉換後的內容讀回並作為 JSON 輸出的一部分傳回。
最終程式碼
最終的 file_upper 模組應該類別似於以下內容:
#!/bin/bash
source $1
tr '[:lower:]' '[:upper:]' < "$filename" > "$filename.upper"
cat <<EOF
{"content":"$(cat $filename.upper)","changed":true}
EOF
這個模組讀取引數,將指設定檔案的內容轉換為大寫,並傳回轉換後的內容以及變更狀態。
內容解密:
- 使用
source $1讀取引數檔案。 - 使用
tr命令進行大小寫轉換。 - 傳回 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
內容解密:
source $1:載入 Ansible 提供的引數檔案。original=$(cat $file):讀取檔案原始內容。content=$(echo $original | tr '[:lower:]' '[:upper:]'):將原始內容轉換為大寫。if [[ "$original" == "$content" ]]; then:檢查原始內容是否已是大寫,如果是,則不進行任何操作。CHANGED="true":如果內容有變化,則設定CHANGED為true。echo $content > $file:將轉換後的大寫內容寫回檔案。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()
內容解密:
from ansible.module_utils.basic import *:匯入 Ansible 的基本模組工具函式庫。module = AnsibleModule(argument_spec=dict(...)):建立一個 Ansible 模組例項,並定義引數規範。params = module.params:取得模組引數。module.exit_json(changed=True, name=params['name']):輸出 JSON 格式的結果,並離開模組。
更新模組定義
以下程式碼更新了模組定義,需要 url、username、password 和可選的 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)
)
)
內容解密:
url=dict(required=True):定義url引數為必填。username=dict(aliases=['name'], required=True):定義username引數為必填,並設定別名name。password=dict(required=True):定義password引數為必填。display_name=dict(required=False):定義display_name引數為可選。