Collection

Collection #

Role adalah cara mengemas dan berbagi konfigurasi Ansible. Tapi role punya batasan — ia hanya bisa berisi tasks, handlers, templates, dan variabel. Ia tidak bisa mengemas module custom, filter plugin, atau inventory plugin bersama-sama. Ansible Collection hadir untuk menyelesaikan keterbatasan ini: satu unit distribusi yang bisa berisi role, module, plugin, playbook, dan dokumentasi — semuanya dengan versi yang jelas dan dependency yang terdeklarasi. Pada artikel tentang Dynamic Inventory sebelumnya, kita sudah melihat plugin inventory dari collection amazon.aws. Pada artikel tentang Custom Plugin kita juga sudah menulis lookup dan filter plugin. Collection adalah cara terstruktur untuk mengemas semua itu menjadi satu paket yang bisa dibagikan ke tim lain atau bahkan ke komunitas open source.

Mengapa Collection, Bukan Sekadar Role? #

Sebelum masuk ke teknis, pahami dulu apa yang membedakan collection dari role. Keduanya tampak mirip — sama-sama direktori dengan struktur tertentu, sama-sama bisa dibagikan. Namun collection jauh lebih ekspresif dan merupakan unit distribusi resmi yang dipakai ekosistem Ansible sejak versi 2.9. Perbandingan singkat berikut membantu kita memutuskan kapan harus berhenti di role dan kapan naik ke collection:

Aspek Role Collection
Isi tasks, handlers, templates, vars, files, defaults, meta Role + module + plugin (lookup, filter, callback, inventory, dll.) + playbook + docs
Module custom Tidak bisa Bisa (plugins/modules/)
Filter custom Tidak bisa (harus taruh di filter_plugins/ di level playbook) Bisa, terisolasi per collection
Versioning Versi di meta/main.yml saja Semver di galaxy.yml + dependency range
Distribusi Copy folder atau Ansible Galaxy roles ansible-galaxy collection install + tarball + Galaxy/Hub
FQCN roles.<name> namespace.collection.<component>
Cocok untuk Konfigurasi server, satu concern Library multi-komponen, platform, framework automation

Jika tim kita hanya membutuhkan “kumpulan task untuk setup web server”, role sudah cukup. Namun begitu kita ingin mengemas module Python untuk berinteraksi dengan API internal, filter Jinja2 untuk transformasi data perusahaan, dan dua atau tiga role sekaligus — collection adalah jawabannya. Banyak perusahaan besar seperti Red Hat, Cisco, dan Microsoft merilis produk mereka dalam bentuk collection, bukan role terpisah.


Kapan Harus Bikin Collection Baru? #

Tidak semua kode Ansible perlu dibungkus dalam collection. Ada titik di mana overhead strukturalnya tidak sebanding dengan manfaatnya. Decision tree berikut membantu kita memutuskan:

flowchart TD
    A["Butuh distribusi kode Ansible?"] --> B{"Ada module atau plugin custom?"}
    B -- Tidak --> C{">1 role yang dipakai bersama?"}
    C -- Tidak --> D["Role cukup"]
    C -- Ya --> E["Pertimbangkan collection"]
    B -- Ya --> F{"Pakai bersama dengan role dan plugin lain?"}
    F -- Tidak --> G["Module/plugin standalone mungkin cukup"]
    F -- Ya --> H{"Berbagi dengan tim/organisasi lain?"}
    H -- Tidak --> I["Collection internal kecil"]
    H -- Ya --> J["Collection dengan namespace, publish ke Hub"]

Pemakaian di dunia nyata biasanya seperti ini: tim DevOps di sebuah perusahaan punya beberapa module untuk berbicara dengan API internal (deployment, monitoring, secret rotation), beberapa filter untuk transformasi data (format nomor rekening, parsing nama karyawan), dan satu atau dua role untuk bootstrap server baru. Semua itu dibungkus dalam satu collection my_company.platform — dipasang di setiap playbook dengan requirements.yml, diuji dengan Molecule, dan dipublikasi ke Private Automation Hub setiap rilis.


Struktur Collection #

Layout direktori collection mengikuti konvensi yang sangat spesifik. Ansible tidak mencari komponen dari konfigurasi — ia mencarinya di lokasi yang sudah disepakati. Ini berbeda dengan role, di mana nama file di dalam direktori tasks/ bebas. Pada collection, setiap jenis komponen punya direktori sendiri di bawah plugins/. Mengikuti konvensi ini bukan hanya best practice — ini wajib, karena jika kita meletakkan module di plugins/modules/, Ansible tidak akan menemukannya.

my_namespace/
└── my_collection/
    ├── galaxy.yml              # Metadata collection (wajib)
    ├── README.md
    ├── CHANGELOG.rst
    ├── LICENSES/               # License file per komponen (REUSE compliant)
    │   └── GPL-2.0-or-later.txt
    ├── docs/                   # Dokumentasi (sphinx/rst)
    │   ├── index.rst
    │   └── modules/
    │       └── app_config.rst
    ├── plugins/
    │   ├── modules/            # Custom modules — muncul sebagai module
    │   │   └── app_config.py
    │   ├── module_utils/       # Shared utilities untuk modules
    │   │   └── api_client.py
    │   ├── lookup/             # Lookup plugins
    │   │   └── company_cmdb.py
    │   ├── filter/             # Filter plugins
    │   │   └── company_filters.py
    │   ├── callback/           # Callback plugins (logging, notifikasi)
    │   │   └── deployment_notifier.py
    │   ├── inventory/          # Inventory plugins
    │   │   └── cmdb.py
    │   ├── connection/         # Connection plugins (jarang, untuk transport custom)
    │   └── strategy/           # Strategy plugins (sangat jarang)
    ├── roles/                  # Role yang dibundel dalam collection
    │   ├── webserver/
    │   └── database/
    ├── playbooks/              # Playbook contoh
    │   └── deploy.yml
    ├── tests/                  # Unit test untuk module/plugin
    │   └── unit/
    │       └── plugins/
    │           └── modules/
    │               └── test_app_config.py
    └── changelogs/             # Changelog per release (fragment-style)
        └── fragments/
            └── add-app-config-module.yml

Yang perlu kita perhatikan dari struktur di atas: plugins/module_utils/ adalah direktori krusial yang sering diabaikan. Jika dua module kita menggunakan fungsi yang sama (misalnya helper untuk HTTP call ke API internal), taruh fungsi itu di module_utils/ lalu import dari kedua module. Ini menghindari duplikasi kode dan memastikan perubahan di satu tempat otomatis ter-reflect di module lain. Pola module_utils + module ini persis sama dengan yang dipakai modul resmi seperti community.general.


Membuat Collection Baru #

Ansible Galaxy CLI menyediakan perintah init yang membuatkan kerangka direktori collection lengkap dengan file kosong yang siap diisi:

# Buat struktur collection dari template
ansible-galaxy collection init my_namespace.my_collection

# Pindah ke direktori collection
cd my_namespace/my_collection

# Lihat struktur yang dibuat
ls -la
# drwxr-xr-x  galaxy.yml
# drwxr-xr-x  plugins/
# drwxr-xr-x  roles/
# drwxr-xr-x  playbooks/
# drwxr-xr-x  tests/
# drwxr-xr-x  docs/

Perhatikan bahwa init menggunakan konvensi namespace.collection — dua segmen yang dipisah titik. Ini bukan hanya gaya penamaan, ini adalah Fully Qualified Collection Name (FQCN) yang akan kita gunakan di playbook. Memilih namespace yang konsisten itu penting karena jika sudah dipublikasikan, kita tidak bisa mengubahnya. Beberapa panduan:

  • Namespace untuk perorangan/tim: nama pendek dan unik, misal unisbadri.tools atau badricreativetech.platform
  • Namespace untuk perusahaan: nama perusahaan atau divisi, misal my_company.cloud
  • Hindari namespace generik seperti common atau utilities — bentrok dengan namespace orang lain di Galaxy

galaxy.yml: Metadata Collection #

File galaxy.yml adalah jantung collection — mendefinisikan identitas, versi, dan dependency. Salah satu kesalahan paling umum adalah membiarkan field kosong atau deskripsi yang terlalu singkat. Galaxy menggunakan metadata ini untuk halaman detail, jadi investasi waktu 10 menit di awal akan menghemat waktu menjelaskan ke pengguna lain di kemudian hari.

# galaxy.yml
namespace: my_company
name: infrastructure
version: 2.1.0
readme: README.md
description: >
  Collection untuk infrastruktur internal My Company — berisi role, module,
  dan plugin yang digunakan di seluruh tim SRE. Mendukung deployment ke AWS,
  manajemen secret via Vault, dan integrasi dengan monitoring internal.  

authors:
  - Tim SRE <[email protected]>

license:
  - GPL-2.0-or-later

tags:
  - infrastructure
  - deployment
  - monitoring
  - aws

repository: https://github.com/mycompany/ansible-infrastructure
documentation: https://docs.mycompany.internal/ansible
issues: https://github.com/mycompany/ansible-infrastructure/issues
build_ignore:
  - .git
  - .github
  - changelogs/*.fragment
  - tests/output

# Dependency ke collection lain — versi minimum atau range
dependencies:
  community.general: ">=7.0.0"
  amazon.aws: ">=7.0.0"
  community.docker: ">=3.4.0"

ANTI-PATTERN vs BENAR di galaxy.yml #

# ANTI-PATTERN: dependency tanpa pin versi — bisa pecah sewaktu-waktu
dependencies:
  community.general:
  amazon.aws:

# BENAR: pin ke versi minimum yang sudah ditest, dengan range yang masuk akal
dependencies:
  community.general: ">=7.0.0,<9.0.0"
  amazon.aws: ">=7.0.0,<8.0.0"
  community.docker: "3.4.6"   # Pin exact di production yang konservatif

Mem-pin dependency ke range yang ditest bukan paranoia — ini satu-satunya cara untuk memastikan playbook kita tidak tiba-tiba gagal di tengah deployment karena upstream collection merilis breaking change. Pipeline CI harus menguji collection kita dengan versi terbaru dependency secara berkala, dan ketika sudah yakin kompatibel, naikkan range di galaxy.yml.

Field build_ignore juga sering diabaikan tapi penting. Saat kita menjalankan ansible-galaxy collection build, Ansible membuat tarball. File .git, direktori .github/, dan output test bisa menambah ukuran tarball secara signifikan dan yang lebih penting — menghindari kebocoran informasi internal ke publik. Selalu exclude direktori yang tidak perlu.


Menginstal dan Menggunakan Collection #

Sebelum modul dan plugin dari sebuah collection bisa dipakai, collection itu harus diinstal. Ada beberapa cara instalasi yang masing-masing cocok untuk skenario berbeda:

# Build collection menjadi archive .tar.gz
ansible-galaxy collection build

# Output: my_company-infrastructure-2.1.0.tar.gz

# Instal dari file lokal — cocok untuk testing internal
ansible-galaxy collection install my_company-infrastructure-2.1.0.tar.gz -p ./collections

# Instal dari Galaxy publik
ansible-galaxy collection install my_company.infrastructure

# Instal dengan versi spesifik
ansible-galaxy collection install my_company.infrastructure:==2.1.0

# Instal dengan range versi
ansible-galaxy collection install 'my_company.infrastructure:>=2.0.0,<3.0.0'

Setelah terinstal, module dan plugin diakses menggunakan Fully Qualified Collection Name (FQCN). FQCN adalah format namespace.collection.<component> yang memastikan tidak ada ambiguitas saat dua collection yang berbeda punya module dengan nama yang sama:

# FQCN: namespace.collection.module_name
- name: Set konfigurasi aplikasi
  my_company.infrastructure.app_config:
    name: max_connections
    value: "100"
    api_url: "{{ api_url }}"
    api_token: "{{ vault_api_token }}"
    state: present

# Role dalam collection
- name: Setup web server
  import_role:
    name: my_company.infrastructure.webserver
  vars:
    nginx_port: 443

# Filter dari collection
- name: Generate report
  debug:
    msg: "{{ user_list | my_company.infrastructure.format_employee_id }}"

# Lookup dari collection
- name: Ambil data dari CMDB
  debug:
    msg: "{{ lookup('my_company.infrastructure.company_cmdb', 'server-001') }}"

ANTI-PATTERN vs BENAR di Penggunaan Module #

# ANTI-PATTERN: pakai nama pendek tanpa FQCN — fragile dan ambiguous
- name: Set konfigurasi
  app_config:
    name: max_connections
    value: "100"

# BENAR: selalu pakai FQCN — eksplisit dan tahan bentrok nama
- name: Set konfigurasi
  my_company.infrastructure.app_config:
    name: max_connections
    value: "100"

Mengapa FQCN wajib bahkan di playbook internal? Bayangkan kita menggunakan app_config di 30 playbook. Suatu hari tim kita melakukan import collection baru dari vendor yang kebetulan juga memiliki module app_config (bukan hal yang mustahil — nama generik sering bentrok). Tanpa FQCN, Ansible akan memprioritaskan satu berdasarkan urutan instalasi — dan yang lebih parah, ini bisa berbeda antara dev machine, CI, dan production. Dengan FQCN, perilakunya deterministik: yang kita tulis adalah yang dipakai.


Alur Build, Publish, dan Install #

Sebelum collection bisa dipakai tim lain atau di server produksi, ada alur yang harus dilalui. Memahami sequence ini penting karena setiap tahap punya error mode yang berbeda:

sequenceDiagram
    participant Dev as "Developer"
    participant Local as "Local Repo"
    participant Build as "ansible-galaxy build"
    participant Tarball as ".tar.gz"
    participant Hub as "Galaxy/Hub"
    participant CI as "CI Pipeline"
    participant Server as "Target Server"

    Dev->>Local: "Edit module/role/plugin"
    Dev->>Local: "Update galaxy.yml (version bump)"
    Dev->>Local: "Tambah changelog fragment"
    Dev->>CI: "Push & buka PR"
    CI->>CI: "Run sanity test (ansible-test sanity)"
    CI->>CI: "Run unit test (pytest)"
    CI->>CI: "Run integration test (Molecule)"
    CI->>Build: "Trigger build di merge ke main"
    Build->>Tarball: "Hasilkan tarball dengan versi"
    Build->>Hub: "Publish ke Galaxy/Hub"
    Hub->>Server: "ansible-galaxy install menarik versi baru"
    Server->>Server: "ansible-playbook jalankan dengan collection baru"

Diagram sequence di bawah menunjukkan bahwa alur ini bukan hanya “build dan publish” — ada banyak titik validasi yang harus dilalui. Sanity test misalnya akan menolak module yang tidak memiliki dokumentasi, yang return value-nya tidak konsisten, atau yang mengimpor modul Python yang dilarang (os.system, subprocess.Popen tanpa argument list, dsb.). Lewati satu tahap dan tarball kita akan ditolak oleh Galaxy atau, lebih parah, akan menjadi bom waktu di production.


requirements.yml untuk Manajemen Dependency #

Proyek yang menggunakan beberapa collection sebaiknya mendefinisikan semuanya di satu file. Tanpa requirements.yml, setiap orang yang clone repo harus ingat untuk ansible-galaxy collection install koleksi yang benar dengan versi yang benar. Ini resep untuk bug yang muncul hanya di laptop developer tertentu tapi tidak di CI.

# requirements.yml
---
collections:
  # Collection dari Ansible Galaxy
  - name: community.general
    version: ">=7.0.0,<8.0.0"

  - name: community.docker
    version: "3.4.6"          # Pin ke versi exact di production

  - name: amazon.aws
    version: ">=7.0.0"

  - name: kubernetes.core
    version: "2.4.0"

  # Collection private dari Automation Hub internal
  - name: my_company.infrastructure
    version: "2.1.0"
    source: https://automation-hub.mycompany.internal/api/galaxy/

  # Collection dari source control langsung (untuk koleksi yang belum di-publish)
  - name: my_company.experimental
    source: https://github.com/mycompany/ansible-experimental.git
    type: git
    version: main

roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
    src: https://github.com/geerlingguy/ansible-role-nginx
# Instal semua dependency sekaligus
ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.yml

# Atau keduanya sekaligus (Ansible >= 2.10)
ansible-galaxy install -r requirements.yml

ANTI-PATTERN vs BENAR di requirements.yml #

# ANTI-PATTERN: tidak ada version pin — bisa pecah setiap kali dependensi update
collections:
  - name: community.general
  - name: amazon.aws

# BENAR: pin version atau range yang sudah ditest di CI
collections:
  - name: community.general
    version: ">=7.0.0,<8.0.0"
  - name: amazon.aws
    version: "7.5.0"

Skenario buruk yang sering terjadi: developer clone repo, lupa jalankan ansible-galaxy install, dan langsung menjalankan playbook. Playbook gagal karena module community.general.parted tidak ada. Atau lebih halus — playbook jalan di laptop developer karena kebetulan sudah ada versi community.general yang kompatibel, tapi gagal di CI yang build dari scratch dengan versi terbaru. Mem-pin version di requirements.yml menghilangkan variabel ini.

Pin dependency di production, tapi range longgar di development. Pola yang sehat: gunakan range >=X,<Y di branch development agar tim selalu menguji versi terbaru, dan pin exact version (X.Y.Z) di branch production yang di-tag untuk rilis. Sebelum naik versi di production, CI harus sudah menjalankan integration test dengan versi baru.

Private Automation Hub #

Untuk collection internal yang tidak boleh dipublikasikan ke Galaxy publik, gunakan Private Automation Hub atau serve via server HTTP sederhana. Ansible Galaxy itu sendiri adalah protokol HTTP yang relatif sederhana — banyak tools bisa menyediakannya tanpa harus instal full Automation Controller.

# ansible.cfg
[galaxy]
server_list = automation_hub, galaxy

[galaxy_server.automation_hub]
url = https://automation-hub.mycompany.internal/api/galaxy/
auth_url = https://automation-hub.mycompany.internal/auth/token/
token = {{ lookup('env', 'AUTOMATION_HUB_TOKEN') }}

[galaxy_server.galaxy]
url = https://galaxy.ansible.com/
# Publish collection ke private hub
ansible-galaxy collection publish \
  my_company-infrastructure-2.1.0.tar.gz \
  --server automation_hub

# Tim lain install dari hub
ansible-galaxy collection install my_company.infrastructure \
  --server automation_hub

Alternatif yang lebih ringan: beberapa tim meng-host tarball collection di internal S3 bucket atau simple HTTP server, lalu referensi langsung di requirements.yml:

collections:
  - name: my_company.infrastructure
    source: https://artifacts.mycompany.internal/ansible-collections/
    type: url
    version: "2.1.0"

Pendekatan ini cocok untuk tim kecil yang tidak ingin mengoperasikan Automation Hub penuh. Trade-off-nya adalah tidak ada UI untuk browsing koleksi, tidak ada notifikasi update otomatis, dan tidak ada RBAC.

Automation Hub vs Galaxy sederhana: Private Automation Hub (dari Red Hat) menyediakan UI, RBAC, approval workflow, dan integrasi dengan SSO — cocok untuk organisasi 50+ engineer. Untuk tim kecil, HTTP server biasa sudah cukup dan jauh lebih ringan untuk dioperasikan.

Testing Collection #

Kualitas collection ditentukan oleh pengujiannya. Tanpa test, kita tidak bisa yakin bahwa module baru tidak merusak module lama, atau bahwa role masih berjalan di versi dependency yang baru. Pola pengujian collection memiliki tiga lapis yang masing-masing memiliki tujuan berbeda:

# molecule/default/molecule.yml untuk testing collection
---
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml

driver:
  name: docker

platforms:
  - name: ubuntu22
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true
  - name: rocky9
    image: geerlingguy/docker-rocky9-ansible:latest
    pre_build_image: true

provisioner:
  name: ansible
  playbooks:
    converge: converge.yml
  inventory:
    links:
      group_vars: ../../../inventory/group_vars

verifier:
  name: ansible
# Sanity test — cek gaya kode, dokumentasi, dan kepatuhan terhadap standar
ansible-test sanity --docker default

# Unit test untuk module Python
ansible-test units --docker default

# Integration test dengan Molecule
molecule test

Tiga lapis test ini punya fungsi berbeda. Sanity test itu murah (menit) dan dijalankan setiap commit — mengecek bahwa setiap module punya dokumentasi, bahwa import diizinkan, bahwa return value konsisten. Unit test untuk module Python yang kompleks (misalnya module yang berinteraksi dengan API) — dijalankan setiap PR. Integration test dengan Molecule — dijalankan setiap malam atau sebelum release, karena butuh image Docker dan lebih lambat.

Jalankan sanity test di pre-commit hook. ansible-test sanity bisa dijalankan dalam hitungan detik untuk satu module. Tambahkan ke pre-commit hook dan kita akan mendapatkan feedback instan setiap kali mengedit module, tanpa harus menunggu CI. Ini sangat mengurangi cycle time untuk perbaikan dokumentasi atau gaya kode.

Decision Tree Kapan Bikin Collection #

Topik terakhir di artikel ini adalah keputusan yang paling sering ditanyakan: apakah kode ini layak dibungkus dalam collection? Jawabannya tidak selalu jelas, dan membuat collection terlalu dini (untuk satu role yang dipakai dua playbook) sama borosnya dengan tidak membuat collection saat sudah waktunya (lima module + tiga role + dua plugin yang disalin ke setiap proyek).

flowchart TD
    Start["Mulai: Punya kode Ansible"] --> Q1{"Ada module/plugin custom?"}
    Q1 -- Tidak --> Q2{"Jumlah role > 1?"}
    Q2 -- Tidak --> Single["Role cukup"]
    Q2 -- Ya --> Q3{"Dipakai di > 1 proyek?"}
    Q3 -- Tidak --> Q4["Pertimbangkan symlink atau copy"]
    Q3 -- Ya --> Coll1["Collection internal"]
    Q1 -- Ya --> Q5{"Ada module + role + plugin?"}
    Q5 -- Tidak --> Single2["Module/plugin standalone + role"]
    Q5 -- Ya --> Q6{"Sharing dengan tim lain?"}
    Q6 -- Tidak --> Coll2["Collection internal dengan meta"]
    Q6 -- Ya --> Q7{"Publikasi ke publik?"}
    Q7 -- Tidak --> Coll3["Collection ke Private Hub"]
    Q7 -- Ya --> Coll4["Collection ke Galaxy publik"]

Decision tree ini bukan aturan keras — ini heuristic yang bisa kita sesuaikan. Beberapa perusahaan membuat collection sejak role pertama karena sudah tahu akan memiliki banyak role. Perusahaan lain menunggu sampai mereka memiliki minimal tiga module custom karena overhead setup collection (galaxy.yml, CI, testing) tidak sebanding untuk satu module.


Ringkasan #

  • Collection adalah unit distribusi Ansible yang lengkap — berisi role, module, plugin, dan playbook dalam satu paket dengan versi yang jelas. Ia adalah evolusi dari role, bukan penggantinya.
  • Struktur direktori collection mengikuti konvensi ketat: plugins/modules/, plugins/filter/, roles/, dan seterusnya — Ansible menemukan komponen berdasarkan lokasi, bukan konfigurasi. plugins/module_utils/ untuk kode bersama yang dipakai banyak module.
  • galaxy.yml mendefinisikan identitas, versi, dan dependency collection. Selalu pin dependency ke versi minimum atau range yang ditest, dan exclude file yang tidak perlu di build_ignore.
  • Gunakan FQCN (Fully Qualified Collection Name) saat menggunakan module dari collection — my_company.infrastructure.app_config, bukan hanya app_config. Ini memastikan tidak ada ambiguitas saat dua collection punya module dengan nama yang sama.
  • requirements.yml untuk mendefinisikan semua collection dependency di satu tempat. Pin versi untuk reproducibility, dan jalankan ansible-galaxy collection install -r requirements.yml di CI/CD agar environment konsisten.
  • Untuk collection internal, gunakan Private Automation Hub atau repository internal — jangan publish kode internal ke Galaxy publik. Untuk tim kecil, HTTP server biasa sudah cukup.
  • Testing berlapis: ansible-test sanity untuk gaya kode (cepat, jalan di pre-commit), unit test untuk logika module Python, dan Molecule untuk integration test. Ketiganya saling melengkapi, bukan menggantikan.
  • Pilih namespace sekali dan konsisten — namespace tidak bisa diubah setelah dipublikasikan ke Galaxy.

← Sebelumnya: Dynamic Inventory   Berikutnya: Jinja2 Lanjutan →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact