iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
DevOps

不爆肝學習 Ansible 的短暫30天系列 第 20

Day20 - 自己的模組自己動手做:AnsibleModule

  • 分享至 

  • xImage
  •  

今日目標

  • 釐清自訂模組的適用情境與放置位置
  • AnsibleModule 撰寫支援冪等與 check mode 的模組
  • 使用 ad-hoc 指令與 Playbook 驗證模組行為

為什麼要自己寫模組?

因為實務上流程百百種,市面上的現成模組也基本上是基於開發者遇到的問題提出來的解決方案,所以當我們遇到一些特殊問題時,這個時候就會需要自己寫模組來解決。

舉個例子

我們來實作一個簡單的模組 file_write,這個模組會將文字內容寫入檔案,並且當內容相同時維持冪等,並支援 check mode。

# library/file_write.py
#!/usr/bin/python
from __future__ import annotations

import hashlib
import os
from ansible.module_utils.basic import AnsibleModule


def read_text(path: str) -> str | None:
    """讀取檔案內容,若檔案不存在則回傳 None"""
    if not os.path.exists(path):
        return None
    with open(path, "r", encoding="utf-8") as handle:
        return handle.read()


def sha1_text(text: str | None) -> str:
    """計算文字內容的 SHA1 雜湊值,用於比較檔案變更"""
    if text is None:
        return ""
    return hashlib.sha1(text.encode("utf-8")).hexdigest()


def ensure_parent(path: str) -> None:
    """確保目標檔案的父目錄存在,若不存在則自動建立"""
    parent = os.path.dirname(path)
    if parent and not os.path.isdir(parent):
        os.makedirs(parent, exist_ok=True)


def write_text(path: str, content: str, mode: str | None) -> None:
    """寫入檔案內容並設定權限"""
    with open(path, "w", encoding="utf-8") as handle:
        handle.write(content)
    if mode:
        os.chmod(path, int(mode, 8))  # 將八進位字串轉換為整數


def main() -> None:
    """模組主要邏輯:處理參數、執行檔案寫入、回傳結果"""
    # 定義模組參數規格
    module = AnsibleModule(
        argument_spec={
            "path": {"type": "path", "required": True},        # 目標檔案路徑
            "content": {"type": "str", "required": True},      # 要寫入的內容
            "mode": {"type": "str"},                           # 檔案權限 (選填)
        },
        supports_check_mode=True,  # 支援 --check 模式
    )

    # 取得模組參數
    path: str = module.params["path"]
    content: str = module.params["content"]
    mode: str | None = module.params.get("mode")

    # 讀取目前檔案內容並判斷是否需要變更
    current = read_text(path)
    will_change = current != content

    # 如果是 check mode,只回報結果不實際執行
    if module.check_mode:
        module.exit_json(
            changed=will_change,
            path=path,
            before=sha1_text(current),
            after=sha1_text(content),
            check_mode=True,
        )

    # 實際執行檔案寫入
    try:
        if will_change:
            ensure_parent(path)  # 確保父目錄存在
            write_text(path, content, mode)  # 寫入檔案
    except Exception as error:
        # 發生錯誤時回傳失敗訊息
        module.fail_json(msg=str(error), path=path)

    # 成功完成,回傳執行結果
    module.exit_json(
        changed=will_change,
        path=path,
        before=sha1_text(current),
        after=sha1_text(read_text(path)),
    )


if __name__ == "__main__":
    main()

來驗證一下

使用 Ad-hoc 測試

# 實際寫入
ansible -i localhost, -c local localhost \
  -M library -m file_write \
  -a "path=/tmp/day20.txt content='Hello from module' mode=0644"

# check mode 模擬(只檢查不實際執行)
ansible -i localhost, -c local localhost \
  -M library -m file_write \
  -a "path=/tmp/day20.txt content='Hello from module'" --check

# 測試冪等性(重複執行應該顯示 changed=false)
ansible -i localhost, -c local localhost \
  -M library -m file_write \
  -a "path=/tmp/day20.txt content='Hello from module' mode=0644"

使用 Playbook 測試

---
# custom-module-demo.yml
- name: Day20 custom module demo
  hosts: all
  gather_facts: false
  tasks:
    - name: Write text file via custom module
      file_write:
        path: /tmp/day20.txt
        content: "Hello from custom module"
        mode: "0644"
ANSIBLE_LIBRARY=library \
ansible-playbook -i inventory.ini custom-module-demo.yml --syntax-check

注意事項

1. 參數定義規範

  • 使用 argument_spec 明確定義參數型別與必填狀態
  • 敏感資訊 (如密碼、密鑰等) 務必加上 no_log=True
  • 提供合理的預設值和選項驗證

2. 冪等性實作

  • 執行前先讀取現況並比較差異
  • 確保 changed 狀態準確反映真實變更
  • 避免不必要的重複操作

3. Check Mode 支援

  • module.check_mode 為 true 時,只需要回報預期結果
  • 不進行任何實際的系統修改
  • 提供 beforeafter 狀態資訊

4. 錯誤處理策略

  • 捕捉可預期的例外情況
  • 使用 fail_json 回傳詳細的錯誤訊息
  • 包含足夠的上下文資訊幫助除錯

作業練習時間

練習一:基礎模組測試

  1. 按照文章範例建立 library/file_write.py 模組
  2. 使用 ad-hoc 指令測試模組的基本功能
  3. 驗證 check mode 和冪等性是否正常運作

練習二:擴展模組功能

嘗試為 file_write 模組添加以下功能:

  1. 新增 backup 參數,當設為 true 時在覆寫前備份原檔案
  2. 新增 ownergroup 參數,支援檔案擁有者設定
  3. 新增輸入驗證,當 mode 格式不正確時回傳錯誤訊息

練習三:實務應用

設計一個新的自訂模組 system_info,功能需求:

  1. 收集系統基本資訊 (OS、記憶體、磁碟空間)
  2. 支援 JSON 格式輸出
  3. 實作適當的錯誤處理

💡Tips:可參考 ansible.module_utils.facts 相關工具函數。

常見問題

Q1: 模組找不到或無法載入

問題現象:

ERROR! couldn't resolve module/action 'file_write'

解決方案:

  • 確認模組檔案路徑正確 (library/ 目錄或使用 -M 參數)
  • 檢查模組檔案是否具有執行權限:chmod +x library/file_write.py
  • 驗證 Python shebang 行是否正確:#!/usr/bin/python

Q2: 模組執行失敗但沒有詳細錯誤訊息

問題現象:

fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "..."}

解決方案:

  • 增加 -vvv 參數獲得詳細除錯資訊
  • 在模組中加入更多的錯誤處理和日誌輸出
  • 使用 ansible-playbook --syntax-check 檢查語法

Q3: 冪等性測試失敗

問題現象:
重複執行同樣的任務總是顯示 changed=true

解決方案:

  • 檢查狀態比較邏輯是否正確實作
  • 確認檔案內容、權限等比較條件包含所有相關屬性
  • 使用 SHA1 等雜湊值比較檔案內容變更

明日預告

明天我們來使用 Molecule 來驗證 Ansible 專案在實際運行中的正確性


上一篇
Day19 - 效能優化與平行處理
下一篇
Day21 - Ansible 也有測試要寫!
系列文
不爆肝學習 Ansible 的短暫30天21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言