前言

最近嘗試在使用 Ansible 寫出針對 Inventory 的 Current State 狀態整理, Inventory 會記錄 kubernetes 中 cluster 所有的 nodes,但很多時候我們在開始執行 playbook 時才發現有些 node 的狀態不是健康的,所以希望寫出一個流程可以先做一些 precheck 捕捉 inventory 的狀態,這樣在執行 playbook 時就可以針對這些 unhealthy node 先排除,不會因為 node 的狀態不健康而導致 playbook 執行失敗。這種自己定義的邏輯我希望自己寫 .py 來實現,所以我在思考應該使用 module 還是 action plugin 來實現。這兩者的差異還是有點模糊,於是就寫下這篇文章來做個比較。

在開始之前,先來講一些 ansible 常見的術語:

  • Modules:
    • 透過 ansible 推送到各個節點的腳本 (像是那個 module 的 .py 檔案),我們稱為module
    • 而需要注意的事情是,這些 module 是在 ansible 的目標主機上執行的,你在 Playbook 裡下 module 的參數,Ansible 幫你把 module(其實是 Python 腳本)丟到目標機器,然後執行。所以 module 的 運作環境 是在遠端機器上。
    • 常見的 module 像是 ansible.builtin.command 或是 ansible.builtin.copy, kubernetes.core.k8s,這些 module 其實都是在 target host 上跑的。
  • Module utilities (Ref: Using and developing module utilities
    • 是一些可以在 module 裡面使用的 helper function,這些 function 可以幫助你更方便地寫 module,像是 AnsibleModule 就是 module utilities 的一個範例。
    • 當很多個 module 使用相同的程式碼時,我們可以把這些函數儲存在 module utilieis 以最大限度地減少重複和維護。
  • Plugins:
    • 主要是用來擴充 ansible 的核心功能,可以做到轉換資料、紀錄輸出、連線到 inventory 等等。
    • Plugin 與 Module 不同的點在於 Plugin 主要執行在 Control Node 上用來處理 Ansible 的輸入和輸出

小知識:Ansible 這個詞起源於科幻小說 Ender’s Game 中的一種超光速通訊裝置,用來控制遠方的星際戰艦。

正確觀念

在開始之前,想先帶各位了解, 是否要使用到 module 還是 plugin? 在加上根據官方說明 Should you develop a module 有提到開發 modules 雖然很簡單,但是很多情境上不一定是必要的,我們在開始寫一個新的 module 之前應該要先問問自己:

1. 使否已經存在一個類似的 module 可以使用了?
在現有的 module 裡面可能已經存在你需要的功能就不建議再重新造輪子了。

2. 你是否使用 action plugin 而不是 module 來開發?(重要)
一個 action plugin 也許是最好的方法來滿足你想做到的功能, action plugins 是跑在 control node 而不是 managed node 上 (也就是我們的目標主機),並且 action plugin 最方便的地方其功能可供所有 module 使用。舉例來說,是可以在 action plugin 中使用其他 module 的,更詳細的部分可以參考 developing plugins page

3. 是否使用 role 而不是 module 來開發?(重要)
現有模組的組合可能涵蓋您想要的功能。您可以為這種類型的用例編寫一個 role。查看 roles documentation

4. 是否要建立一個 collections 而不是一個 module 來開發?(重要)
您想要的功能可能對於單一 module 來說太大時。像是您想將 Ansible 連接到新的雲端供應商、資料庫或網路平台,則可能需要開發一個新的 collection 會比較適合。查看 develop a new collection

另外,有幾個 module 撰寫要注意的事情:

1. 每個 module 都應具有簡潔且定義明確的功能。基本上,遵循 UNIX 原則,專注做好一件事。

2. 一個 module 不應該要求使用者了解它底層呼叫的 API 或工具的所有選項或參數

  • 如果你寫了一個 module,是呼叫某個底層工具(例如 kubectl、curl、aws cli…),你不應該把所有底層參數暴露給使用者,讓他們來選。

  • 使用者需要 查 API 文件或 CLI 手冊 才知道該怎麼用你的 module,那這不是一個好 module。

  • 如果你無法在 Ansible module 文件裡 列出所有合法參數值(例如:因為參數太複雜、格式不穩定、依賴別的 context),那可能代表你的 module 應該被重構或會被拒絕收錄到 Ansible Galaxy。

    範例

    ❌ 不好的例子(違反原則):這樣的寫法其實只是把使用者丟進 CLI 的坑,Ansible 本身幾乎沒幫忙做事。

    1
    2
    - my_module:
    cli_args: "--foo=bar --enable-x --disable-y"

    ✅ 好的例子(符合原則):這種寫法抽象了底層工具的參數,讓使用者可以「以直觀的方式」來使用 module,而不是需要熟悉底層工具。

    1
    2
    3
    4
    - my_module:
    enable_feature_x: true
    region: us-east-1
    timeout: 60

3. Ansible modules 應該把實際邏輯包好,不是只做一層很薄的包裝

  • Module 通常應該包含大部分與資源互動的邏輯。也就是說,不是把責任丟給 user 自己寫 playbook,而是 module 自己要負責好處理邏輯。
  • 如果你寫的 module 只是簡單包一層 API,裡面幾乎沒邏輯,使用者就會被迫把複雜邏輯寫在 playbook 裡。
  • 所以這種 module 應該被拒絕收錄。

案例分析

因為我的 use case 主要是在:

  • 在控制端執行 playbook 的地方,使用 kubectl get nodes 的相關指令來獲取 node 的狀態。
  • 然後整理出 unhealthy 的節點內容,會傳整理結果給 plabyook

在上面的例子中,因為我們沒有需要「到每一台節點」執行某些檢查,像是去每一台機器下 systemctl status 來確認某些套件是否有裝了。因次我們應該使用 action plugin 在控制主機執行即可。
所以我們不需要使用 module 的方式來執行。使用 action plugin 即可做到。因為你根本不需要去很多台主機,事情在 bastion 查一次就可以了。

如果我們使用 Module 會遇到什麼困難?
如果我們強行使用 Module 會變成:

  • 每個 module 會被分派到目標主機去跑,但其實我們的案例沒有需要到目標主機。
  • 如果 target 是多套 cluster 讓每台都要自己下 kubectl 指令,那資料分散也不好處理。
  • (重要)最重要的是自己寫的 module 不能呼叫其他 module,因為 module 是在 target 主機上執行的程式(通常是獨立的 Python script)。
  • 它執行時是經由 ansible runner 單獨執行的環境,不知道其他 module 的存在,也無法像 action plugin 呼叫 _execute_module
  • module 設計原則是「單一職責、專注在某個資源的狀態處理」,不應該再管 orchestrate 其他 module

使用其他 module ?

因為我有一個步驟是想要使用 ansible 本身的 ping module 來檢查目標主機是否可以使用 ansible 控制這台主機,他與一般的 ICMP ping 不同,這個是要確認目標主機是否可以使用 ansible 控制這台主機。
為了可以使用這個 module,我們需要在 action plugin 裡面使用 _execute_module 來執行這個 module,這樣就可以在控制端執行 ping 的指令了。

1
2
3
4
5
result = self._execute_module(
module_name='ping',
module_args=dict(),
task_vars=task_vars
)

結論

在了解完這些背景之後,最關鍵的要素使用 action plugin 來開發有以下:

  1. 沒有需要「到每一台節點」執行某些檢查
  2. 使用 ansible 原生的 module 來做處理資料

但是如果今天你需要到每一台節點去檢查某些狀態,像是去每一台機器下 systemctl status 來確認某些套件是否有裝了,那就可以考慮使用 module 的方式來執行。

補充:ansible ping vs icmp ping

什麼是「普通的 ping」?
當你在電腦上打這行指令:

1
ping 192.168.1.10

這會用「ICMP 協定」去問 192.168.1.10 這台機器:

嘿~你有在線上嗎?可以回我一個訊息嗎?

這個叫 網路層的連線測試(只是確認網路通不通、主機有沒有回應)。

什麼是「Ansible 的 ping module」?
在 Ansible 裡,如果你這樣下:

1
2
- name: Ping hosts with ansible
ansible.builtin.ping:

它不是做「普通 ping」,而是:
👉 Ansible會 連到目標機器(通常用 SSH 連線),
👉 登入成功後,在機器上跑一小段 Python code,
👉 確認「我可以用 Ansible 控制這台機器喔」。

所以 Ansible 的 ping 是在問:
嘿~我能成功登入你的系統、而且可以跑 Python 腳本嗎?