Scope & Precedence #
Variabel adalah fondasi utama dari otomatisasi yang dinamis dan fleksibel. Di dalam Ansible, kita menggunakan variabel untuk menyimpan nilai dinamis seperti nama paket, port layanan, jalur direktori, kunci API, hingga kredensial database. Namun, seiring dengan berkembangnya kompleksitas proyek otomatisasi, kita akan sering mendapati situasi di mana suatu variabel didefinisikan di beberapa tempat berbeda secara bersamaan. Ketika playbook dieksekusi, nilai variabel yang aktif ternyata tidak sesuai dengan yang kita harapkan.
Masalah tersebut hampir selalu disebabkan oleh kegagalan dalam memahami dua konsep vital: cakupan variabel (variable scope) dan urutan prioritas (variable precedence). Ansible memiliki salah satu sistem prioritas paling kompleks di dunia alat manajemen konfigurasi, dengan 22 tingkatan yang dievaluasi secara berurutan. Artikel ini akan membedah secara komprehensif bagaimana Ansible membatasi aksesibilitas variabel berdasarkan scope, menguraikan 22 level prioritas secara mendalam, membandingkan empat tingkat variabel yang paling sering digunakan, serta menyajikan strategi namespacing untuk menghindari tabrakan nama variabel di lingkungan produksi kita.
Tiga Cakupan Variabel (Variable Scopes) #
Sebelum kita mempelajari seberapa kuat suatu variabel dapat menimpa variabel lainnya, kita harus terlebih dahulu memahami batas-batas ruang lingkup di mana variabel tersebut hidup dan dapat diakses. Di Ansible, seluruh variabel dikelompokkan ke dalam tiga tingkat cakupan (scope):
flowchart TD
Global["Global Scope (Tersedia di semua host, play, dan role)<br/>Contoh: Extra vars via CLI (-e), Konfigurasi ansible.cfg"]
Play["Play Scope (Tersedia untuk semua host dalam satu play)<br/>Contoh: vars:, vars_files:, vars_prompt: di playbook"]
Host["Host Scope (Hanya tersedia untuk host spesifik tertentu)<br/>Contoh: host_vars/, group_vars/, facts host, registered vars"]
Global --> Play
Play --> Host
1. Global Scope #
Variabel dalam cakupan global dapat dibaca dari mana saja sepanjang eksekusi playbook berlangsung. Variabel ini tidak terikat pada host tertentu ataupun play tertentu. Sumber utama dari global scope adalah argumen baris perintah (command line extra variables) yang dimasukkan menggunakan opsi -e atau --extra-vars, serta konfigurasi internal dari file ansible.cfg.
2. Play Scope #
Variabel dalam cakupan play didefinisikan di dalam struktur play di dalam playbook. Variabel ini tersedia untuk semua host yang menjadi target dari play tersebut, serta semua role yang disertakan di dalamnya. Contohnya adalah variabel yang dideklarasikan di bawah opsi vars:, berkas variabel eksternal di bawah vars_files:, atau masukan pengguna yang bersifat interaktif di bawah vars_prompt:.
3. Host Scope #
Variabel dalam cakupan host bersifat sangat spesifik dan hanya terikat pada satu host tertentu saja. Meskipun kita mendefinisikannya di dalam grup (seperti berkas di dalam direktori group_vars/), Ansible secara internal mengaitkan variabel tersebut ke masing-masing host yang menjadi anggota grup itu saat eksekusi. Contoh lain dari host scope adalah data fakta sistem (host facts), variabel yang didefinisikan di direktori host_vars/, serta variabel hasil tangkapan tugas menggunakan kata kunci register.
22 Level Precedence di Ansible #
Ketika ada nama variabel yang identik didefinisikan di berbagai scope dan lokasi berbeda, Ansible menggunakan algoritma pemutus rantai yang sangat ketat untuk menentukan nilai mana yang akan digunakan. Algoritma ini terdiri dari 22 tingkat prioritas.
Berikut adalah daftar lengkap urutan prioritas variabel dari yang paling lemah (terendah) hingga yang paling kuat (tertinggi):
Prioritas Terendah (Paling Mudah Ditimpa):
1. command line values (Nilai argumen CLI bawaan, misalnya -u REMOTE_USER)
2. role defaults (Berkas defaults/main.yml di dalam suatu role)
3. inventory file atau script group vars (Variabel grup yang ditulis langsung di hosts.ini/yaml)
4. inventory group_vars/all (Variabel grup global all dari direktori inventaris)
5. playbook group_vars/all (Variabel grup global all dari direktori playbook)
6. inventory group_vars/* (Variabel grup spesifik dari direktori inventaris)
7. playbook group_vars/* (Variabel grup spesifik dari direktori playbook)
8. inventory file atau script host vars (Variabel host yang ditulis langsung di hosts.ini/yaml)
9. inventory host_vars/* (Variabel host spesifik dari direktori inventaris)
10. playbook host_vars/* (Variabel host spesifik dari direktori playbook)
11. host facts / cached set_facts (Data pengumpulan sistem atau set_fact yang di-cache)
12. play vars (Blok vars: di tingkat play dalam playbook)
13. play vars_prompt (Variabel interaktif vars_prompt: di tingkat play)
14. play vars_files (Berkas variabel vars_files: di tingkat play)
15. role vars (Berkas vars/main.yml di dalam suatu role)
16. block vars (Blok vars: yang ditempelkan pada struktur block)
17. task vars (Blok vars: yang ditempelkan langsung pada satu task tertentu)
18. include_vars (Variabel yang dimuat dinamis di tengah task menggunakan include_vars)
19. set_facts / registered vars (Variabel dinamis set_fact atau register saat runtime)
20. role (dan include_role) params (Argumen parameter yang dilewatkan saat memanggil role)
21. include params (Argumen parameter yang dilewatkan saat memanggil include_tasks/block)
22. extra vars (Variabel CLI yang dipass menggunakan -e atau --extra-vars) <-- Prioritas Tertinggi (Selalu Menang)
Visualisasi hierarki prioritas ini dapat kita gambarkan menggunakan diagram Mermaid berikut untuk memudahkan pemahaman alur penimpangan:
flowchart TD
subgraph LowPrecedence["Tingkat Lemah (Defaults & Inventory)"]
Level2["Role Defaults (defaults/main.yml)"] --> Level3["Inventory Group Vars (hosts.ini)"]
Level3 --> Level4["Inventory group_vars/all"]
Level4 --> Level6["Inventory group_vars/group_name"]
Level6 --> Level9["Inventory host_vars/host_name"]
end
subgraph MidPrecedence["Tingkat Menengah (Play & Role Vars)"]
Level9 --> Level11["Host Facts (setup module)"]
Level11 --> Level12["Playbook Vars (vars:)"]
Level12 --> Level14["Playbook vars_files"]
Level14 --> Level15["Role Vars (vars/main.yml)"]
end
subgraph HighPrecedence["Tingkat Kuat (Task, Runtime & Extra)"]
Level15 --> Level17["Task Vars (vars:)"]
Level17 --> Level19["set_fact & registered vars"]
Level19 --> Level20["Role Parameters (include_role vars)"]
Level20 --> Level22["Extra Vars (-e CLI parameter)"]
end
style Level2 stroke:#f43f5e,stroke-width:2px
style Level15 stroke:#eab308,stroke-width:2px
style Level22 stroke:#22c55e,stroke-width:2px
Empat Tingkat Variabel yang Paling Sering Digunakan #
Meskipun menghafal ke-22 tingkatan di atas terdengar sangat menakutkan, dalam pengembangan proyek sehari-hari kita sebenarnya hanya berinteraksi secara aktif dengan empat tingkat prioritas utama. Kita harus memahami fungsi taktis masing-masing tingkat ini agar dapat mendesain otomatisasi yang bersih.
1. Role Defaults (defaults/main.yml)
#
Berkas defaults/main.yml di dalam direktori role berada di tingkat 2 prioritas. Ini adalah lokasi penyimpanan variabel yang paling lemah di Ansible.
- Fungsi: Digunakan untuk menetapkan nilai default yang “aman” untuk seluruh variabel yang dibutuhkan oleh role kita.
- Filosofi: “Ini adalah nilai rekomendasi dari saya untuk penggunaan standar. Silakan timpa nilai ini jika infrastruktur kita memiliki konfigurasi berbeda.”
- Contoh:
# roles/nginx/defaults/main.yml --- nginx_port: 80 nginx_worker_connections: 1024 nginx_enable_ssl: false
2. Inventory Variables (group_vars/ dan host_vars/)
#
Variabel inventaris berada di tingkat 3 hingga 10 prioritas. Variabel ini menimpa default dari role secara otomatis.
- Fungsi: Digunakan untuk menyesuaikan konfigurasi berdasarkan lingkungan operasional (Development, Staging, Production) atau karakteristik host fisik tertentu.
- Filosofi: “Untuk grup server produksi, kita harus menggunakan port 443 dengan koneksi yang lebih besar. Nilai default role di atas harus disesuaikan.”
- Contoh:
# inventory/production/group_vars/webservers.yml --- nginx_port: 443 nginx_worker_connections: 4096 nginx_enable_ssl: true
3. Role Vars (vars/main.yml)
#
Berkas vars/main.yml berada di tingkat 15 prioritas. Ini adalah salah satu tingkat prioritas yang cukup tinggi dan sulit untuk ditimpa.
- Fungsi: Digunakan untuk menetapkan nilai konstanta internal yang menjadi dasar bekerjanya kode di dalam role tersebut. Variabel di sini tidak dimaksudkan untuk diubah oleh pengguna role dari luar.
- Filosofi: “Nilai ini adalah konfigurasi internal yang paten. Jika nilai ini diganti secara sembarangan, role kita berpotensi mengalami error sistem.”
- Contoh:
# roles/nginx/vars/main.yml --- nginx_config_dir: /etc/nginx nginx_pid_path: /var/run/nginx.pid nginx_binary_path: /usr/sbin/nginx
4. Extra Vars (-e di CLI)
#
Variabel ekstra yang dikirimkan melalui terminal CLI berada di tingkat 22 prioritas. Ini adalah prioritas mutlak yang tidak dapat ditimpa oleh apa pun.
- Fungsi: Digunakan untuk melakukan override sementara saat debugging, pengujian ad-hoc, atau eksekusi darurat.
- Filosofi: “Saya ingin menjalankan playbook ini sekarang secara khusus dengan parameter ini, abaikan semua konfigurasi bawaan yang tertulis di dalam file.”
- Contoh:
ansible-playbook -i inventory/ site.yml -e "nginx_port=8080 nginx_enable_ssl=false"
Analisis Komparatif: Perbedaan Filosofis Defaults vs Vars di Role #
Salah satu kebingungan terbesar yang sering dialami developer adalah perbedaan antara direktori defaults/ dan vars/ di dalam struktur Ansible Role. Keduanya sama-sama mendefinisikan variabel untuk role, tetapi memiliki tujuan yang bertolak belakang.
Tabel di bawah ini menjelaskan perbedaan tersebut agar kita tidak salah memilih lokasi penyimpanan:
| Karakteristik | defaults/main.yml |
vars/main.yml |
|---|---|---|
| Urutan Prioritas | Tingkat 2 (Sangat Lemah). | Tingkat 15 (Sangat Kuat). |
| Tujuan Penggunaan | Menyediakan nilai konfigurasi bawaan yang dapat dikustomisasi. | Menyediakan konstanta sistem, pemetaan OS, dan path internal. |
| Kemudahan Override | Sangat mudah ditimpa oleh inventory, playbook, vars_files, atau CLI. | Sangat sulit ditimpa (hanya bisa ditimpa oleh task vars, parameter role, atau CLI). |
| Kolaborasi Tim | Ditujukan untuk diekspos sebagai “API” atau input konfigurasi bagi pengguna role. | Ditujukan sebagai ruang privat untuk logika internal pembuat role. |
Perbandingan Kode Anti-Pattern vs Solusi Praktis #
Mari kita amati kesalahan dalam menaruh variabel yang seharusnya dinamis ke dalam berkas vars/main.yml (anti-pattern) dan bagaimana cara memperbaikinya:
# ANTI-PATTERN: Menaruh variabel port di vars/main.yml
# BERKAS: roles/nginx/vars/main.yml
---
nginx_port: 80
# ✗ Kesalahan: Jika pengguna role ingin mengubah port webserver menjadi 8080
# melalui file inventory group_vars/webservers.yml, nilai tersebut akan DIABAIKAN.
# Prioritas vars/main.yml (Level 15) jauh lebih tinggi daripada group_vars/ (Level 6).
# SOLUSI: Pisahkan variabel dinamis ke defaults/ dan konstanta ke vars/
# BERKAS: roles/nginx/defaults/main.yml
---
nginx_port: 80
# ✓ Benar: Pengguna dapat dengan mudah menimpa nilai ini dari inventory.
# BERKAS: roles/nginx/vars/main.yml
---
nginx_system_user: www-data
nginx_mime_types_path: /etc/nginx/mime.types
# ✓ Benar: Konstanta sistem operasi ini aman dari penimpaan tidak sengaja.
Mitigasi Bentrokan Nama Variabel (Namespacing) #
Karena Ansible mengevaluasi variabel secara flat di akhir play untuk setiap host, ada risiko besar terjadinya bentrokan nama variabel (variable namespace collision) jika kita menggunakan nama variabel yang terlalu umum di beberapa role yang berbeda.
Sebagai contoh, jika kita memiliki role mysql dan role nginx, dan keduanya sama-sama mendefinisikan variabel bernama port di dalam berkas defaults-nya:
mysqlmendefinisikanport: 3306nginxmendefinisikanport: 80
Ketika kedua role tersebut dijalankan dalam satu play pada host yang sama, salah satu nilai port akan menimpa nilai port lainnya tergantung pada urutan pemanggilan role. Hal ini akan menyebabkan salah satu layanan terkonfigurasi dengan port yang salah secara fatal.
Taktik Solusi: Menerapkan Prefiks Role (Namespacing) #
Untuk menghindari bencana ini di lingkungan produksi, kita wajib menerapkan aturan penulisan variabel yang disiplin dengan menyertakan nama role sebagai awalan (prefix) untuk semua variabel yang dideklarasikan.
# ANTI-PATTERN: Nama variabel terlalu umum dan rawan bentrok
# Di dalam role mysql:
port: 3306
config_file: /etc/my.cnf
# Di dalam role nginx:
port: 80
config_file: /etc/nginx/nginx.conf
# BENAR: Menerapkan namespacing berbasis nama role
# Di dalam role mysql:
mysql_port: 3306
mysql_config_file: /etc/my.cnf
# Di dalam role nginx:
nginx_port: 80
nginx_config_file: /etc/nginx/nginx.conf
# ✓ Aman: Tidak akan ada tabrakan variabel meskipun kedua role berjalan bersamaan.
Mengamankan Nilai Variabel dengan Filter default
#
Terkadang kita ingin memastikan playbook kita tetap dapat berjalan dengan aman meskipun ada variabel tertentu yang belum didefinisikan sama sekali di file inventaris atau role. Kita bisa mencegah kesalahan sistem terhenti (undefined variable error) dengan memanfaatkan filter Jinja2 default.
# Mengamankan eksekusi dengan menyediakan nilai cadangan dinamis
- name: Membuat file konfigurasi custom
template:
src: custom_settings.conf.j2
dest: /opt/app/settings.conf
vars:
# Jika variabel 'app_max_memory' tidak didefinisikan di mana pun,
# gunakan nilai default cadangan '512m'.
max_mem: "{{ app_max_memory | default('512m') }}"
Penggunaan filter default ini sangat disarankan untuk menjaga ketahanan (resiliency) dari template Jinja2 dan modul tugas kita.
Ringkasan #
- Tiga Scope Utama: Variabel Ansible terisolasi dalam tiga tingkatan ruang lingkup yaitu cakupan Global, Play, dan Host.
- 22 Tingkat Precedence: Ansible memiliki aturan evaluasi prioritas 22 level, yang berjalan dari prioritas terendah (
role defaults) hingga tertinggi (extra vars -e).- Filosofi
defaults/: Gunakan berkasdefaults/main.ymluntuk mendeklarasikan nilai bawaan role yang dimaksudkan agar dapat ditimpa dengan mudah dari luar.- Filosofi
vars/: Gunakan berkasvars/main.ymluntuk menyimpan nilai konstanta internal role yang tidak boleh diganti secara sembarangan oleh pengguna.- Namespacing: Selalu berikan prefiks nama role pada semua variabel (contoh:
nginx_portalih-alihport) demi mencegah tabrakan nama variabel lintas role.- Filter Default: Gunakan filter
| default()di dalam template Jinja2 untuk menyediakan nilai cadangan aman jika variabel target belum sempat didefinisikan.