פיתוח Ansible Collections למודולים לשימוש חוזר

כאשר מודולי Ansible סטנדרטיים אינם מספיקים למשימות לא סטנדרטיות, אנו מפתחים Ansible Collections — חבילות אוטומציה לשימוש חוזר. הצוות שלנו בונה מודולים מותאמים אישית, תוספי inventory ואוספים במפתח מלא, ומפרסם אותם ל-Ansible Galaxy או ל-automation hub. התוצאה היא פתרונות אמינים עם גרסאות, שנבדקים ומתרחבים עם העסק שלכם.

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

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 מבודדים

תהליך

  1. ניתוח – הגדרת הרכב ה-collection: מודולים, תוספים, תלויות.
  2. עיצוב – פיתוח מבנה, ממשקי מודול, תרחישי בדיקה.
  3. פיתוח – כתיבת קוד למודולים, תוספים, roles.
  4. בדיקות – בדיקות יחידה, sanity, אינטגרציה ב-CI.
  5. פריסה – פרסום ל-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.