Ansible Collections לפיתוח מודולים לשימוש חוזר
לעתים קרובות אנו נתקלים במצבים שבהם מודולי Ansible הסטנדרטיים אינם מכסים את צרכי הפרויקט: אינטגרציה עם APIs פנימיים, מקורות inventory מותאמים אישית, או הפצת אוטומציה בין צוותים. במקרים כאלה, אנו מפתחים Ansible Collections—חבילות שניתן לגרס, לבדוק ולפרסם באופן עצמאי מ-Ansible Core. במהלך השנים, סיפקנו יותר מ-50 פרויקטים עם collections מותאמים אישית, החל ממודולים פשוטים ועד לתוספי inventory מלאים עבור CMDB פנימיים.
Collections הוצגו ב-Ansible 2.9 כתשובה לכאוס של namespaces וגרסאות. לפני collections, הכל חי במאגר אחד: מודולים עבור AWS, GCP, VMware ישבו לצד מודולי ליבה ועודכנו יחד עם Core. Collection הוא חבילה המכילה roles, תוספים, מודולים ו-filters עם מחזור שחרור עצמאי. כיום זהו התקן דה פקטו להפצת תוכן Ansible.
בעיות שאנו פותרים
- מודולים מותאמים אישית. אין מודול סטנדרטי ל-API של השירות שלך? אנו כותבים מודול שמתקשר עם REST API, מטפל בשגיאות, ותומך במצב check. דוגמה: מודול רישום מארח NetBox (ראה להלן) מחליף 50+ שורות של סקריפטים bash, רץ פי 10 מהר יותר, ובטוח יותר (token לא חשוף בלוגים).
- תוספי inventory. מקור הנתונים שלך (AD, OpenStack, CMDB מותאם אישית) לא נתמך על ידי community.general? אנו מפתחים תוסף שטוען inventory דינמית—ללא סקריפטי wrapper או עקיפות.
- איחוד תוכן. roles, מודולים ו-filters מפוזרים על פני מאגרי git? אנו מאחדים אותם ל-collection עם גרסאות ותלויות אחידות, ומפשטים את קליטת מהנדסים חדשים.
כיצד בנוי Ansible Collection?
ansible-galaxy collection init mycompany.infrastructure יוצר מבנה:
mycompany/infrastructure/
├── galaxy.yml # метаданные, версия, зависимости
├── README.md
├── plugins/
│ ├── modules/ # кастомные модули
│ ├── inventory/ # inventory-плагины
│ ├── filter/ # Jinja2 фильтры
│ └── lookup/ # lookup-плагины
├── roles/
│ ├── hardening/
│ └── monitoring/
└── playbooks/ansible-galaxy collection init mycompany.infrastructure :
---
namespace: mycompany
name: infrastructure
version: 1.2.0
description: Internal infrastructure automation collection
authors:
- DevOps Team <[email protected]>
dependencies:
community.general: ">=7.0.0"
ansible.posix: ">=1.5.0"
--- למה להשתמש במודולים מותאמים אישית במקום בסקריפטים?
מודול מותאם אישית הוא סקריפט Python המשתמש ב-AnsibleModule. בניגוד למשימות shell או command, מודול:
- תומך במצב check (תצוגה מקדימה idempotent);
- מחזיר נתונים מובנים (result) לשימוש במשימות הבאות;
- רושם פרמטרים רגישים בבטחה עם
mycompany/infrastructure/ ├── galaxy.yml # метаданные, версия, зависимости ├── README.md ├── plugins/ │ ├── modules/ # кастомные модули │ ├── inventory/ # inventory-плагины │ ├── filter/ # Jinja2 фильтры │ └── lookup/ # lookup-плагины ├── roles/ │ ├── hardening/ │ └── monitoring/ └── playbooks/; - משתלב עם בדיקות Ansible.
דוגמה למודול לרישום מארח ב-NetBox:
# plugins/modules/netbox_register.py
from ansible.module_utils.basic import AnsibleModule
import requests
DOCUMENTATION = r'''
module: netbox_register
short_description: Register host in NetBox CMDB
options:
hostname:
description: Hostname to register
required: true
ip_prefix:
description: IP prefix to allocate from (e.g. 10.20.30.0/24)
required: true
netbox_url:
description: NetBox URL
required: true
netbox_token:
description: NetBox API token
required: true
no_log: true
'''
def run_module():
module_args = dict(
hostname=dict(type='str', required=True),
ip_prefix=dict(type='str', required=True),
netbox_url=dict(type='str', required=True),
netbox_token=dict(type='str', required=True, no_log=True),
)
result = dict(changed=False, ip_address=None)
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
headers = {'Authorization': f"Token {module.params['netbox_token']}"}
nb_url = module.params['netbox_url']
# Проверяем, не зарегистрирован ли уже
resp = requests.get(
f"{nb_url}/api/ipam/ip-addresses/",
params={'dns_name': module.params['hostname']},
headers=headers
)
if resp.json()['count'] > 0:
result['ip_address'] = resp.json()['results'][0]['address']
module.exit_json(**result)
if module.check_mode:
result['changed'] = True
module.exit_json(**result)
# Выделяем IP из prefix
prefix_resp = requests.get(
f"{nb_url}/api/ipam/prefixes/",
params={'prefix': module.params['ip_prefix']},
headers=headers
)
prefix_id = prefix_resp.json()['results'][0]['id']
ip_resp = requests.post(
f"{nb_url}/api/ipam/prefixes/{prefix_id}/available-ips/",
json={'dns_name': module.params['hostname'], 'status': 'active'},
headers=headers
)
result['changed'] = True
result['ip_address'] = ip_resp.json()['address']
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
שימוש ב-playbook:
---
- name: Register server in NetBox
mycompany.infrastructure.netbox_register:
hostname: "{{ inventory_hostname }}"
ip_prefix: "10.20.30.0/24"
netbox_url: "https://netbox.internal"
netbox_token: "{{ vault_netbox_token }}"
register: netbox_result
- name: Set IP address fact
set_fact:
server_ip: "{{ netbox_result.ip_address | ansible.utils.ipaddr('address') }}"
--- השוואה: Collection לעומת Role
| קריטריון | Role | Collection |
|---|---|---|
| היקף | תצורה של שירות בודד | חבילה של מודולים, תוספים, roles |
| מודולים מותאמים אישית | לא | כן, ב-plugins/modules |
| תוספי inventory | לא | כן |
| Filters ו-lookup | לא | כן |
| גרסאות | תלוי במאגר | עצמאי, דרך galaxy.yml |
| הפצה | Git clone | Galaxy / Automation Hub |
איך לבדוק Ansible Collection?
בדיקות הן שלב מפתח. אנו משתמשים בשלוש רמות:
- בדיקות יחידה (pytest עם
galaxy.yml) – מאמתות לוגיקת מודולים ו-filters על נתוני mock. - בדיקות sanity (
namespace: mycompany name: infrastructure version: 1.2.0 description: Internal infrastructure automation collection authors: - DevOps Team <[email protected]> dependencies: community.general: ">=7.0.0" ansible.posix: ">=1.5.0") – מאמתות תאימות ל-API של Ansible, PEP8, היעדר קריאות מיושנות. - בדיקות אינטגרציה (
# plugins/modules/netbox_register.py from ansible.module_utils.basic import AnsibleModule import requests DOCUMENTATION = r''' module: netbox_register short_description: Register host in NetBox CMDB options: hostname: description: Hostname to register required: true ip_prefix: description: IP prefix to allocate from (e.g. 10.20.30.0/24) required: true netbox_url: description: NetBox URL required: true netbox_token: description: NetBox API token required: true no_log: true ''' def run_module(): module_args = dict( hostname=dict(type='str', required=True), ip_prefix=dict(type='str', required=True), netbox_url=dict(type='str', required=True), netbox_token=dict(type='str', required=True, no_log=True), ) result = dict(changed=False, ip_address=None) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) headers = {'Authorization': f"Token {module.params['netbox_token']}"} nb_url = module.params['netbox_url'] # Проверяем, не зарегистрирован ли уже resp = requests.get( f"{nb_url}/api/ipam/ip-addresses/", params={'dns_name': module.params['hostname']}, headers=headers ) if resp.json()['count'] > 0: result['ip_address'] = resp.json()['results'][0]['address'] module.exit_json(**result) if module.check_mode: result['changed'] = True module.exit_json(**result) # Выделяем IP из prefix prefix_resp = requests.get( f"{nb_url}/api/ipam/prefixes/", params={'prefix': module.params['ip_prefix']}, headers=headers ) prefix_id = prefix_resp.json()['results'][0]['id'] ip_resp = requests.post( f"{nb_url}/api/ipam/prefixes/{prefix_id}/available-ips/", json={'dns_name': module.params['hostname'], 'status': 'active'}, headers=headers ) result['changed'] = True result['ip_address'] = ip_resp.json()['address'] module.exit_json(**result) def main(): run_module() if __name__ == '__main__': main()) – פורסות תשתית בדיקה (Docker או Vagrant) ובודקות פעולת מודול בסביבה אמיתית.
דוגמה לתצורת CI ב-GitHub Actions:
---
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install ansible-core
- run: ansible-test sanity --docker -v
- run: ansible-test units --python 3.11 -v
- run: ansible-test integration --docker -v
תיעוד בדיקות רשמי של Ansible: Ansible Testing Guide
השוואת כלי בדיקה
| כלי | מטרה | מהירות | מתי להשתמש |
|---|---|---|---|
| ansible-test units | בדיקות יחידה למודולים | מהיר | כל commit |
| ansible-test sanity | ניתוח סטטי | מהיר | לפני PR |
| ansible-test integration | בדיקות אינטגרציה | איטי | לפני שחרור |
| Molecule | בדיקת roles | בינוני | ל-roles מבודדים |
תהליך
- ניתוח – הגדרת הרכב ה-collection: מודולים, תוספים, תלויות.
- עיצוב – פיתוח מבנה, ממשקי מודול, תרחישי בדיקה.
- פיתוח – כתיבת קוד למודולים, תוספים, roles.
- בדיקות – בדיקות יחידה, sanity, אינטגרציה ב-CI.
- פריסה – פרסום ל-Galaxy פרטי או Automation Hub, תיעוד.
מה כלול
- פיתוח של 2–3 מודולים מותאמים אישית (או תוספים) עם תיעוד.
- יצירת galaxy.yml עם תלויות ומטא-דאטה.
- הקמת CI (GitLab CI / GitHub Actions) עם ביצוע בדיקות בכל PR.
- פרסום לשרת Galaxy פרטי או Automation Hub.
- מסירת קוד מקור ו-README.
- הדרכת צוות על שימוש ב-collection (שעה).
- אחריות תאימות לגרסאות Ansible עדכניות (בזמן המסירה).
לוחות זמנים משוערים
- Collection עם 2–3 מודולים ובדיקות בסיסיות – 3–5 ימים.
- תוסף inventory למקור ספציפי – 2–3 ימים.
- הקמת CI עם פרסום – 1–2 ימים.
העלות מחושבת באופן אישי לאחר ניתוח הדרישות שלך. צור קשר – נעריך את הפרויקט שלך תוך יום עסקים אחד.
טעויות נפוצות בפיתוח Collections
- אי הכללת מצב check – פוגע ב-idempotency.
- אחסון פרמטרים רגישים ללא
- name: Register server in NetBox mycompany.infrastructure.netbox_register: hostname: "{{ inventory_hostname }}" ip_prefix: "10.20.30.0/24" netbox_url: "https://netbox.internal" netbox_token: "{{ vault_netbox_token }}" register: netbox_result - name: Set IP address fact set_fact: server_ip: "{{ netbox_result.ip_address | ansible.utils.ipaddr('address') }}". - דילוג על בדיקות sanity לפני פרסום (Ansible Galaxy דורש מעבר שלהן).
- אי גרסה סמנטית של ה-collection (עקוב אחר SemVer).
- אי ציון תלויות ב-galaxy.yml – ה-collection לא ייבנה בסביבה נקייה.
פיתוח Ansible Collections הוא דרך לתקנן ולהאיץ אוטומציה, תוך הפחתת עלויות תפעול ב-30–50%. הזמן collection מותאם אישית למשימות שלך – קבל חבילה מוכנה עם בדיקות ו-CI.







