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.

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.
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:
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.
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:
changed: true dan memiliki keyword notify, Ansible akan mengantrekan (queue) handler yang di-notify.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.
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:
ansible-nginx-handler/
├── inventory.yml
├── files/
│ └── nginx-custom.conf
└── playbook-nginx-handler.ymlLalu isi inventory.yml dengan group webservers seperti yang sudah kita pelajari di episode 3:
---
all:
children:
webservers:
hosts:
web01:
ansible_host: 10.10.10.11
ansible_user: devopsSekarang, inilah inti dari episode ini, playbook yang memanfaatkan handler. Perhatikan bagian notify pada task dan blok handlers di bagian bawah:
---
- 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: restartedMari kita bedah playbook di atas baris demi baris:
ansible.builtin.apt. Task ini idempotent, artinya di jalankan kedua kalinya akan melaporkan ok karena paket sudah terinstal./etc/nginx/nginx.conf. Perhatikan keyword notify: Restart Nginx di bawahnya.changed (karena isi file berbeda dari yang sudah ada di server), maka handler Restart Nginx akan diantrekan.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.
RUNNING HANDLERSekarang mari kita jalankan playbook-nya:
ansible-playbook -i inventory.yml playbook-nginx-handler.ymlKetika dijalankan pertama kali (saat konfigurasi belum pernah ada di server), output yang akan muncul kurang lebih seperti ini:
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:
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=0Lihat 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.
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: restartedPendekatan tanpa handler terlihat lebih pendek dan sederhana, tapi ada jebakannya. Mari kita bandingkan perilaku keduanya dalam tabel berikut:
| Skenario | Tanpa Handler | Dengan Handler |
|---|---|---|
| Konfigurasi berubah | Service restart (benar) | Service restart (benar) |
| Konfigurasi tidak berubah | Service tetap restart (downtime tak perlu) | Service tidak di-restart |
| Playbook dijalankan 100x di CI | 100x restart service | 0x restart (jika tidak ada perubahan) |
| Task gagal sebelum handler | Tidak relevan (restart selalu jalan) | Handler tidak dieksekusi (play gagal) |
| Dampak pada produksi | Risiko downtime berkali-kali | Downtime 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.
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.
flush_handlersTerkadang 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:
---
- 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: restartedSetelah 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.
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:
---
- 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: reloadedPerhatikan 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.
notify Harus Persis SamaSekarang 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:
---
- 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: restartedKarena 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! The requested handler 'restart nginx' was not found in either the
main handlers list nor in the listening handlers listCaution
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:
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.
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:
notify di task + blok handlers di level play adalah pasangan yang tidak terpisahkan.flush_handlers.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!