在 Ansible 自動化世界中,有效管理 Collection 的版本和安裝路徑至關重要。版本控制不當可能導致 Playbook 無法執行,甚至引發非預期的錯誤。本文將分享我在 Ansible 專案中實踐的 Collection 管理策略,並教你如何利用 ansible-galaxy 提升團隊協作效率。

全域安裝:分享 Collection

對於團隊分享的 Collection,我建議統一安裝在全域路徑,例如 /usr/share/ansible/collections。這樣所有使用者都能存取相同的 Collection 版本,避免版本衝突。

首先,建立全域 Collection 路徑並設定許可權:

sudo mkdir -p /usr/share/ansible/collections
sudo chmod a+w /usr/share/ansible/collections/

mkdir -p 指令可以遞迴建立目錄,如果父目錄不存在則會一併建立。chmod a+w 指令賦予所有使用者對該目錄的寫入許可權,確保 Ansible 可以安裝 Collection。

接著,使用 -p 引數指定安裝路徑:

ansible-galaxy collection install -p /usr/share/ansible/collections community.general

ansible-galaxy collection install 指令用於安裝 Collection。-p 引數指定安裝路徑。此指令會將 community.general Collection 安裝到指定路徑。

驗證安裝結果:

ansible-galaxy collection list

ansible-galaxy collection list 指令會列出所有已知路徑下的 Collection,包含 COLLECTIONS_PATHS 變數中定義的路徑以及 Ansible 預設安裝路徑。

指定版本安裝與升級

ansible-galaxy 支援指定 Collection 版本安裝。例如,要安裝 community.general 的 4.8.0 版本:

ansible-galaxy collection install community.general:4.8.0

在 Collection 名稱後加上冒號和版本號即可指定安裝特定版本。

升級 Collection 也相當簡單,指定最新版本或使用 --force 引數強制重新安裝:

ansible-galaxy collection install --force community.general

--force 引數會強制重新安裝 Collection,即使已安裝相同版本。

ansible-galaxy 也支援版本範圍指定,例如安裝大於 4.5.0 小於 5.0.0 的版本:

ansible-galaxy collection install 'community.general:>4.5.0,<5.0.0'

使用版本範圍指定可以確保安裝的 Collection 版本符合特定需求,這在管理相依性時非常實用。

requirements.yml:版本控制利器

跨機器開發或執行 Playbook 時,全域安裝方式可能失效。此時,requirements.yml 檔案就派上用場了。它定義了所需的 Collection 及其版本,確保所有團隊成員使用一致的開發環境。

以下是一個 requirements.yml 範例:

---
collections:
- name: community.general
  version: '>4.5.0,<5.0.0'
- community.crypto

requirements.yml 檔案使用 YAML 格式,列出所需的 Collection 及其版本或版本範圍。

使用 -r 引數即可根據 requirements.yml 安裝 Collection:

ansible-galaxy collection install -r requirements.yml

-r 引數指定 requirements.yml 檔案,ansible-galaxy 會根據檔案內容安裝或升級 Collection。

移除 Collection

ansible-galaxy 沒有提供移除 Collection 的指令。由於 Collection 只是特定結構的目錄和檔案,因此移除 Collection 就像刪除目錄一樣簡單:

rm -rf /usr/share/ansible/collections/ansible_collections/community/general/

使用 rm -rf 指令可以遞迴刪除指定目錄及其所有內容。請務必小心使用此指令,確保刪除的是正確的目錄。

建立個人 Ansible Collection

瞭解 Collection 的管理和維護後,讓我們建立自己的 Collection。

如同 Roles,Collection 只是目錄中的一組有組織的檔案。雖然你可以手動建立所有目錄,但也可以使用 ansible-galaxy 工具建立空白範本。

首先,需要一個名稱空間 (namespace) 和一個 Collection 名稱。在 Ansible Galaxy 網站上發布時,名稱空間將是你的 GitHub 控制程式碼。在本例中,我們不會發布到 Ansible Galaxy,因此我選擇 mynamespace 作為名稱空間,你可以根據需要替換它。

Collection 名稱通常用於指示 Collection 的用途。這裡,我們將建立一個名為 mycollection 的 Collection。

建立一個空目錄,然後建立目錄結構:

mkdir collection-development
cd collection-development/
ansible-galaxy collection init mynamespace.mycollection

ansible-galaxy collection init 指令會建立 Collection 的基本目錄結構。

檢查目錄結構:

.
└── mynamespace
    └── mycollection
        ├── README.md
        ├── docs
        ├── galaxy.yml
        ├── meta
        │   └── runtime.yml
        ├── plugins
        │   └── README.md
        └── roles
  graph LR
    C[C]
    D[D]
    E[E]
    F[F]
    G[G]
    H[H]
    I[I]
    J[J]
    A[mynamespace] --> B(mycollection)
    B --> C{README.md}
    B --> D{docs}
    B --> E{galaxy.yml}
    B --> F{meta}
    F --> G{runtime.yml}
    B --> H{plugins}
    H --> I{README.md}
    B --> J{roles}

此圖表展示了新建立的 Ansible Collection 的目錄結構。mynamespace 是名稱空間,mycollection 是 Collection 名稱。其他檔案和目錄包含了 Collection 的檔案、設定、依賴關係、plugins 和 roles。

本文探討了 Ansible Collection 的版本控制、安裝和建立方法。妥善管理 Collection 版本能確保 Playbook 在不同環境中穩定執行,並提升團隊協作效率。我強烈建議將 requirements.yml 納入版本控制系統,讓所有團隊成員都能輕鬆設定一致的開發環境。

- name: 安裝 Apache
  ansible.builtin.apt:
    name: apache2
    state: present
    update_cache: yes

這個任務使用 apt 模組來安裝 apache2 套件。update_cache: yes 引數確保在安裝前更新套件快取。

建置並安裝 Collection 後,執行 playbook 即可安裝 Apache。

Collection 結構

以下 圖表展示了 Ansible Collection 的目錄結構:

  flowchart LR
subgraph blackcat1968.com
    A[README.md] --> B(galaxy.yml)
    B --> C(docs/)
    C --> D(meta/)
    D --> E(plugins/)
    E --> F(modules/)
    E --> G(lookup/)
    D --> H(roles/)
    H --> I(apache/)
    I --> J(tasks/)
    J --> K(main.yml)
    D --> L(playbooks/)
    L --> M(collection_test1.yml)
    L --> N(collection_test2.yml)
    D --> O(tests/)
end

這個圖表清晰地展現了 Collection 內部的檔案和目錄組織,讓讀者更容易理解 Collection 的結構。

透過 Ansible Collection,您可以更有效地組織和管理您的自動化程式碼,提高程式碼的重用性和可維護性。善用 Collection,讓您的 Ansible 自動化更加便捷和高效。

/usr/lib/python3.6/site-packages/ansible/plugins

在 macOS 上,使用 Homebrew 安裝的 Ansible,外掛則位於:

/opt/homebrew/lib/python3.9/site-packages/ansible/plugins

您可以在自己的系統上使用 ansible --version 命令來確認 Ansible 的安裝路徑,從而找到外掛的具體位置。找到外掛目錄後,您可以瀏覽不同型別的外掛,例如 connectionlookupinventory 等等。每個目錄都包含了該型別外掛的 Python 原始碼。

建立客製化外掛:以 Lookup 外掛為例

現在,讓我們動手建立一個簡單的 Lookup 外掛。Lookup 外掛的主要功能是從外部資料來源擷取資訊,並將其提供給 Ansible Playbook 使用。以下是一個名為 firstchar 的 Lookup 外掛範例,它會傳回字串的第一個字元:

# plugins/lookup/firstchar.py
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase

class LookupModule(LookupBase):

    def run(self, terms, variables=None, **kwargs):
        # terms is a list of lookup terms
        # variables is a dictionary of variables
        # kwargs is a dictionary of additional keyword arguments

        if not isinstance(terms, list):
            raise AnsibleError('Terms must be a list')

        ret = []
        for term in terms:
            if not isinstance(term, str):
                raise AnsibleError('Term must be a string')

            if term:
                ret.append(term[0])
            else:
                ret.append('')  # Return empty string for empty input

        return ret

這個 firstchar 外掛繼承自 ansible.plugins.lookup.LookupBase,並覆寫了 run 方法。run 方法接收一個 terms 列表作為輸入,其中每個元素都是一個要處理的字串。它會檢查輸入是否有效,並傳回一個列表,其中包含每個輸入字串的第一個字元。如果輸入字串為空,則傳回空字串。

將這個 firstchar.py 檔案儲存到您的外掛目錄下的 lookup 資料夾中。現在,您可以在 Playbook 中使用這個客製化外掛:

- hosts: all
  tasks:
    - debug:
        msg: "The first character of 'hello' is {{ lookup('firstchar', 'hello') }}"

    - debug:
        msg: "The first character of an empty string is {{ lookup('firstchar', '') }}"

    - name: Demonstrate handling multiple terms
      debug:
        msg: "First characters: {{ lookup('firstchar', 'apple', 'banana', 'cherry') }}"

    - name: Demonstrate handling an empty list of terms
      debug:
        msg: "First characters of empty list: {{ lookup('firstchar') }}"

這個 Playbook 使用 lookup 函式呼叫 firstchar 外掛,並將結果顯示在螢幕上。它示範瞭如何處理單個字串、空字串、多個字串以及空列表等不同輸入情況。

執行這個 Playbook,您應該會看到以下輸出:

ok: [localhost] => {
    "msg": "The first character of 'hello' is h"
}
ok: [localhost] => {
    "msg": "The first character of an empty string is "
}
ok: [localhost] => {
    "msg": "First characters: ['a', 'b', 'c']"
}
ok: [localhost] => {
    "msg": "First characters of empty list: []"
}

Lookup 外掛執行流程:

  graph LR
    B[B]
    A["Playbook 執行 lookup('firstchar', 'hello')"] --> B{"firstchar.py 的 run 方法"}
    B --> C["檢查輸入"]
    C -- "有效" --> D["擷取第一個字元 'h'"]
    C -- "無效" --> E["丟擲 AnsibleError"]
    D --> F["傳回 'h'"]

這個圖表展示了 Lookup 外掛的執行流程,從 Playbook 呼叫 lookup 函式開始,到外掛傳回結果結束。

透過這個簡單的範例,我們展示瞭如何建立和使用客製化 Lookup 外掛。您可以根據自己的需求修改和擴充套件這個範例,例如從檔案、資料函式庫或 API 中擷取資訊。

Ansible 的強大擴充套件性來自其靈活的外掛機制。本文將深入 filter 和 lookup 兩種外掛的開發,帶您掌握 Ansible 客製化外掛的精髓,提升自動化效率。

外掛位於 Ansible 安裝目錄下的 plugins 子目錄中,並根據其類別進一步分類別。例如,paramiko_ssh 連線外掛位於 connection/ 子目錄:

/usr/lib/python3.11/site-packages/ansible/plugins/connection/paramiko_ssh.py

不建議直接修改已安裝的外掛檔案,因為套件升級時修改會被覆寫。查閱外掛原始碼的最佳方法是從 GitHub 複製

Ansible 儲存函式庫:

  1. 複製儲存函式庫:
git clone https://github.com/ansible/ansible.git
cd ansible
  1. 外掛位於 lib/ansible/plugins/ 目錄下,同樣按類別放置在子目錄中:
cd lib/ansible/plugins
  1. 例如,connection 目錄包含所有根據連線的外掛:
ls -al connection/

目錄內容取決於 Ansible 版本。每個外掛都有一個 Python 檔案。

  1. 檢視外掛內容:
less connection/paramiko_ssh.py

您會看到類別似以下程式碼片段,其中 DOCUMENTATION 區塊與模組的定義方式非常相似:

# (c) 2012, BlackCat <blackcat@blackcat1968.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or
https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = """
author: Ansible Core Team
connection: paramiko
...

接下來,我們將透過實際範例,建立一個客製化過濾器外掛。

建立客製化 Filter 外掛

這個範例將建立一個名為 custom_filter.py 的 Python 檔案,其中包含一個簡單的過濾器,用另一個字串替換給定的字串。

您的目錄結構應如下所示:

.
├── hosts
├── filter_plugins
│ └── custom_filter.py
├── myplugin2.yml
└── testdoc.txt
  1. 增加檔案標頭:
# (c) 2024, 玄貓(BlackCat) <blackcat@blackcat1968.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  1. 定義過濾器函式:這個函式會將字串中的 “Puppet” 替換為 “Ansible”:
def improve_automation(a):
    return a.replace("Puppet", "Ansible")
  1. 建立 FilterModule 物件:
class FilterModule(object):
    ''' 改進自動化過濾器 '''
    def filters(self):
        return {'improve_automation': improve_automation}
  1. 編寫測試 playbook:
---
- name: 示範客製化過濾器的 Playbook
  hosts: frontends
  gather_facts: false

  vars:
    statement: "Puppet 是一款出色的自動化工具!"

  tasks:
    - name: 顯示訊息
      debug:
        msg: "{{ statement | improve_automation }}"

這個 playbook 使用了自定義的 improve_automation 過濾器。filter_plugins 目錄指示 Ansible 查詢自定義過濾器的位置。Playbook 使用 debug 模組印出套用過濾器後的 statement 變數值,預期輸出會將 “Puppet” 替換為 “Ansible”。

這個簡單的範例展示瞭如何建立和使用 Ansible filter 外掛。您可以以此為基礎,構建更複雜的外掛以滿足您的自動化需求。

  graph LR
    B[B]
A[建立 filter_plugins 目錄] --> B{新增 custom_filter.py};
B --> C[定義過濾器函式];
C --> D[建立 FilterModule 類別];
D --> E[編寫 playbook];
E --> F[執行 playbook];

透過以上步驟,我們可以輕鬆建立一個客製化的 Ansible filter 外掛,並在 playbook 中使用它。這將大幅提升 Ansible 的靈活性和效率,讓您可以更好地管理和自動化您的基礎設施。

這個方法不僅限於字串操作,您可以根據需求編寫更複雜的邏輯,例如數值計算、資料轉換等,從而更好地滿足您的自動化需求。 善用 Ansible 的外掛機制,可以讓您的自動化任務更加簡潔、高效,並提升整體的可維護性。