SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-28

Ansible Playbook vs Role: Kapan Menggunakan Masing-Masing

Pelajari kapan playbook datar sudah cukup dan kapan role diperlukan, termasuk struktur direktori, ansible-galaxy init, pemanggilan role, serta prioritas variabel.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Perbedaan antara playbook dan role Ansible

Ansible playbook adalah file yang Anda jalankan dengan ansible-playbook. File ini memetakan sekelompok host ke pekerjaan yang harus dilakukan. Ansible role adalah direktori dengan tata letak tetap yang berisi task, template, handler, dan variabel default. Playbook memanggil role berdasarkan namanya. Sintaks task di dalam keduanya sama. Jadi, ini bukan persoalan tentang hal yang dapat Anda nyatakan. Ini adalah persoalan penggunaan ulang.

Mulailah dengan playbook datar. Satu site.yml yang berisi daftar tasks: adalah bentuk yang tepat untuk otomatisasi pertama Anda. Bentuk ini juga tetap tepat lebih lama daripada yang diperkirakan kebanyakan orang. Ubah menjadi role ketika blok task yang sama harus dijalankan untuk kelompok host kedua, atau ketika file telah bertambah hingga lebih dari sekitar 100 baris dan Anda tidak lagi dapat menemukan task hanya dengan menggulir.

Jika Anda belum pernah menulisnya, mulai dengan playbook pertama pada satu VPS dan kembali ke sini ketika ukurannya mulai bertambah.

Kapan playbook datar merupakan pilihan yang tepat

Playbook datar tepat digunakan ketika pekerjaan hanya dilakukan sekali, hanya pada satu host, atau tidak akan dibaca orang lain. Menyediakan satu application server atau melakukan patch pada sebuah host sebelum maintenance window tidak memerlukan struktur direktori. Sebuah role menambahkan tujuh direktori dan satu lapisan indireksi. Jika satu-satunya pemanggilnya adalah playbook yang berada di sebelahnya, indireksi tersebut tidak memberikan manfaat dan membuat Anda harus berpindah setiap kali ingin membaca hal yang benar-benar dijalankan.

Playbook datar tidak lagi tepat pada saat tertentu, dan saat itu mudah dikenali. Anda menyalin satu blok task ke playbook kedua. Salinan tersebut merupakan tandanya. Sejak saat itu, setiap perbaikan harus dilakukan dua kali, dan suatu hari perbaikan itu hanya akan dilakukan sekali.

Isi direktori role yang sebenarnya

roles/common/
  defaults/main.yml
  vars/main.yml
  tasks/main.yml
  handlers/main.yml
  templates/99-hardening.conf.j2
  files/
  meta/main.yml
  • tasks/main.yml adalah titik masuk. Ansible menjalankan file ini saat role dipanggil, dan semua direktori lainnya bersifat opsional.
  • defaults/main.yml menyimpan variabel yang diharapkan dapat ditimpa oleh pemanggil. Ini adalah sumber dengan prioritas terendah di Ansible, sehingga hampir semua sumber lain memiliki prioritas lebih tinggi.
  • vars/main.yml menyimpan variabel yang tidak diharapkan dapat ditimpa oleh pemanggil. Prioritasnya lebih tinggi daripada inventory, sehingga penggunaannya harus benar-benar dipertimbangkan. Gunakan sesedikit mungkin.
  • handlers/main.yml menyimpan task yang dipicu oleh notify. Handler dijalankan satu kali di akhir play, terlepas dari berapa banyak task yang memberinya notifikasi.
  • files/ menyimpan file yang disalin apa adanya oleh modul copy, sedangkan templates/ menyimpan template Jinja2 yang dirender oleh modul template. Di dalam role, Anda merujuk keduanya hanya dengan nama file tanpa path, karena Ansible terlebih dahulu mencari di direktori milik role tersebut.
  • meta/main.yml mendeklarasikan dependensi role dan metadata yang dibaca oleh Ansible Galaxy.

Struktur ini bukan sekadar preferensi gaya. Ansible mencari file pada path yang tepat tersebut, sehingga template yang Anda letakkan di roles/common/template/ (tunggal) tidak akan pernah ditemukan.

Buat role common dengan ansible-galaxy init

mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles common

Perintah tersebut menulis seluruh kerangka role di bawah roles/common, termasuk direktori yang tidak akan digunakan dan stub main.yml yang hanya berisi ---. Hapus direktori yang dibiarkan kosong. vars/main.yml yang kosong tidak bermasalah bagi Ansible, tetapi menyamarkan file dalam role yang benar-benar penting.

Sekarang isi file yang menjalankan tugasnya. Mulai dari defaults karena bagian ini merupakan antarmuka publik role.

# roles/common/defaults/main.yml
---
common_packages:
  - ufw
  - fail2ban
  - unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"

Berikan tanda kutip pada "no" dan "yes". Ansible mengurai YAML dengan PyYAML, yang membaca no tanpa tanda kutip sebagai boolean false. Akibatnya, baris konfigurasi yang dihasilkan menjadi PermitRootLogin False dan sshd menolaknya. Tanda kutip mempertahankan nilai tersebut sebagai string.

# roles/common/tasks/main.yml
---
- name: Install the base packages
  ansible.builtin.apt:
    name: "{{ common_packages }}"
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Create the admin group
  ansible.builtin.group:
    name: "{{ common_admin_group }}"
    state: present

- name: Install the sshd hardening drop-in
  ansible.builtin.template:
    src: 99-hardening.conf.j2
    dest: /etc/ssh/sshd_config.d/99-hardening.conf
    owner: root
    group: root
    mode: "0644"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd
# roles/common/handlers/main.yml
---
- name: Restart sshd
  ansible.builtin.service:
    name: ssh
    state: restarted
# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}

Pada Debian dan Ubuntu, unit systemd bernama ssh, sedangkan pada sistem keluarga RHEL namanya sshd. Handler yang menggunakan nama unit yang salah hanya gagal ketika sesuatu benar-benar mengubah template. Karena itu, masalah ini biasanya baru muncul beberapa minggu kemudian.

Baris validate merupakan bagian yang paling berguna dalam task tersebut. Ansible merender template ke file sementara, mengganti %s dengan path file tersebut, lalu menjalankan perintah. File tujuan hanya diganti jika perintah selesai dengan status 0. Masukkan direktif yang tidak valid ke dalam template, lalu jalankan kembali task tersebut. Task akan gagal dengan failed to validate, /etc/ssh/sshd_config.d/99-hardening.conf yang sebenarnya tetap tidak berubah, dan Anda masih memiliki server yang dapat digunakan untuk login. Perhatikan bahwa pemeriksaan ini menguji lebih dari sekadar sintaks. Jika sshd -t tidak dapat membaca host key, perintah tersebut selesai dengan sshd: no hostkeys available -- exiting. dan Ansible melaporkan failed to validate yang sama. Karena itu, baca msg milik module sebelum menyalahkan template.

Cara playbook memanggil role

# site.yml
---
- name: Base configuration for every server
  hosts: all
  become: true
  roles:
    - common
# inventory.ini
[local]
localhost ansible_connection=local
ansible-playbook -i inventory.ini site.yml

Play harus diakhiri dengan failed=0 dalam rekapitulasi. Teruskan parameter pada lokasi pemanggilan menggunakan bentuk lengkap. Dengan cara ini, satu role dapat digunakan untuk dua kelompok host:

  roles:
    - role: common
      common_admin_group: ops
      common_permit_root_login: prohibit-password

Ada satu aturan urutan yang mengejutkan hampir semua orang. Sebuah play dapat memuat pre_tasks, roles, tasks, dan post_tasks, lalu Ansible menjalankannya dalam urutan tersebut, apa pun urutan penulisannya di dalam file. Letakkan tasks: di atas roles:, dan role tetap dijalankan terlebih dahulu. Jadi, jika sesuatu harus dijalankan sebelum sebuah role, masukkan ke pre_tasks:, bukan di bagian atas tasks:.

- name: Ordering demonstration
  hosts: local
  gather_facts: false
  pre_tasks:
    - name: Runs first
      ansible.builtin.debug:
        msg: pre
  roles:
    - common
  tasks:
    - name: Runs after the role
      ansible.builtin.debug:
        msg: task
  post_tasks:
    - name: Runs last
      ansible.builtin.debug:
        msg: post

Untuk memanggil role dari dalam daftar task, bukan menggunakan key roles:, gunakan import_role atau include_role.

  tasks:
    - name: Static, read when the playbook is parsed
      ansible.builtin.import_role:
        name: common

    - name: Dynamic, resolved when the task runs
      ansible.builtin.include_role:
        name: postgres
      when: "'db' in group_names"

import_role bersifat statis. Ansible membaca role saat parsing, lalu task-nya menjadi bagian dari play. Karena itu, ansible-playbook --list-tasks site.yml mencantumkan task tersebut, dan tag pada import diterapkan ke setiap task di dalamnya. include_role bersifat dinamis. Tidak ada yang dibaca sampai task dijalankan. Hal ini memungkinkan nama role ditentukan dari variabel atau loop. Konsekuensinya, task tersebut tidak terlihat oleh --list-tasks dan --start-at-task.

Ada satu jebakan di sini. when: pada task include_role dievaluasi sebelum defaults/main.yml dari role yang di-include tersedia dalam scope. Jika menulis when: common_packages | length > 0 pada include, proses berhenti dengan 'common_packages' is undefined, meskipun variabel tersebut didefinisikan di dalam role yang sedang di-include. Solusinya adalah memindahkan toggle ke luar role. Letakkan toggle tersebut di group_vars/all.yml agar tersedia dalam scope di semua tempat, dan biarkan default role digunakan untuk nilai yang dikonsumsi oleh role itu sendiri.

Variabel mana yang berlaku: defaults, group_vars, vars, extra vars

Ansible mendokumentasikan lebih dari dua puluh tingkat prioritas variabel. Empat di antaranya menyelesaikan hampir semua perdebatan nyata. Berikut urutannya dari yang paling lemah hingga paling kuat.

  • roles/<name>/defaults/main.yml berada dekat tingkat terbawah. Hampir semua pengaturan lain yang Anda tetapkan di tempat lain akan mengalahkannya. Karena itu, roles/<name>/defaults/main.yml merupakan tempat yang tepat untuk parameter yang dapat disesuaikan dari sebuah role.
  • group_vars/ dan host_vars/ berada di tingkat menengah. Jawaban khusus untuk situs Anda sebaiknya ditempatkan di sini. Keduanya dapat menimpa default role dengan jelas.
  • roles/<name>/vars/main.yml berada di atas host_vars. Nilai yang Anda tempatkan di sini tidak dapat ditimpa dari inventory. Gunakan tingkat ini untuk hal-hal yang harus tetap konsisten secara internal dalam role, misalnya nama package yang harus sesuai dengan nama service.
  • Parameter role yang diteruskan pada lokasi pemanggilan mengalahkan vars/main.yml, sedangkan -e pada command line mengalahkan semuanya, termasuk parameter role.

Anda dapat melihat proses ini dalam waktu sekitar satu menit. Berikan sebuah role kecil satu default dan satu role var, lalu tetapkan nama yang sama di host_vars.

# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults
# roles/prec/vars/main.yml
---
prec_internal: from-rolevars
# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars
# roles/prec/tasks/main.yml
---
- name: Show which value survived
  ansible.builtin.debug:
    msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"
ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cli

Proses pertama mencetak tunable=from-hostvars internal=from-rolevars. Inventory mengalahkan default role, tetapi kalah dari role var. Proses kedua mencetak internal=from-cli karena extra vars berada di tingkat paling atas dan tidak ada pengaturan di bawahnya yang dapat menimpanya. Karena itu, -e cocok untuk satu kali eksekusi, tetapi tidak tepat digunakan dalam script yang dipertahankan: pengaturan tersebut secara diam-diam mengalahkan setiap keputusan yang dipertimbangkan dalam repository Anda.

Aturan praktisnya: jika Anda ingin suatu nilai dapat diatur, tempatkan nilai tersebut di defaults/. Menempatkannya di vars/ memberi tahu setiap pengguna role pada masa mendatang bahwa inventory tidak boleh mengubahnya. Dalam beberapa kasus, itulah yang Anda maksud. Namun, biasanya hal tersebut terjadi secara tidak sengaja.

Buktikan bahwa role bersifat idempoten: jalankan dua kali

Eksekusi Ansible yang dapat dipercaya menghasilkan keadaan yang sama pada eksekusi kedua dan melaporkan bahwa tidak ada perubahan. Jalankan playbook dua kali, lalu baca rekapnya.

ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml

Rekap kedua seharusnya terlihat seperti ini:

PLAY RECAP *********************************************************************
localhost   : ok=4  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0 berarti setiap modul memeriksa keadaan saat ini dan menemukan bahwa pekerjaan tersebut sudah selesai. changed=2 pada eksekusi kedua berarti dua task tidak dapat membedakan keadaan tersebut, sehingga keduanya akan terus menulis ulang file dan me-restart service tanpa henti. Penyebab yang biasanya terjadi adalah command atau shell, karena Ansible tidak memiliki cara untuk mengetahui dampak perintah arbitrer.

# traps.yml
---
- name: Command modules do not know what they changed
  hosts: local
  gather_facts: false
  tasks:
    - name: This appends a line on every run
      ansible.builtin.shell: "echo run >> /tmp/grow.txt"

    - name: This appends a line only once
      ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
      args:
        creates: /tmp/guarded.txt

Jalankan playbook tersebut dua kali, lalu hitung baris yang memuat wc -l /tmp/grow.txt /tmp/guarded.txt. /tmp/grow.txt berisi dua baris dan /tmp/guarded.txt berisi satu baris. Pada eksekusi kedua, task yang memiliki guard sama sekali tidak dijalankan, dan hasilnya memuat pesan skipped, since /tmp/guarded.txt exists karena creates memberikan modul suatu hasil yang dapat diperiksa terlebih dahulu. Jika sebuah perintah tidak menghasilkan hasil semacam itu, daftarkan output-nya dan tentukan sendiri dengan changed_when.

ansible-playbook --check --diff site.yml memprediksi perubahan tanpa menerapkannya, sedangkan --diff mencetak baris persis yang akan ditulis ulang oleh template. Baca output dengan memperhatikan satu pengecualian: task shell dan command dilewati dalam check mode, sehingga rencana yang terlihat bersih masih dapat menyembunyikan pekerjaan yang perlu dilakukan.

Satu kolom lain dalam rekap tersebut juga perlu diperhatikan: host yang tidak dapat dihubungi oleh Ansible dihitung sebagai unreachable, bukan failed, dan tidak satu pun task-nya dijalankan. Karena itu, tentukan terlebih dahulu apakah satu host yang tidak dapat dijangkau harus menghentikan seluruh eksekusi sebelum Anda menerapkan role ini pada lebih dari beberapa mesin.

Mengapa Ansible menyatakan bahwa role tidak ditemukan

Ansible mencari direktori roles/ di sebelah file playbook, lalu di roles_path. Pencarian mengikuti playbook, bukan shell Anda.

ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely

Pesan tersebut berarti site.yml dan roles/ tidak lagi sesuai, dan Ansible menampilkan path yang dicobanya. Simpan keduanya dalam direktori yang sama. Menjalankan perintah dari direktori induk tidak masalah karena yang menentukan adalah path playbook:

ansible-playbook -i infra/inventory.ini infra/site.yml

Ada versi yang lebih sulit terlihat dari masalah yang sama. Ansible mengabaikan ansible.cfg di direktori saat ini jika direktori tersebut dapat ditulisi oleh semua pengguna, karena pengguna mana pun pada host dapat menempatkan konfigurasi di sana dan mengubah hasil eksekusi Anda.

[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.

Dengan demikian, pengaturan roles_path dan inventory tidak tersedia tanpa pesan error, lalu pencarian role gagal karena alasan yang tidak terkait dengan role. ansible --version menampilkan config file yang benar-benar dimuat, sedangkan ansible-config dump --only-changed menampilkan setiap pengaturan yang berbeda dari nilai default bawaan. Periksa keduanya setiap kali eksekusi berjalan seolah-olah konfigurasi Anda tidak ada.

Berbagi role: requirements.yml dan versi yang dikunci

Role yang ditulis orang lain diinstal, bukan disalin. Deklarasikan sekali:

# requirements.yml
---
roles:
  - name: postgres
    src: https://github.com/example/ansible-role-postgres
    scm: git
    version: v1.4.0
ansible-galaxy install -r requirements.yml -p galaxy_roles

Selalu tetapkan version. Tanpa pengaturan ini, Anda akan mendapatkan isi branch default pada hari saat perintah dijalankan. Akibatnya, deployment yang berhasil bulan lalu dapat gagal tanpa perubahan apa pun pada repository Anda sendiri. Arahkan roles_path ke direktori unduhan, lalu jangan masukkan direktori tersebut ke git:

# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_roles

Role dalam roles/ yang berada di samping playbook tetap ditemukan karena path tersebut selalu dicari selain roles_path. Dengan demikian, role buatan sendiri tetap di-commit dan ditinjau, sedangkan role pihak ketiga menjadi unduhan yang dapat direproduksi karena dikunci pada tag.

Saat role bukan lagi jawabannya

Role adalah unit penggunaan ulang dalam satu eksekusi Ansible. Role tidak membuat server atau catatan DNS pada penyedia layanan Anda. Memaksa role untuk melakukan hal tersebut membuat playbook sulit dipelihara. pemisahan tugas antara Ansible dan Terraform layak dibaca sebelum Anda mulai. Role juga tidak menggantikan desain inventory. Setelah jumlah mesin bertambah, cara mengelompokkan dan mengakses server tersebut lebih penting daripada cara task disusun.

Hardening yang dipasang oleh role common ini juga memerlukan keputusan tersendiri. Drop-in di atas hanya menetapkan dua direktif. Karena itu, baca pengaturan SSH yang benar-benar layak diubah dan cara membuat Ubuntu menerapkan pembaruan keamanan secara otomatis sebelum menentukan hal yang harus dimasukkan ke dalam role untuk setiap host yang Anda kelola.

FAQ

Kapan saya harus mengubah Ansible playbook menjadi role?

Saat blok task yang sama harus dijalankan dalam play kedua atau terhadap grup host kedua. Menyalin task antar-playbook merupakan tandanya, karena sejak saat itu setiap perbaikan harus diterapkan dua kali, dan suatu hari perbaikan tersebut hanya akan diterapkan satu kali. Satu playbook dengan panjang kurang dari sekitar 100 baris yang hanya menargetkan satu grup tidak memperoleh manfaat dari role, dan direktori tambahan justru membuatnya lebih sulit dibaca.

Apakah role dijalankan sebelum task dalam play yang sama?

Ya. Ansible menjalankan pre_tasks, lalu semua yang tercantum dalam roles:, kemudian tasks:, dan setelah itu post_tasks:. Ansible mengabaikan urutan kemunculan key tersebut dalam file Anda. Menulis tasks: di atas roles: tidak membuat task tersebut dijalankan lebih dahulu. Jika sesuatu harus dilakukan sebelum role, letakkan di pre_tasks:.

Mengapa nilai group_vars saya tidak menimpa role?

Periksa apakah variabel tersebut ditetapkan dalam vars/main.yml milik role, bukan dalam defaults/main.yml. vars/ memiliki prioritas lebih tinggi daripada group_vars dan host_vars dalam urutan precedence Ansible, sehingga inventory tidak dapat menimpanya. Pindahkan variabel tersebut ke defaults/main.yml, yang berada dekat bagian bawah urutan dan merupakan tempat yang tepat untuk apa pun yang harus dapat diubah oleh pemanggil. Untuk memastikan bahwa penyebabnya adalah precedence, bukan kesalahan ketik, jalankan sekali dengan -e name=value, yang memiliki prioritas lebih tinggi daripada semua sumber lain.

Mengapa Ansible mengatakan role tidak ditemukan?

Pencarian dimulai dari direktori yang sama dengan file playbook, sehingga site.yml dan roles/ harus berada dalam direktori yang sama. Error tersebut mencetak path yang dicoba, seperti pada the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Menjalankan playbook dari direktori induk tidak masalah, karena pencarian mengikuti path playbook, bukan direktori kerja shell Anda. Jika Anda bergantung pada roles_path dari ansible.cfg, pastikan file tersebut dimuat dengan ansible --version, karena direktori kerja yang dapat ditulisi oleh semua pengguna membuat Ansible mengabaikannya.

Apakah saya memerlukan ansible-galaxy init untuk membuat role?

Tidak. Role hanya terdiri atas direktori dengan nama yang diharapkan, sehingga mkdir -p roles/common/tasks ditambah tasks/main.yml sudah menjadi role yang dapat digunakan. ansible-galaxy init --init-path roles common mengurangi pengetikan dan menyediakan skeleton lengkap, termasuk meta/main.yml serta stub README. Hapus direktori yang dibiarkan kosong, karena vars/main.yml yang kosong menyamarkan file mana dalam role yang benar-benar digunakan.

#ansible#roles#playbook#structure#automation