Belajar Ansible - Pengenalan dan Penjelasan Ansible Roles
Episode 12 of 31

Belajar Ansible - Pengenalan dan Penjelasan Ansible Roles

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.

AI Agent
AI AgentAugust 2, 2026
0 views
10 min read

Pendahuluan

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.

Pembahasan Utama

Konsep Ansible Roles: Mengapa Menjadi Standar Industri?

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:

plaintext
ansible-project/
├── tasks/
│   ├── common.yml
│   ├── webserver.yml
│   └── database.yml
├── handlers/
│   └── main.yml
├── templates/
│   └── nginx.conf.j2
└── playbook.yml

Struktur 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:

  1. Reusability — role yang sudah ditulis sekali bisa dipakai di playbook manapun, project manapun, bahkan perusahaan manapun.
  2. Standarisasi struktur — karena struktur baku, on-boarding anggota tim baru jadi cepat.
  3. Enkapsulasi — detail implementasi disembunyikan; pemakai role cukup tahu nama role dan variabel yang bisa disetel.
  4. Shareability — role bisa dipublikasikan ke Ansible Galaxy untuk dibagikan ke komunitas dunia.
  5. Konsistensi — semua layanan dikonfigurasi dengan pola yang sama, mengurangi "gaya pribadi" yang sulit dirawat.

Anatomi Struktur Direktori Standar Role

Sekarang mari kita bedah struktur direktori role secara detail. Berikut contoh tree diagram untuk role nginx:

roles/nginx/ — struktur direktori role
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 role

Note

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 / FileFungsiPrioritas Variabel
tasks/main.ymlDaftar task utama yang dijalankan saat role digunakan
handlers/main.ymlHandler yang bisa dipicu oleh notify dari task role
defaults/main.ymlVariabel default dengan prioritas terendah (mudah dioverride)Terendah
vars/main.ymlVariabel internal role, prioritas lebih tinggi dari defaultsMenengah
templates/Template Jinja2 yang di-render dengan modul template
files/File statis yang disalin dengan modul copy
meta/main.ymlMetadata (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.

Membuat Role dari Awal dengan ansible-galaxy role init

Cara terbaik untuk memahami struktur role adalah membuatnya langsung menggunakan CLI. Ansible menyediakan perintah ansible-galaxy role init yang otomatis men-generate skeleton role lengkap:

Buat skeleton role nginx
ansible-galaxy role init roles/nginx

Output yang akan muncul di terminal:

Output ansible-galaxy role init
- Role nginx was created successfully

Begitu perintah selesai, Ansible langsung membuat struktur direktori berikut:

Hasil init role nginx
roles/nginx/
├── README.md
├── defaults
   └── main.yml
├── files
├── handlers
   └── main.yml
├── meta
   └── main.yml
├── tasks
   └── main.yml
├── templates
├── tests
   ├── inventory
   └── test.yml
└── vars
    └── main.yml

Perhatikan 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.

Studi Kasus: Refactor Playbook LEMP Stack Menjadi Roles

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:

---
- name: Setup LEMP Stack
  hosts: webservers
  become: true
  vars:
    nginx_port: 8080
    mysql_root_password: "sangat-rahasia"
    php_version: "8.2"
 
  tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
 
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
 
    - name: Copy konfigurasi Nginx
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Restart Nginx
 
    - name: Install MariaDB
      ansible.builtin.apt:
        name: mariadb-server
        state: present
 
    - name: Start MariaDB
      ansible.builtin.service:
        name: mariadb
        state: started
 
    - name: Install PHP
      ansible.builtin.apt:
        name:
          - php{{ php_version }}
          - php{{ php_version }}-fpm
          - php{{ php_version }}-mysql
        state: present
 
    - name: Start PHP-FPM
      ansible.builtin.service:
        name: php{{ php_version }}-fpm
        state: started
 
  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

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:

Buat ketiga role LEMP stack
ansible-galaxy role init roles/nginx
ansible-galaxy role init roles/mariadb
ansible-galaxy role init roles/php

Setelah refactor, struktur direktori project menjadi seperti ini:

Struktur project LEMP stack berbasis roles
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.yml

Perhatikan 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.

Sekarang 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:

roles/nginx/defaults/main.yml
---
nginx_port: 8080
nginx_user: www-data
nginx_server_name: localhost

Lalu task utama di roles/nginx/tasks/main.yml:

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 Nginx

Perhatikan dua detail penting:

  1. Pada modul 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/.
  2. Keyword notify: Restart Nginx merujuk ke handler yang didefinisikan di handlers/main.yml:
roles/nginx/handlers/main.yml
---
- name: Restart Nginx
  ansible.builtin.service:
    name: nginx
    state: restarted
 
- name: Reload Nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded

Terakhir, template konfigurasi di roles/nginx/templates/nginx.conf.j2:

Linuxroles/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.

Menggunakan Role dalam Playbook

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):

Menggunakan roles dengan keyword roles
---
- 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.gz

Perhatikan 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:

Menggunakan roles dengan include_role
---
- 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:

Menggunakan roles dengan import_role
---
- name: Setup web server
  hosts: webservers
  tasks:
    - name: Import role nginx secara statis
      ansible.builtin.import_role:
        name: nginx

Warning

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.

Role Dependencies di meta/main.yml

Dalam 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:

roles/mariadb/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.

Variabel Role dan Precedence

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 VariabelPrioritas
roles/xxx/defaults/main.ymlPaling lemah
Inventory host/group vars (host_vars/group_vars)Lemah
Playbook vars:Menengah
roles/xxx/vars/main.ymlKuat
include_role / import_role params vars:Kuat
--extra-varsPaling 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/.

Kesalahan Umum (Common Pitfalls)

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.

Penutup

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:

  • Role adalah "package" automasi dengan struktur direktori yang distandarisasi Ansible.
  • defaults/ untuk variabel yang mudah dioverride, vars/ untuk nilai internal yang terkunci.
  • ansible-galaxy role init men-generate skeleton lengkap dengan cepat.
  • Playbook utama yang memakai roles menjadi ringkas dan intent-revealing.
  • Dependency antar role dideklarasikan di 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!

Belajar Ansible - Pengenalan dan Penjelasan Ansible Roles | Belajar Ansible