Belajar Ansible - Pengenalan dan Penjelasan Handlers
Episode 6 of 31

Belajar Ansible - Pengenalan dan Penjelasan Handlers

Pelajari konsep Handler di Ansible, kenapa restart service hanya boleh terjadi saat konfigurasi berubah, perbedaan task biasa dengan handler, serta aturan dan keterbatasannya seperti flush_handlers dan multiple notification.

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

Pendahuluan

Setelah di episode 5 sebelumnya kita membahas cara menulis playbook pertama kita, mulai dari anatomi file YAML (hosts, name, become, tasks), privilege escalation, sampai mekanisme idempotency dan status output OK, CHANGED, FAILED, serta SKIPPED. Kalian sekarang sudah bisa membuat playbook yang aman untuk dijalankan berulang kali.

Namun, ada satu masalah klasik yang akan langsung kalian temui begitu playbook kalian mulai mengelola service, terutama service yang melayani user secara langsung seperti NGINX atau PostgreSQL. Mari kita analogikan begini: bayangkan kalian punya aplikasi e-commerce yang sedang ramai dikunjungi pengguna. Jika ada satu kode konfigurasi yang salah dan memaksa seluruh server web di-restart secara bersamaan, maka semua pengunjung akan merasakan downtime seketika. Tentu ini bukan pengalaman yang kita inginkan untuk produksi.

Masalahnya, banyak playbook pemula melakukan restart service di setiap kali playbook dijalankan, padahal konfigurasinya tidak berubah sama sekali. Di episode 6 ini, kita akan membahas solusi elegan dari Ansible untuk masalah tersebut, yaitu Handler. Handler adalah mekanisme yang memastikan restart service hanya terjadi ketika memang ada perubahan, bukan setiap kali playbook dieksekusi. Konsep ini adalah salah satu pembeda antara automasi yang "asal jalan" dengan automasi yang benar-benar aman untuk produksi.

Pembahasan Utama

Kenapa Restart Service Tidak Boleh Sembarangan?

Sebelum masuk ke handler, kita perlu benar-benar memahami mengapa restart service adalah operasi yang "mahal" dan berisiko. Mari kita lihat sisi teknisnya dulu.

Ketika kalian melakukan restart pada sebuah service, terjadi beberapa hal:

  • Koneksi yang sedang aktif diputus secara paksa.
  • Proses baru harus dimulai dari nol (warm-up cache, koneksi database, dan sebagainya).
  • Ada selang waktu di mana service tidak merespon permintaan sama sekali.
  • Jika service restart-nya berbarengan di banyak server, kapasitas layanan bisa turun drastis.

Bandingkan dengan reload yang umumnya hanya membaca ulang file konfigurasi tanpa memutus koneksi yang sedang berjalan. Itulah kenapa untuk perubahan konfigurasi yang ringan, kita lebih memilih reload daripada restart.

Note

Perbedaan istilah: restart mematikan dan menghidupkan kembali proses (memutus koneksi aktif), sedangkan reload hanya menyuruh proses membaca ulang konfigurasi tanpa memutus koneksi yang berjalan. Untuk NGINX, Apache, dan banyak service web, reload lebih aman untuk perubahan konfigurasi yang sifatnya non-breaking.

Pertanyaan selanjutnya adalah: kapan sebenarnya restart itu dibutuhkan? Jawabannya sederhana, restart hanya dibutuhkan ketika file konfigurasi service benar-benar berubah. Kalau konfigurasi identik dengan yang sudah terpasang, restart hanyalah pemborosan downtime tanpa manfaat.

Memahami Task Biasa vs Handler

Di Ansible, seluruh unit kerja dieksekusi sebagai task di dalam blok tasks. Setiap task selalu dijalankan (kecuali di-skip karena kondisi), dan hasilnya diklasifikasikan menjadi ok, changed, atau failed berkat mekanisme idempotency yang kita bahas di episode 5.

Nah, handler adalah tipe task yang istimewa. Handler bukanlah task yang langsung dieksekusi di dalam alur playbook, melainkan "task yang menunggu dipanggil". Handler hanya akan dijalankan jika ada task lain yang memberi tahu (notify) dan task tersebut melaporkan status changed: true.

Bayangkan handler seperti pager ala jaman dulu: ia tidak bekerja terus-menerus, ia hanya beraksi ketika ada seseorang yang memanggilnya. Sedangkan task biasa seperti karyawan yang mengerjakan pekerjaannya setiap kali shift dimulai, apakah ada perubahan atau tidak.

Mekanisme lengkapnya adalah seperti ini:

  1. Ansible menjalankan task secara berurutan.
  2. Jika sebuah task selesai dengan status changed: true dan memiliki keyword notify, Ansible akan mengantrekan (queue) handler yang di-notify.
  3. Setelah seluruh task pada play selesai, Ansible mengeksekusi handler yang sudah diantrekan.
  4. Jika tidak ada task yang berubah, handler tidak akan pernah dijalankan.

Poin penting yang perlu kalian garis bawahi: handler hanya terpicu oleh task yang melaporkan changed. Task yang statusnya ok (tidak ada perubahan) tidak akan memicu handler.

Contoh Lengkap: Playbook Konfigurasi NGINX dengan Handler

Supaya konsepnya lebih nyata, mari kita buat playbook lengkap yang menginstal NGINX, menyalin konfigurasi custom, lalu menggunakan handler untuk me-restart service hanya ketika konfigurasi berubah.

Pertama, siapkan struktur direktori proyek seperti ini:

Struktur direktori proyek
ansible-nginx-handler/
├── inventory.yml
├── files/
   └── nginx-custom.conf
└── playbook-nginx-handler.yml

Lalu isi inventory.yml dengan group webservers seperti yang sudah kita pelajari di episode 3:

inventory.yml
---
all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 10.10.10.11
          ansible_user: devops

Sekarang, inilah inti dari episode ini, playbook yang memanfaatkan handler. Perhatikan bagian notify pada task dan blok handlers di bagian bawah:

playbook-nginx-handler.yml
---
- name: Setup Nginx Web Server dengan Handler
  hosts: webservers
  become: true
  vars:
    nginx_port: 8080
 
  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
 
    - name: Copy konfigurasi custom NGINX
      ansible.builtin.copy:
        src: files/nginx-custom.conf
        dest: /etc/nginx/nginx.conf
        mode: "0644"
      notify: Restart Nginx
 
  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Mari kita bedah playbook di atas baris demi baris:

  • Task Install Nginx menggunakan modul ansible.builtin.apt. Task ini idempotent, artinya di jalankan kedua kalinya akan melaporkan ok karena paket sudah terinstal.
  • Task Copy konfigurasi custom NGINX menyalin file konfigurasi ke /etc/nginx/nginx.conf. Perhatikan keyword notify: Restart Nginx di bawahnya.
  • Jika task copy ini melaporkan changed (karena isi file berbeda dari yang sudah ada di server), maka handler Restart Nginx akan diantrekan.
  • Handler didefinisikan di bawah keyword handlers, dengan nama yang harus persis sama dengan nilai di notify.

Important

Kata kunci yang membedakan handler adalah notify di dalam task dan blok handlers di level play. Tanpa notify, handler hanyalah task mati yang tidak akan pernah dipanggil. Tanpa blok handlers, keyword notify tidak akan menemukan targetnya.

Menjalankan Playbook dan Membaca Output RUNNING HANDLER

Sekarang mari kita jalankan playbook-nya:

Jalankan playbook
ansible-playbook -i inventory.yml playbook-nginx-handler.yml

Ketika dijalankan pertama kali (saat konfigurasi belum pernah ada di server), output yang akan muncul kurang lebih seperti ini:

Output eksekusi pertama
PLAY [Setup Nginx Web Server dengan Handler] **********************************
 
TASK [Gathering Facts] ********************************************************
ok: [web01]
 
TASK [Install Nginx] **********************************************************
ok: [web01]
 
TASK [Copy konfigurasi custom NGINX] ******************************************
changed: [web01]
 
RUNNING HANDLER [Restart Nginx] ***********************************************
changed: [web01]
 
PLAY RECAP ********************************************************************
web01                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Perhatikan baris RUNNING HANDLER [Restart Nginx]. Baris inilah penanda bahwa handler dieksekusi. Handler ini hanya muncul karena task copy melaporkan changed: [web01].

Sekarang jalankan playbook yang sama untuk kedua kalinya tanpa mengubah apa pun:

Output eksekusi kedua (idempotent)
PLAY [Setup Nginx Web Server dengan Handler] **********************************
 
TASK [Gathering Facts] ********************************************************
ok: [web01]
 
TASK [Install Nginx] **********************************************************
ok: [web01]
 
TASK [Copy konfigurasi custom NGINX] ******************************************
ok: [web01]
 
PLAY RECAP ********************************************************************
web01                      : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Lihat perbedaannya! Pada eksekusi kedua, task copy melaporkan ok karena isi file sudah identik, sehingga handler tidak dijalankan dan tidak ada baris RUNNING HANDLER sama sekali. NGINX tidak pernah di-restart tanpa alasan.

Tip

Baris RUNNING HANDLER [Nama Handler] adalah cara cepat untuk memastikan handler bekerja. Jika kalian mengharapkan handler berjalan tapi baris ini tidak muncul, cek apakah task yang men-notify benar-benar melaporkan changed. Salah satu penyebab paling umum adalah task menggunakan modul yang tidak idempotent (misal command tanpa creates), sehingga status changed selalu muncul dan handler terus terpicu.

Dengan vs Tanpa Handler

Untuk memperjelas why di balik handler, mari kita bandingkan dua pendekatan secara langsung. Pendekatan pertama adalah playbook "naif" yang me-restart service setiap kali playbook dijalankan:

---
- name: Setup NGINX tanpa handler
  hosts: webservers
  become: true
  tasks:
    - name: Copy konfigurasi NGINX
      ansible.builtin.copy:
        src: files/nginx-custom.conf
        dest: /etc/nginx/nginx.conf
 
    - name: Restart NGINX
      ansible.builtin.service:
        name: nginx
        state: restarted

Pendekatan tanpa handler terlihat lebih pendek dan sederhana, tapi ada jebakannya. Mari kita bandingkan perilaku keduanya dalam tabel berikut:

SkenarioTanpa HandlerDengan Handler
Konfigurasi berubahService restart (benar)Service restart (benar)
Konfigurasi tidak berubahService tetap restart (downtime tak perlu)Service tidak di-restart
Playbook dijalankan 100x di CI100x restart service0x restart (jika tidak ada perubahan)
Task gagal sebelum handlerTidak relevan (restart selalu jalan)Handler tidak dieksekusi (play gagal)
Dampak pada produksiRisiko downtime berkali-kaliDowntime hanya saat benar-benar perlu

Kesimpulannya, handler membuat playbook berperilaku sesuai prinsip idempotency yang sudah kita pelajari di episode 5: hasil akhir sama (service berjalan dengan konfigurasi yang diinginkan), tapi efek sampingnya minimal.

Aturan & Keterbatasan Handler

Handler bukan fitur ajaib tanpa aturan. Memahami keterbatasannya justru akan mencegah kebingungan di lapangan. Berikut aturan-aturan penting yang wajib kalian hafal:

1. Handler dieksekusi di akhir play (secara default). Handler tidak berjalan langsung setelah task yang me-notify selesai. Ansible menunggu sampai semua task pada play selesai, baru menjalankan handler yang sudah diantrekan. Ini adalah keputusan desain yang disengaja agar jika banyak task mengantrekan handler yang sama, service hanya di-restart/reload satu kali.

2. Handler hanya berjalan satu kali per play, berapa pun jumlah notifikasinya. Jika ada tiga task yang me-notify handler Reload Nginx, handler tetap hanya dieksekusi satu kali di akhir play. Ini adalah optimasi sekaligus perilaku yang harus kalian pahami.

3. Handler berjalan sesuai urutan definisi, bukan urutan notifikasi. Urutan eksekusi handler mengikuti urutan kemunculannya di blok handlers, bukan urutan task yang men-notify. Jika handler B didefinisikan sebelum handler A, maka B yang akan dieksekusi lebih dulu.

4. Handler hanya terpicu oleh task yang melaporkan changed. Task dengan status ok tidak akan me-notify handler. Ini justru perilaku yang kita inginkan, karena tidak ada perubahan berarti tidak perlu ada aksi lanjutan.

5. Jika ada task yang gagal, handler yang sudah diantrekan tidak akan dijalankan. Ketika sebuah play gagal, Ansible menghentikan eksekusi dan handler yang antre akan dibuang. Ini adalah default yang aman: kalian tidak ingin me-reload konfigurasi yang rusak dan membuat service makin tidak stabil.

Warning

Untuk skenario di mana kalian justru ingin handler tetap berjalan meskipun task setelahnya gagal (misalnya handler itu yang memulihkan sistem ke kondisi baik), gunakan opsi force_handlers: true di level play. Gunakan secara bijak, karena ini menyimpang dari perilaku default yang sebenarnya lebih aman.

Memaksa Handler Dieksekusi di Tengah Play: flush_handlers

Terkadang kalian butuh handler untuk berjalan sebelum play selesai. Contoh nyata: kalian mengubah konfigurasi NGINX, lalu di task berikutnya ingin langsung men-deploy aplikasi yang membutuhkan NGINX dengan konfigurasi baru. Jika menunggu sampai akhir play, aplikasi yang di-deploy bisa memakai konfigurasi lama.

Solusinya adalah modul ansible.builtin.meta dengan parameter flush_handlers. Modul ini "memaksa" semua handler yang sudah diantrekan untuk langsung dieksekusi saat itu juga:

playbook-flush-handlers.yml
---
- name: Install NGINX lalu flush handler di tengah play
  hosts: webservers
  become: true
 
  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
 
    - name: Update konfigurasi NGINX
      ansible.builtin.copy:
        src: files/nginx-custom.conf
        dest: /etc/nginx/nginx.conf
      notify: Restart Nginx
 
    - name: Flush semua handler yang sudah diantrekan
      ansible.builtin.meta: flush_handlers
 
    - name: Deploy aplikasi yang butuh NGINX aktif
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
        mode: "0644"
 
  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Setelah task flush_handlers selesai, handler Restart Nginx sudah dieksekusi, sehingga task deploy aplikasi di bawahnya dapat berjalan dengan asumsi NGINX sudah memakai konfigurasi terbaru.

Tip

Jangan terlalu sering menggunakan flush_handlers. Sering flushing sama saja membuang keuntungan dari "batching" yang sudah dirancang Ansible. Gunakan hanya ketika urutan eksekusi antar task benar-benar bergantung pada efek handler, misalnya restart service harus selesai sebelum task berikutnya.

Pemanggilan Multiple Notification ke Satu Handler

Skenario yang sangat umum di dunia nyata adalah beberapa task yang mengubah konfigurasi berbeda dari service yang sama. Misalnya kita mengubah nginx.conf, menambahkan virtual host baru, dan memperbarui konfigurasi SSL. Ketiga perubahan ini semuanya butuh reload NGINX.

Dengan handler, ketiga task tersebut cukup me-notify ke satu handler yang sama:

playbook-multi-notify.yml
---
- name: Update beberapa konfigurasi NGINX sekaligus
  hosts: webservers
  become: true
 
  tasks:
    - name: Update konfigurasi utama
      ansible.builtin.copy:
        src: files/nginx-custom.conf
        dest: /etc/nginx/nginx.conf
      notify: Reload Nginx
 
    - name: Update virtual host situs A
      ansible.builtin.copy:
        src: files/site-a.conf
        dest: /etc/nginx/sites-available/site-a.conf
      notify: Reload Nginx
 
    - name: Update konfigurasi SSL
      ansible.builtin.copy:
        src: files/ssl-params.conf
        dest: /etc/nginx/conf.d/ssl-params.conf
      notify: Reload Nginx
 
  handlers:
    - name: Reload Nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Perhatikan bahwa kita memakai state: reloaded bukan restarted. Ini selaras dengan penjelasan di awal, untuk perubahan konfigurasi yang bersifat additive, reload jauh lebih aman karena tidak memutus koneksi.

Yang menarik: meskipun ada tiga task yang me-notify handler yang sama, NGINX hanya di-reload satu kali, yaitu di akhir play. Ini adalah perilaku yang sangat diinginkan, karena me-reload service tiga kali berturut-turut dalam satu eksekusi playbook adalah pemborosan yang sia-sia.

Pitfall: Nama Handler dan notify Harus Persis Sama

Sekarang kita masuk ke bagian kesalahan umum (common pitfalls) yang paling sering membuat pemula kebingungan. Nama handler pada blok handlers dan nilai pada keyword notify harus persis sama, termasuk huruf besar/kecil, spasi, dan tanda baca.

Perhatikan contoh yang salah berikut:

playbook-salah.yml (nama tidak match)
---
- name: Playbook dengan nama handler tidak match
  hosts: webservers
  become: true
 
  tasks:
    - name: Update konfigurasi NGINX
      ansible.builtin.copy:
        src: files/nginx-custom.conf
        dest: /etc/nginx/nginx.conf
      notify: restart nginx      # huruf kecil, tidak match
 
  handlers:
    - name: Restart Nginx        # huruf besar, definisi handler
      ansible.builtin.service:
        name: nginx
        state: restarted

Karena restart nginx (huruf kecil) tidak sama dengan Restart Nginx (huruf besar), handler tidak akan pernah terpicu. Bahkan pada Ansible versi terbaru, playbook akan gagal langsung dengan pesan error:

Error saat handler tidak ditemukan
ERROR! The requested handler 'restart nginx' was not found in either the
main handlers list nor in the listening handlers list

Caution

Pada Ansible versi lama (sebelum 2.18), handler yang tidak ditemukan hanya dilewati secara diam-diam tanpa error. Inilah yang membuat bug ini sangat berbahaya: playbook tampak sukses, tapi restart tidak pernah terjadi dan service tetap memakai konfigurasi lama. Di versi terbaru, error ini muncul secara eksplisit agar kalian sadar lebih cepat. Selalu copy-paste nama handler dari blok handlers ke notify untuk menghindari typo, dan manfaatkan ansible-lint untuk menangkapnya lebih awal di CI/CD.

Masih terkait naming, ada satu tip lanjutan yang berguna. Untuk membuat handler lebih fleksibel, kalian bisa menggunakan keyword listen pada handler. Dengan listen, beberapa handler bisa "mendengarkan" satu topik yang sama:

Menggunakan listen untuk nama yang fleksibel
  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
      listen: "nginx config changed"
 
    - name: Notify tim monitoring
      ansible.builtin.uri:
        url: https://hooks.example.com/nginx-reloaded
        method: POST
      listen: "nginx config changed"

Dengan pola di atas, task cukup me-notify nginx config changed, dan semua handler yang "mendengarkan" topik tersebut akan terpicu. Ini sangat berguna ketika nanti kalian mulai bekerja dengan Ansible Roles di episode 12, karena nama handler di dalam role memiliki cakupan (scope) tersendiri.

Penutup

Pada episode 6 ini, kita telah belajar bahwa handler adalah mekanisme penting untuk menjaga playbook tetap idempotent dan aman untuk produksi. Kita sudah memahami perbedaan mendasar antara task biasa dan handler, di mana handler hanya berjalan ketika ada task yang melaporkan changed dan me-notify-nya. Kita juga sudah melihat aturan dan keterbatasannya: handler berjalan di akhir play, hanya satu kali per notifikasi ganda, dan bisa dipaksa berjalan di tengah play menggunakan ansible.builtin.meta: flush_handlers. Terakhir, kita sudah mengidentifikasi pitfall paling umum, yaitu ketidaksesuaian nama antara notify dan handler, beserta solusinya.

Poin kunci yang perlu kalian bawa pulang:

  • Restart service adalah operasi mahal yang hanya layak dilakukan saat konfigurasi benar-benar berubah.
  • notify di task + blok handlers di level play adalah pasangan yang tidak terpisahkan.
  • Handler menunggu sampai akhir play, kecuali dipaksa dengan flush_handlers.
  • Beberapa task boleh me-notify satu handler yang sama, dan handler tetap berjalan satu kali.

Di episode 7 selanjutnya kita akan membahas topik yang tidak kalah penting, yaitu Variables & Facts. Kita akan belajar bagaimana membuat playbook yang benar-benar dinamis dengan variabel, memahami hierarki precedence dari yang terlemah hingga terkuat, hingga memanfaatkan fakta sistem (ansible facts) supaya satu playbook bisa berjalan di berbagai jenis sistem operasi. Ini adalah fondasi yang akan sangat berguna ketika kita masuk ke episode 8 tentang Jinja2 templating. Pastikan tetap semangat!

Belajar Ansible - Pengenalan dan Penjelasan Handlers | Belajar Ansible