Parameterized

Parameterized #

Sebuah Ansible role yang dinilai tangguh tidak hanya dinilai dari kemampuannya menyelesaikan tugas tanpa error, melainkan juga dari fleksibilitasnya saat diadaptasikan ke berbagai skenario deployment yang berbeda. Dalam praktiknya, kita sering kali menghadapi situasi di mana satu role yang sama harus dijalankan dengan konfigurasi yang berbeda secara radikal—misalnya mengonfigurasi instance database primer (master) yang memerlukan memori tinggi dan instance replika (slave) yang menggunakan pengaturan read-only pada infrastruktur yang sama. Proses mengabstraksikan perilaku role ini agar dapat dikontrol sepenuhnya melalui variabel eksternal disebut sebagai parameterisasi role. Melalui parameterisasi yang matang, kita dapat menghindari duplikasi kode yang sia-sia dan mengonsolidasikan logika otomatisasi kita ke dalam satu sumber kebenaran (single source of truth).


Konsep Fondasi: Mengapa Kita Butuh Parameterisasi? #

Dalam arsitektur otomatisasi modern berbasis Infrastructure as Code (IaC), pemisahan yang jelas antara instruksi tugas (code) dan data konfigurasi (data) adalah hukum yang tidak boleh dilanggar. Bayangkan jika kita memiliki sepuluh aplikasi mikro (microservices) yang semuanya menggunakan kerangka kerja Node.js dan membutuhkan konfigurasi reverse proxy yang serupa. Tanpa parameterisasi, kita mungkin tergoda untuk membuat sepuluh folder role yang berbeda atau menulis tugas yang sangat panjang dengan banyak kondisi percabangan di dalam satu playbook.

Dengan menerapkan parameterisasi, kita cukup membuat satu role generik bernama nodejs_app. Logika internal role tersebut akan mendefinisikan langkah-langkah standar untuk menyalin kode, menginstal dependensi npm, mengonfigurasi file environment, dan mendaftarkan layanan sistem (systemd service). Semua aspek spesifik aplikasi—seperti nama aplikasi, port internal yang didengarkan, direktori root, versi Node.js yang diinginkan, hingga variabel lingkungan—dideklarasikan sebagai parameter variabel. Ketika kita memanggil role tersebut di dalam playbook, kita cukup “melempar” nilai variabel yang berbeda untuk masing-masing aplikasi mikro. Hal ini membuat playbook kita menjadi sangat bersih, modular, dan mudah dipelihara dalam jangka panjang.


Anatomi Variabel Role: defaults/main.yml vs vars/main.yml #

Ansible menyediakan dua direktori utama di dalam struktur standardisasi role untuk menyimpan variabel, yaitu defaults/ dan vars/. Meskipun keduanya tampak serupa karena sama-sama mendefinisikan variabel dalam format YAML, keduanya memiliki fungsi, tingkat prioritas (precedence), dan filosofi penggunaan yang sangat berbeda.

1. defaults/main.yml (Prioritas Terendah) #

Direktori defaults/main.yml adalah tempat di mana kita meletakkan semua variabel default untuk role kita. Variabel yang didefinisikan di sini memiliki prioritas ke-2 (dari bawah) dalam sistem hirarki prioritas variabel Ansible. Ini berarti variabel di sini dirancang untuk sangat mudah ditimpa (overridden) oleh pengguna role melalui file konfigurasi grup, file host, atau variabel playbook.

# File: roles/nodejs_app/defaults/main.yml
---
# ✓ VARIABEL DEFAULT YANG AMAN & DAPAT DITIMPA
node_app_name: "generic-node-app"
node_app_port: 3000
node_app_env: "production"
node_app_install_dir: "/opt/{{ node_app_name }}"
node_app_version: "20.x"
node_app_enable_clustering: false

Filosofi penulisan di defaults/ adalah memberikan nilai yang masuk akal dan aman secara default (convention over configuration). Jika pengguna memanggil role tanpa memberikan parameter apa pun, role harus tetap dapat berjalan dengan sukses menggunakan nilai-nilai default ini.

2. vars/main.yml (Prioritas Sangat Tinggi) #

Sebaliknya, direktori vars/main.yml digunakan untuk mendefinisikan variabel yang bersifat internal untuk role tersebut. Variabel di sini memiliki prioritas ke-16 dalam hirarki Ansible. Ini berarti variabel di sini sangat sulit ditimpa dan secara sengaja dikunci agar tidak diubah secara tidak sengaja oleh pemanggil playbook.

Kita menggunakan vars/main.yml untuk menyimpan konstanta internal, pustaka internal, nama paket sistem yang bergantung pada distribusi OS (jika tidak menggunakan dynamic vars loading), atau jalur file sistem yang tidak boleh diubah demi menjaga integritas sistem operasi target.

# File: roles/nodejs_app/vars/main.yml
---
# ✓ KONSTANTA INTERNAL ROLE (JANGAN DITIMPA)
node_system_dependencies:
  - build-essential
  - curl
  - git

node_process_manager: "pm2"
node_pm2_config_path: "/etc/pm2/conf.d"

Jika seorang pengguna mencoba menulis ulang variabel node_system_dependencies di tingkat playbook vars:, Ansible secara default akan tetap memprioritaskan nilai dari vars/main.yml di dalam role tersebut. Pemisahan ini memberikan perlindungan kepada pembuat role agar logika internal role tidak dirusak oleh input luar yang tidak valid.


Teknik Meng-override Variabel Role #

Setelah kita merancang variabel default di dalam role, langkah berikutnya adalah memahami bagaimana pengguna dapat mengirimkan nilai kustom untuk menimpa nilai default tersebut saat memanggil role di dalam playbook mereka. Terdapat beberapa teknik yang dapat kita gunakan, masing-masing dengan kegunaan spesifiknya:

1. Overriding di Tingkat Playbook (vars:) #

Pendekatan ini paling cocok jika nilai variabel berlaku untuk seluruh server yang ditargetkan di dalam suatu play yang sama.

# File: site.yml
---
- name: Deploy Aplikasi Backend Utama
  hosts: backend_servers
  vars:
    node_app_name: "backend-api"
    node_app_port: 8080
    node_app_env: "production"
  roles:
    - nodejs_app

2. Overriding Inline Saat Inisiasi Role #

Jika kita ingin melekatkan variabel secara spesifik pada pemanggilan role tertentu (terutama jika kita memanggil role yang sama beberapa kali dalam play yang sama), kita dapat mendefinisikannya langsung di dalam blok deklarasi roles:.

# File: site.yml
---
- name: Deploy Multi-App pada Satu Host
  hosts: web_servers
  roles:
    - role: nodejs_app
      vars:
        node_app_name: "frontend-spa"
        node_app_port: 4000
    
    - role: nodejs_app
      vars:
        node_app_name: "auth-service"
        node_app_port: 5000

3. Overriding Menggunakan group_vars dan host_vars (Sangat Direkomendasikan) #

Untuk manajemen infrastruktur skala besar, teknik terbaik adalah memisahkan variabel dari playbook itu sendiri dan meletakkannya di dalam direktori group_vars/ (berdasarkan grup server, seperti staging vs production) atau host_vars/ (berdasarkan host spesifik).

Jika kita memiliki file inventory seperti ini:

# File: inventory.ini
[staging]
staging-app-01 ansible_host=192.168.1.50

[production]
prod-app-01 ansible_host=10.0.0.10

Kita dapat membuat file variabel grup staging:

# File: group_vars/staging.yml
---
node_app_env: "staging"
node_app_port: 3000
node_app_enable_clustering: false

Dan file variabel grup production:

# File: group_vars/production.yml
---
node_app_env: "production"
node_app_port: 80
node_app_enable_clustering: true

Playbook kita tetap bersih tanpa deklarasi variabel statis, dan Ansible secara otomatis memetakan variabel yang tepat berdasarkan keanggotaan host dalam grup inventory.

4. Overriding Menggunakan Extra Variables (-e) #

Teknik ini memiliki prioritas tertinggi (ke-22) dan digunakan untuk override darurat atau pengujian ad-hoc dari CLI tanpa mengubah berkas konfigurasi apa pun.

# Menjalankan playbook dengan memaksa port aplikasi ke 9000
ansible-playbook site.yml -e "node_app_port=9000 node_app_env=development"

Pemanggilan Multi-Instance Menggunakan include_role dan Loops #

Secara default, Ansible menerapkan mekanisme deduplikasi role. Jika kita memanggil role yang sama lebih dari sekali dalam satu play (seperti pada contoh teknik overriding inline di atas), Ansible mendeteksi bahwa role tersebut sudah pernah dieksekusi dan mungkin akan mengabaikan pemanggilan kedua jika tidak ada variabel yang berubah secara signifikan atau jika parameter allow_duplicates: false diatur di meta/main.yml.

Untuk mengatasi keterbatasan ini secara elegan dan dinamis, terutama saat kita ingin mendeploy sejumlah variabel instansi dari daftar terstruktur, kita harus menggunakan modul include_role di dalam tugas loop (loop).

Contoh Skenario: Multi-Instance Deployment Database Redis #

Bayangkan kita ingin mendeploy tiga instance database Redis pada satu server fisik, masing-masing berjalan pada port yang berbeda dan memiliki batas memori yang berbeda. Kita dapat merancang playbook dengan struktur data list of dictionaries sebagai berikut:

# File: deploy_redis_instances.yml
---
- name: Setup Multi-Instance Redis
  hosts: db_servers
  vars:
    # ✓ STRUKTUR DATA TERSTRUKTUR UNTUK LOOPING
    redis_instances:
      - name: "redis-cache"
        port: 6379
        max_memory: "512mb"
      - name: "redis-session"
        port: 6380
        max_memory: "1gb"
      - name: "redis-queue"
        port: 6381
        max_memory: "256mb"

  tasks:
    - name: Iterasi instalasi role Redis untuk setiap instansi
      include_role:
        name: redis_instance
      vars:
        # ✓ MELEMPAR VARIABEL DARI ITEM LOOP
        redis_instance_name: "{{ item.name }}"
        redis_instance_port: "{{ item.port }}"
        redis_instance_max_memory: "{{ item.max_memory }}"
      loop: "{{ redis_instances }}"
      loop_control:
        label: "Instance: {{ item.name }} pada Port: {{ item.port }}"

Implementasi di Sisi Role (roles/redis_instance/tasks/main.yml) #

Di dalam role redis_instance, kita menggunakan variabel-variabel tersebut untuk membuat file konfigurasi unik dan mendaftarkan layanan systemd yang unik untuk setiap instansi.

# File: roles/redis_instance/tasks/main.yml
---
- name: Buat file konfigurasi spesifik untuk instansi Redis
  template:
    src: redis.conf.j2
    dest: "/etc/redis/redis-{{ redis_instance_name }}.conf"
    owner: redis
    group: redis
    mode: '0640'

- name: Konfigurasi file unit systemd untuk instansi Redis
  template:
    src: redis-server.service.j2
    dest: "/etc/systemd/system/redis-server-{{ redis_instance_name }}.service"
    owner: root
    group: root
    mode: '0644'
  register: systemd_unit_result

- name: Reload systemd daemon jika unit file berubah
  systemd:
    daemon_reload: true
  when: systemd_unit_result.changed

- name: Pastikan instansi Redis berjalan dan aktif
  service:
    name: "redis-server-{{ redis_instance_name }}"
    state: started
    enabled: true

Menggunakan pola include_role yang dikombinasikan dengan looping membuat arsitektur playbook kita sangat skalabel. Jika besok kita perlu menambah instansi Redis keempat, kita tidak perlu memodifikasi kode tugas sama sekali; kita cukup menambahkan satu item baru ke dalam daftar variabel redis_instances.


Validasi Parameter Menggunakan Modul Assert #

Salah satu kelemahan dari parameterisasi role yang kompleks adalah potensi kesalahan input dari pengguna. Jika pengguna lupa mengisi variabel wajib, atau memberikan nilai yang tidak masuk akal (misalnya port aplikasi berupa string huruf atau angka port di luar rentang port TCP yang valid), Ansible akan tetap menjalankan playbook dan mungkin akan gagal di tengah jalan setelah merusak sebagian konfigurasi server.

Untuk mencegah skenario buruk ini, kita harus menerapkan prinsip fail-fast dengan melakukan validasi parameter di awal eksekusi role menggunakan modul assert.

# File: roles/nodejs_app/tasks/main.yml
---
# ✓ VALIDASI PARAMETER INPUT SEBELUM MELAKUKAN INSTALASI
- name: Validasi parameter input role nodejs_app
  assert:
    that:
      - node_app_name is defined
      - node_app_name | length > 0
      - node_app_name is match("^[a-zA-Z0-9_-]+$")
      - node_app_port is defined
      - node_app_port | int >= 1024
      - node_app_port | int <= 65535
      - node_app_env in ['development', 'staging', 'production']
      - node_app_version in ['18.x', '20.x', '22.x']
    fail_msg: >
      Error: Validasi variabel untuk role nodejs_app gagal!
      Silakan periksa kembali definisi variabel di playbook atau group_vars Anda.
      Aturan validasi:
      - node_app_name wajib diisi, tidak boleh kosong, dan hanya boleh alfanumerik, dash (-), atau underscore (_).
      - node_app_port wajib diisi dan harus berupa angka port non-privileged (1024-65535).
      - node_app_env harus bernilai salah satu dari: development, staging, atau production.
      - node_app_version harus bernilai salah satu dari: 18.x, 20.x, atau 22.x.      
  tags: [always, validation]

- name: Lanjutkan ke langkah instalasi setelah validasi sukses
  include_tasks: install.yml

Dengan meletakkan tugas asersi ini di bagian paling atas berkas tasks/main.yml dengan tag always dan validation, kita menjamin bahwa Ansible akan langsung menghentikan proses eksekusi dalam hitungan detik jika ada variabel yang salah input. Hal ini menyelamatkan server target dari kondisi konfigurasi yang setengah jadi (broken state).


Diagram Alur Penentuan Prioritas Variabel (Precedence) #

Untuk memahami bagaimana Ansible menentukan nilai akhir dari suatu variabel saat terjadi bentrokan definisi di berbagai tingkat, kita dapat merujuk pada diagram alir penyelesaian prioritas berikut:

flowchart TD
    A["Evaluasi Variabel Ansible"] --> B{"Apakah didefinisikan di Extra Vars -e?"}
    B -- "Ya" --> C["Gunakan nilai Extra Vars (Prioritas Tertinggi)"]
    B -- "Nu" --> D{"Apakah didefinisikan di connection vars / ansible_facts?"}
    D -- "Ya" --> E["Gunakan nilai Connection / Facts"]
    D -- "Tidak" --> F{"Apakah didefinisikan di Playbook vars block?"}
    F -- "Ya" --> G["Gunakan nilai Playbook vars"]
    F -- "Tidak" --> H{"Apakah didefinisikan di host_vars / group_vars?"}
    H -- "Ya" --> I["Gunakan nilai host_vars / group_vars"]
    H -- "Tidak" --> J{"Apakah didefinisikan di vars/main.yml role?"}
    J -- "Ya" --> K["Gunakan nilai vars/main.yml (Internal Role)"]
    J -- "Tidak" --> L{"Apakah didefinisikan di defaults/main.yml role?"}
    L -- "Ya" --> M["Gunakan nilai defaults/main.yml (Default Terendah)"]
    L -- "Tidak" --> N["Kembalikan Error: Variabel Tidak Terdefinisi"]

Ringkasan #

  • Pemisahan Logika & Data — Parameterisasi memisahkan tugas otomatisasi (tasks) dari variabel konfigurasi (vars). Satu role generik dapat melayani banyak jenis deployment.
  • Konvensi defaults/main.yml — Letakkan semua parameter kustomisasi umum di direktori default dengan nilai bawaan yang aman agar role langsung dapat dijalankan tanpa konfigurasi wajib.
  • Enkapsulasi vars/main.yml — Gunakan berkas variabel internal ini untuk mengunci konstanta sistem operasi atau pustaka dependensi yang tidak boleh diubah oleh pengguna luar.
  • Hirarki Prioritas Variabel — Pahami bahwa variabel dari defaults/main.yml memiliki prioritas terendah, sementara variabel yang dideklarasikan saat inisiasi role atau extra vars (-e) akan menimpa nilai default tersebut.
  • Multi-Instance via include_role — Gunakan include_role di dalam loop tugas untuk mendeploy beberapa instance layanan pada satu server dengan parameter berbeda guna menghindari deduplikasi role statis.
  • Prinsip Fail-Fast — Integrasikan asersi (assert) di baris paling atas tugas role untuk memvalidasi tipe data, rentang angka, dan kelengkapan parameter kritis sebelum instalasi dimulai.
  • Keamanan Variabel Sensitif — Integrasikan parameterisasi role dengan sistem Ansible Vault untuk menjaga agar rahasia (secrets) tetap terenkripsi dan aman.
  • Desain Skalabilitas Data — Desain variabel masukan dalam bentuk daftar terstruktur (list of dictionaries) agar playbook pemanggil dapat melakukan perluasan kapasitas (scaling) dengan mudah.

← Sebelumnya: Reusability   Berikutnya: Versioning →

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