Pahami mengapa Ansible Roles menjadi standar industri untuk mengemas automasi. Pelajari struktur direktori standar role, membuat role dengan ansible-galaxy role init, serta studi kasus refactor playbook LEMP stack menjadi roles modular.

Setelah di episode 11 sebelumnya kita membahas modularisasi playbook menggunakan include_tasks, import_tasks, dan import_playbook, pada episode kali ini kita akan membahas evolusi alami dari konsep tersebut: Ansible Roles.
Di episode 11 kita memecah playbook besar menjadi file-file task kecil di dalam direktori tasks/. Itu langkah yang bagus, tapi coba bayangkan kalian harus membagikan tasks/webserver.yml itu ke tim lain atau project lain. Kalian harus menjelaskan "jangan lupa ikutkan juga file handler-nya, template-nya, dan variabel default-nya". Tanpa aturan baku, setiap orang akan mengemas hal yang sama dengan cara berbeda, dan akhirnya automasi jadi sulit dipakai ulang antar project.
Dalam dunia kerja nyata, prinsip ini dikenal sebagai standarisasi: jika semua orang mengikuti struktur folder yang sama, maka siapa pun bisa mengambil role orang lain dan langsung memakainya tanpa perlu membaca dokumentasi yang panjang. Inilah alasan mengapa Roles menjadi standar industri. Mirip seperti bagaimana npm mengatur package JavaScript atau pip mengatur package Python, Roles adalah cara Ansible mengemas automasi menjadi unit yang lengkap, mandiri, dan siap dibagikan.
Pada episode ini kita akan membahas apa itu role, mengapa role penting, anatomi struktur direktori standarnya, cara membuat role dari CLI menggunakan ansible-galaxy role init, studi kasus mengubah playbook LEMP stack monolith menjadi roles modular, sampai fitur dependensi antar role di meta/main.yml.
Sebelum memahami roles, mari kita refleksikan masalah yang ingin dipecahkan. Di episode 11, kita membagi playbook menjadi beberapa file task. Strukturnya kurang lebih seperti ini:
ansible-project/
├── tasks/
│ ├── common.yml
│ ├── webserver.yml
│ └── database.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
└── playbook.ymlStruktur seperti ini sudah jauh lebih rapi, tetapi punya kelemahan fundamental: tidak ada konvensi yang mengikat. Apa nama file task untuk instalasi webserver? Apakah handler diletakkan di folder handlers/? Bagaimana dengan variabel default dan template yang menyertainya? Semuanya terserah masing-masing orang, sehingga ketika satu project dibagikan ke project lain, orang lain harus menebak-nebak.
Roles menyelesaikan masalah ini dengan satu ide sederhana: konvensi lokasi. Role adalah struktur direktori yang sudah ditentukan nama dan isinya secara baku oleh Ansible. Ketika kalian melihat sebuah direktori bernama roles/nginx, kalian langsung tahu bahwa:
tasks/main.yml berisi daftar task utama role tersebut.handlers/main.yml berisi handler yang dibutuhkan role.defaults/main.yml berisi variabel default yang bisa dioverride.templates/ berisi template Jinja2.files/ berisi file statis.meta/main.yml berisi metadata dan dependensi role.Bayangkan seperti resep masakan dalam kotak. Resep biasa ditulis di secarik kertas yang mudah hilang dan setiap orang menulis formatnya beda-beda. Roles adalah kotak berisi resep lengkap, daftar bahan, alat yang dibutuhkan, dan langkah memasak, semuanya dengan label yang standar. Siapa pun yang menerima kotak tersebut langsung tahu cara memakainya.
Keuntungan utama roles dalam produksi:
Sekarang mari kita bedah struktur direktori role secara detail. Berikut contoh tree diagram untuk role nginx:
roles/
└── nginx/
├── tasks/
│ ├── main.yml # Entry point: daftar task utama (wajib ada)
│ └── ...
├── handlers/
│ └── main.yml # Handler yang bisa di-notify oleh task role
├── defaults/
│ └── main.yml # Variabel default paling rendah prioritasnya
├── vars/
│ └── main.yml # Variabel internal role, prioritas lebih tinggi
├── templates/
│ └── nginx.conf.j2 # Template Jinja2 untuk konfigurasi dinamis
├── files/
│ └── index.html # File statis untuk di-copy ke target
├── meta/
│ └── main.yml # Metadata & role dependencies
├── library/ # (opsional) Custom module khusus role
├── module_utils/ # (opsional) Helper untuk custom module
├── plugins/ # (opsional) Plugin khusus role
└── README.md # (opsional) Dokumentasi roleNote
Hanya tasks/main.yml yang benar-benar wajib ada dalam sebuah role. Direktori lainnya bersifat opsional dan hanya perlu dibuat jika memang digunakan. Namun untuk role yang rapi dan production-grade, biasanya semua direktori yang relevan tetap disediakan, meskipun isinya tipis.
Setiap direktori memiliki peran yang jelas. Berikut rangkumannya:
| Direktori / File | Fungsi | Prioritas Variabel |
|---|---|---|
tasks/main.yml | Daftar task utama yang dijalankan saat role digunakan | — |
handlers/main.yml | Handler yang bisa dipicu oleh notify dari task role | — |
defaults/main.yml | Variabel default dengan prioritas terendah (mudah dioverride) | Terendah |
vars/main.yml | Variabel internal role, prioritas lebih tinggi dari defaults | Menengah |
templates/ | Template Jinja2 yang di-render dengan modul template | — |
files/ | File statis yang disalin dengan modul copy | — |
meta/main.yml | Metadata (galaxy_info, author) & dependensi role | — |
Poin penting yang sering disalahpahami adalah perbedaan antara defaults/ dan vars/. Keduanya berisi variabel, tapi tujuannya berbeda:
defaults/main.yml — berisi nilai bawaan yang sengaja dibuat mudah untuk dioverride oleh pemakai role. Variabel di sini punya prioritas paling rendah dibanding seluruh hierarki prioritas Ansible, sehingga pemakai role bisa menggantinya dari group_vars, host_vars, --extra-vars, dan sebagainya.vars/main.yml — berisi nilai internal yang sebaiknya tidak diganggu pemakai role, misalnya versi package tertentu yang sudah diuji, atau path internal. Prioritasnya lebih tinggi dari group_vars, jadi mengubahnya lewat group_vars tidak akan berpengaruh.ansible-galaxy role initCara terbaik untuk memahami struktur role adalah membuatnya langsung menggunakan CLI. Ansible menyediakan perintah ansible-galaxy role init yang otomatis men-generate skeleton role lengkap:
ansible-galaxy role init roles/nginxOutput yang akan muncul di terminal:
- Role nginx was created successfullyBegitu perintah selesai, Ansible langsung membuat struktur direktori berikut:
roles/nginx/
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.ymlPerhatikan bahwa ansible-galaxy role init juga membuat direktori tests/ berisi inventory dan playbook test sederhana. Direktori ini berguna nanti saat kalian mempelajari testing role dengan Molecule di episode 18.
Tip
Ada beberapa opsi tambahan yang berguna untuk ansible-galaxy role init. Gunakan --init-path <path> untuk menentukan lokasi pembuatan, --force untuk menimpa direktori yang sudah ada, dan --role-skeleton <path> untuk memakai skeleton custom milik tim kalian sendiri. Skeleton custom sangat umum di perusahaan untuk memasukkan template README dan lisensi khas tim.
Teori memang penting, tapi kita perlu melihat bagaimana role memecahkan masalah nyata. Mari kita ambil studi kasus klasik: setup LEMP Stack (Linux, Nginx, MySQL/MariaDB, PHP). Berikut perbandingan playbook monolith sebelum dan sesudah refactor menjadi roles:
Playbook monolith di atas bekerja, tetapi punya semua masalah yang sudah kita bahas: sulit dibaca, sulit diuji per-komponen, dan tidak bisa dipakai ulang. Jika besok tim butuh setup LEMP di project lain, seluruh blok harus di-copy-paste. Sebaliknya, playbook hasil refactor menjadi sangat ringkas dan intent-revealing: dari sekadar membacanya, kalian langsung paham komponen apa saja yang dikelola.
Sekarang mari kita refactor playbook tersebut menjadi struktur roles. Pertama, inisialisasi tiga role:
ansible-galaxy role init roles/nginx
ansible-galaxy role init roles/mariadb
ansible-galaxy role init roles/phpSetelah refactor, struktur direktori project menjadi seperti ini:
ansible-lemp/
├── ansible.cfg
├── inventory.yml
├── site.yml
├── group_vars/
│ └── all.yml
└── roles/
├── nginx/
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ ├── defaults/main.yml
│ ├── templates/nginx.conf.j2
│ └── meta/main.yml
├── mariadb/
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ ├── defaults/main.yml
│ └── meta/main.yml
└── php/
├── tasks/main.yml
├── handlers/main.yml
├── defaults/main.yml
└── meta/main.ymlPerhatikan perbedaannya. Playbook utama yang tadinya puluhan baris sekarang hanya tiga baris. Seluruh detail implementasi pindah ke masing-masing role, di mana setiap role bisa diuji, dipelihara, dan dipakai ulang secara independen. Inilah esensi dari modularisasi yang sesungguhnya.
nginxSekarang mari kita isi role nginx dengan implementasi lengkap. Ini akan menunjukkan bagaimana task, handler, defaults, dan template bekerja sama dalam satu paket.
Pertama, variabel default role di roles/nginx/defaults/main.yml:
---
nginx_port: 8080
nginx_user: www-data
nginx_server_name: localhostLalu task utama di roles/nginx/tasks/main.yml:
---
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Copy konfigurasi Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
notify: Restart NginxPerhatikan dua detail penting:
template, src cukup ditulis nginx.conf.j2 tanpa prefix roles/nginx/templates/. Ansible otomatis mencari file template di dalam direktori templates/ role yang sedang aktif. Hal yang sama berlaku untuk modul copy yang mencari file di files/.notify: Restart Nginx merujuk ke handler yang didefinisikan di handlers/main.yml:---
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restarted
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloadedTerakhir, template konfigurasi di roles/nginx/templates/nginx.conf.j2:
server {
listen {{ nginx_port }};
server_name {{ nginx_server_name }};
root /var/www/html;
index index.php index.html;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
}Important
Di dalam role, variabel yang didefinisikan di defaults/main.yml (seperti nginx_port) otomatis tersedia untuk seluruh task, template, dan handler di dalam role tersebut. Tidak perlu lagi mendefinisikan vars: di playbook — ini adalah salah satu keunggulan besar role: pemakai cukup mensetel variabel default yang ingin dioverride, sisanya dikelola oleh role.
Ada tiga cara menggunakan role dalam playbook, dan masing-masing punya perilaku yang sedikit berbeda.
Cara pertama: keyword roles:. Ini yang paling umum dan paling sederhana. Role dijalankan sebelum task-task biasa di play (urutan: pre_tasks → roles → tasks → post_tasks):
---
- name: Deploy aplikasi web
hosts: webservers
become: true
roles:
- role: nginx
vars:
nginx_port: 8443
- role: mariadb
tasks:
- name: Deploy aplikasi custom
ansible.builtin.copy:
src: files/app.tar.gz
dest: /opt/app/app.tar.gzPerhatikan bahwa kita bisa memberikan variabel khusus per-role menggunakan sintaks role: <nama> dengan vars: di bawahnya. Ini memungkinkan pemakaian role yang sama dengan konfigurasi berbeda di play yang berbeda.
Cara kedua: keyword include_role (dinamis). Role di-load saat runtime, mirip include_tasks. Berguna ketika nama role bergantung pada variabel, atau ingin di-loop:
---
- name: Terapkan role berdasarkan variabel
hosts: all
tasks:
- name: Include role app sesuai environment
ansible.builtin.include_role:
name: "{{ app_role }}"Cara ketiga: keyword import_role (statis). Role di-proses saat parsing playbook, mirip import_tasks. Keuntungannya, task di dalam role terlihat pada --list-tasks:
---
- name: Setup web server
hosts: webservers
tasks:
- name: Import role nginx secara statis
ansible.builtin.import_role:
name: nginxWarning
Perbedaan besar antara ketiga cara ini: dengan keyword roles: dan import_role (statis), isi role langsung terlihat saat menjalankan ansible-playbook site.yml --list-tasks, sehingga operasional seperti --start-at-task dan filter --tags bekerja andal. Dengan include_role (dinamis), isi role hanya diketahui saat runtime. Gunakan keyword roles: sebagai default, dan beralih ke include_role hanya ketika kalian benar-benar membutuhkan fleksibilitas nama role yang dinamis atau looping.
meta/main.ymlDalam dunia nyata, role sering saling bergantung. Contohnya, role php mungkin membutuhkan Nginx tidak harus ada, tapi role mariadb pasti butuh paket dasar terinstall. Untuk menangani ini, Ansible menyediakan deklarasi role dependencies di meta/main.yml:
---
galaxy_info:
author: arman
description: Role untuk setup MariaDB
license: MIT
min_ansible_version: "2.16"
dependencies:
- role: common
vars:
common_packages:
- curl
- gnupg
- role: apt-transport-https
when: ansible_facts['os_family'] == "Debian"Ketika role mariadb dipakai, Ansible otomatis menjalankan role common dan apt-transport-https terlebih dahulu (sesuai urutan dalam list). Kita juga bisa mem-passing variabel ke role dependency serta menambahkan kondisi when untuk memastikan dependency hanya dijalankan di OS tertentu.
Tip
Hati-hati dengan dependency yang berlebihan. Setiap role dependency akan menambah waktu eksekusi dan memperbesar kemungkinan konflik variabel. Aturan praktisnya: hanya deklarasikan dependency yang benar-benar dibutuhkan, dan pastikan dependency tersebut juga idempotent.
Karena role adalah blok logika yang dipakai ulang, memahami bagaimana variabel bekerja di dalamnya sangat krusial. Di episode 7 kita sudah membahas hierarki precedence variabel. Di dalam role, defaults punya prioritas paling rendah dan vars punya prioritas yang cukup tinggi. Berikut gambaran singkat urutan (dari terlemah ke terkuat):
| Sumber Variabel | Prioritas |
|---|---|
roles/xxx/defaults/main.yml | Paling lemah |
Inventory host/group vars (host_vars/group_vars) | Lemah |
Playbook vars: | Menengah |
roles/xxx/vars/main.yml | Kuat |
include_role / import_role params vars: | Kuat |
--extra-vars | Paling kuat |
Konsekuensi praktisnya: jika kalian ingin variabel role mudah dioverride oleh pemakai (misalnya port, domain, versi package), tempatkan di defaults/. Jika kalian ingin nilai tersebut "terkunci" dan sulit diubah, tempatkan di vars/.
1. Menganggap semua direktori role wajib ada
Hanya tasks/main.yml yang wajib. Membuat templates/ dan files/ kosong tidak masalah, tapi meta/main.yml kosong juga wajar selama tidak ada dependency. Jangan membuat direktori yang tidak terpakai hanya karena ikut-ikutan.
2. Menulis src dengan path absolut di modul template/copy
Di dalam role, src: nginx.conf.j2 akan otomatis dicari di roles/nginx/templates/. Menulis src: roles/nginx/templates/nginx.conf.j2 tidak hanya berlebihan, tapi juga rawan rusak ketika role dipindahkan atau dipasang dari Galaxy.
3. Mendefinisikan variabel role di playbook vars: padahal seharusnya di defaults/
Variabel yang didefinisikan di playbook akan menimpa defaults, dan membuat role tersebut tidak lagi "portable". Best practice-nya: nilai bawaan di defaults/, nilai environment-specific di group_vars atau --extra-vars.
4. Meletakkan variabel rahasia di vars/ lalu meng-commit ke Git
vars/ dan defaults/ sama-sama versi-able. Jangan pernah menaruh password atau API key di dalamnya. Kita akan membahas solusi yang benar, yaitu Ansible Vault, secara mendalam di episode 14.
5. Mengabaikan meta/main.yml saat role akan dibagikan
Jika role akan dipublikasikan ke Ansible Galaxy, meta/main.yml berisi galaxy_info yang menampilkan informasi author, lisensi, dan versi Ansible minimum. Role tanpa metadata yang jelas sulit ditemukan dan dipercaya komunitas.
6. Nested roles terlalu dalam
Role yang memanggil role lain memanggil role lain lagi akan sangat sulit di-debug. Jika hirarki dependency sudah tiga level ke bawah, pertimbangkan untuk menggabungkan atau mendesain ulang.
Pada episode 12 ini kita telah belajar bahwa Ansible Roles adalah standar industri untuk mengemas automasi karena membawa konvensi struktur yang baku, enkapsulasi yang rapi, dan reusability yang tinggi. Kita sudah membedah anatomi struktur direktori role mulai dari tasks, handlers, defaults, vars, templates, files, hingga meta, memahami perbedaan defaults dan vars, membuat skeleton role dengan ansible-galaxy role init, mengubah playbook LEMP stack monolith menjadi tiga role modular, serta mempelajari cara memakai role melalui keyword roles:, include_role, dan import_role beserta deklarasi role dependencies.
Poin kunci yang perlu kalian bawa pulang:
defaults/ untuk variabel yang mudah dioverride, vars/ untuk nilai internal yang terkunci.ansible-galaxy role init men-generate skeleton lengkap dengan cepat.meta/main.yml.Sekarang kalian memiliki bekal untuk mengorganisir automasi secara profesional. Namun, ketika kalian mulai menggunakan role buatan orang lain atau membutuhkan modul khusus cloud (misalnya AWS, Kubernetes), kalian akan menemukan istilah Collections — sebuah konsep yang lebih modern dan lebih besar dari sekadar role.
Di episode 13 selanjutnya kita akan membahas Pengenalan dan Penjelasan Ansible Collections & Galaxy — bagaimana ekosistem Ansible berevolusi dari ansible classic menjadi ansible-core + ansible-community, apa itu namespace dan FQCN, serta bagaimana mencari dan menginstall collection dari Ansible Galaxy. Pastikan tetap semangat!