Pada episode ini kita akan belajar menangani long-running tasks seperti OS upgrade, migrasi database, dan backup menggunakan mekanisme async & poll, termasuk mode fire-and-forget dan pengecekan status dengan async_status.

Setelah di episode 15 sebelumnya kita membahas performance tuning — forks, SSH pipelining, fact caching, dan execution strategies — kalian mungkin berpikir playbook sudah cukup cepat. Memang benar, sebagian besar. Tapi ada kategori task yang sama sekali tidak bisa dipercepat dengan tuning, karena waktu yang dibutuhkan memang intrinsik: OS upgrade di ratusan paket, migrasi database multi-juta baris, backup volume besar, atau menjalankan data migration script di server produksi. Task seperti ini bisa berjalan 20 menit, satu jam, bahkan lebih.
Masalahnya, mekanisme default Ansible adalah synchronous: control node membuka koneksi SSH, menjalankan modul, lalu menunggu dengan koneksi tetap terbuka sampai modul selesai dan mengembalikan hasil. Koneksi yang digantung terlalu lama itu rapuh. Timeout SSH, idle timeout dari load balancer atau firewall, atau sekadar jitter jaringan bisa memutus koneksi di tengah jalan. Akibatnya playbook melaporkan kegagalan — padahal di sisi server, proses yang sedang dijalankan mungkin masih berjalan normal. Inilah skenario paling ditakuti: status yang ambigu, dan server yang "tidak jelas sedang apa".
Di episode 16 ini kita akan membahas Asynchronous Actions & Polling — mekanisme Ansible untuk melepaskan task panjang dari koneksi SSH. Kalian akan belajar parameter async dan poll, mode fire-and-forget dengan poll: 0, serta cara mengecek status job secara berkala dengan modul ansible.builtin.async_status. Materi ini adalah salah satu keterampilan yang paling sering ditanyakan di dunia kerja, karena semua orang pernah bertemu database migration atau OS upgrade yang harus jalan semalaman.
Untuk memahami kenapa task panjang gagal, kita harus paham bagaimana Ansible menjalankan task secara sinkron. Alurnya seperti ini:
Masalahnya ada di langkah 3. Semua komponen jaringan di antara control node dan managed node punya timeout: server OpenSSH punya ServerAliveInterval, firewall/load balancer punya idle timeout (umumnya 5–30 menit), dan koneksi SSH yang tidak ada lalu lintas untuk waktu lama akan diputus. Ketika itu terjadi, Ansible tidak bisa lagi membaca hasil modul:
TASK [Upgrade semua paket sistem] *****************************************
fatal: [db01]: UNREACHABLE! => {"changed": false, "msg": "Timeout (12s) waiting for privilege escalation prompt", "unreachable": true}
to retry, use: --limit @/home/arman/playbook.retry
PLAY RECAP *********************************************************************
db01 : ok=1 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0Status UNREACHABLE berarti Ansible kehilangan koneksi — tetapi proses apt-get atau migrasi database di db01 kemungkinan besar masih terus berjalan di background. Kalian tidak tahu apakah sudah selesai, sedang berjalan, atau justru stuck. Menjalankan playbook ulang bisa menimbulkan race: dua proses upgrade berjalan bersamaan di server yang sama.
Warning
Kesalahan terbesar yang bisa dilakukan saat menghadapi UNREACHABLE pada task panjang adalah menjalankan ulang task tersebut begitu saja. Untuk task yang tidak idempotent (seperti migrasi database), eksekusi ganda justru bisa merusak data. Aturan emasnya: kalau connection putus di tengah task panjang, periksa dulu status di server (misalnya ps aux, systemctl status, atau log) sebelum memutuskan langkah berikutnya.
Tuning SSH seperti menambahkan ServerAliveInterval memang membantu menjaga koneksi tetap hidup:
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=30 -o ServerAliveCountMax=3...tetapi ini hanya menunda masalah, bukan menyelesaikannya. Jika task berjalan 2 jam, tidak ada pengaturan SSH yang realistis akan menjaga satu koneksi tetap terbuka selama itu — terutama jika ada perangkat jaringan di tengah yang memutus koneksi idle. Solusi yang benar adalah mengubah model eksekusinya: jangan simpan task di dalam satu koneksi SSH, tapi jalankan di background dan pantau secara terpisah. Itulah yang dilakukan async.
Mekanisme async di Ansible bekerja dengan membalik logika eksekusi:
ansible_job_id) beserta lokasi file hasil (di direktori async_dir, default ~/.ansible_async di remote).poll > 0) membuka koneksi baru secara berkala untuk menanyakan status job ke modul ansible.builtin.async_status menggunakan job ID tersebut, hingga job selesai.Dua parameter kuncinya adalah async dan poll:
async: <detik> adalah batas waktu maksimum (timeout) yang diizinkan untuk task tersebut. Ini bukan durasi "target", melainkan batas keras: jika job belum selesai setelah async detik, Ansible menganggapnya gagal karena timeout.poll: <detik> adalah interval yang dipakai control node untuk mengecek status job. Jika poll: 0, mode berubah menjadi fire-and-forget (kita bahas sebentar lagi).Perhatikan perbedaannya dengan default:
| Mode | async | poll | Perilaku |
|---|---|---|---|
| Sinkron (default) | Tidak di-set | Tidak di-set | Control node menunggu satu koneksi SSH sampai selesai |
| Async + polling | di-set | > 0 (default 10) | Job jalan di background, koneksi ditutup, status dicek tiap poll detik |
| Async fire-and-forget | di-set | 0 | Job jalan di background, playbook langsung lanjut ke task berikutnya |
Jika kalian men-set async tanpa menyebut poll, Ansible menggunakan default poll = 10 detik. Jadi cara termudah untuk mengingatnya: async membatasi durasi, poll mengatur seberapa sering kalian menengok.
Note
Secara praktik, async + poll > 0 terlihat "sinkron" dari sudut pandang playbook — playbook tetap menunggu task selesai. Bedanya hanya pada cara menunggu: bukan satu koneksi SSH yang digantung berjam-jam, melainkan koneksi pendek yang dibuka dan ditutup setiap poll detik. Inilah yang membuat task berjam-jam menjadi aman.
Skenario paling umum adalah OS upgrade. Jalankan dengan async: 3600 (batas maksimum 1 jam) dan poll: 30 (cek status tiap 30 detik):
---
- name: OS upgrade dengan async & poll
hosts: db01
become: true
tasks:
- name: Upgrade semua paket (sampai 1 jam)
ansible.builtin.apt:
upgrade: dist
update_cache: true
async: 3600
poll: 30Saat playbook dijalankan, kalian akan melihat baris polling seperti ini — perhatikan bahwa koneksi dibuka-tutup berulang kali, bukan digantung:
TASK [Upgrade semua paket (sampai 1 jam)] *********************************
ASYNC POLL ON db01: job_id=178183079391.21821
ASYNC POLL ON db01: job_id=178183079391.21821
ASYNC POLL ON db01: job_id=178183079391.21821
ASYNC RESULT ON db01: {"changed": true, "cmd": ["apt-get", "dist-upgrade", ...], "finished": 1, "rc": 0, ...}
PLAY RECAP *********************************************************************
db01 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Ketika finished: 1 dan rc: 0 muncul, task dianggap sukses. Jika durasi melewati async: 3600, job akan dilaporkan gagal dengan pesan async task timed out. Karena itu, selalu pasang nilai async dengan margin yang lebar dari estimasi durasi normal task — kalian tidak ingin upgrade yang biasanya 40 menit gagal hanya karena terburu-buru di-set async: 1800.
Tip
Kombinasikan async/poll dengan parameter play serial dari episode 15 untuk rolling update yang lebih aman. Misalnya rolling upgrade 20% server dengan tiap batch memakai async: batch tidak pernah menyentuh server berikutnya sebelum yang sebelumnya benar-benar tuntas.
poll: 0Sekarang bagian yang lebih menarik: fire-and-forget. Dengan poll: 0, playbook tidak menunggu sama sekali — task dijalankan di background, job ID langsung dikembalikan, dan playbook melanjutkan ke task berikutnya. Ini berguna ketika:
Contoh playbook: kick-off backup besar, lalu playbook lanjut ke pekerjaan lain sementara backup berjalan:
---
- name: Jalankan backup besar tanpa memblokir playbook
hosts: db01
become: true
tasks:
- name: Mulai backup database (tidak menunggu)
ansible.builtin.shell:
cmd: pg_dumpall > /backups/db-$(date +%Y%m%d).sql
async: 7200
poll: 0
register: backup_job
- name: Lanjutkan pekerjaan lain yang tidak terkait backup
ansible.builtin.debug:
msg: "Backup berjalan di background, job_id={{ backup_job.ansible_job_id }}"Karena register: backup_job, hasil task ini menyimpan ansible_job_id yang bisa dipakai nanti. Statusnya tidak akan berisi hasil final backup — itu normal untuk mode fire-and-forget.
Important
register pada task async fire-and-forget hanya berisi metadata job (ansible_job_id, results_file, started), bukan hasil eksekusi. Jangan mengakses backup_job.stdout atau backup_job.rc pada task ini — nilainya belum ada. Hasil final hanya bisa didapat setelah mengecek job dengan async_status.
ansible.builtin.async_statusJob yang di-fire-and-forget tidak hilang begitu saja. Ia berjalan di managed node, dan statusnya bisa dicek kapan saja menggunakan modul ansible.builtin.async_status dengan parameter jid (job ID):
---
- name: Kick-off backup lalu pantau sampai selesai
hosts: db01
become: true
tasks:
- name: Mulai backup database (fire-and-forget)
ansible.builtin.shell:
cmd: pg_dumpall > /backups/db-$(date +%Y%m%d).sql
async: 7200
poll: 0
register: backup_job
- name: Tunggu sampai backup selesai
ansible.builtin.async_status:
jid: "{{ backup_job.ansible_job_id }}"
register: backup_result
until: backup_result.finished
retries: 120
delay: 15Mari kita bedah bagian pengecekan ini:
async_status menanyakan status job ke managed node berdasarkan jid.backup_result.finished akan bernilai 1 ketika job selesai (baik sukses maupun gagal).until + retries + delay (kita pelajari di episode 9 tentang control flow) membuat task ini memeriksa ulang setiap 15 detik, maksimal 120 kali — total jendela penantian 30 menit.Hasil async_status ketika job selesai terlihat seperti ini:
{
"changed": false,
"cmd": "pg_dumpall > /backups/db-20260802.sql",
"finished": 1,
"job": "875623478192.28173",
"rc": 0,
"results_file": "/root/.ansible_async/875623478192.28173",
"started": 1,
"stdout": "...",
"stderr": ""
}Perhatikan key finished: 1 — inilah penanda yang dijadikan kondisi until. Setelah job selesai, kalian bisa membaca rc dan stdout untuk menentukan sukses atau gagal, misalnya dengan task berikutnya menggunakan failed_when: backup_result.rc != 0.
Pola "kick-off dengan poll: 0 → pantau dengan async_status" adalah teknik yang sangat fleksibel. Salah satu penggunaan favorit di produksi adalah reboot paralel: semua host di-kick reboot sekaligus, lalu playbook menunggu masing-masing kembali online dengan wait_for_connection:
---
- name: Reboot semua host secara paralel
hosts: all
become: true
serial: 50%
tasks:
- name: Kick-off reboot (fire-and-forget)
ansible.builtin.reboot:
reboot_timeout: 600
async: 900
poll: 0
register: reboot_job
- name: Tunggu host kembali online
ansible.builtin.wait_for_connection:
delay: 30
timeout: 300| Parameter | Tipe | Default | Fungsi |
|---|---|---|---|
async | int | Tidak ada | Batas waktu maksimum (detik) yang diizinkan untuk sebuah job |
poll | int | 10 | Interval (detik) pengecekan status; 0 = fire-and-forget |
register | — | — | Menyimpan hasil task, termasuk ansible_job_id |
async_status.jid | str | — | Job ID yang ingin dicek statusnya |
async_status.mode | str | status | status untuk cek, cleanup untuk hapus file hasil job |
async_dir | str | ~/.ansible_async | Direktori di managed node tempat file hasil job disimpan |
until / retries / delay | — | — | Pola polling manual saat dipakai bersama async_status |
Async bukan solusi ajaib — ada batasan dan risiko yang harus kalian pahami sebelum memakainya:
1. Tidak semua modul mendukung async. Modul-modul yang bekerja lewat connection delegation (seperti wait_for, win_* yang berbasis WinRM, atau modul network tertentu) umumnya tidak mendukung mode async. Sebagai aturan praktis, modul command, shell, script, apt, dnf, dan yum berjalan baik dengan async; sebelum memakai async pada modul lain, periksa dokumentasinya.
2. Fire-and-forget = kehilangan kendali. Dengan poll: 0, jika control node mati atau playbook dibatalkan, job di managed node tetap berjalan — tapi kalian kehilangan cara untuk mengeceknya (kecuali kalian menyimpan job ID dan mengecek manual via async_status). Pastikan ada mekanisme lain (log, monitoring) untuk memastikan job tersebut benar-benar selesai.
3. async adalah timeout, bukan durasi rencana. Nilai async yang terlalu kecil membuat job yang masih sehat dianggap gagal. Selalu beri margin besar (misalnya 2× estimasi normal).
4. File hasil job bisa hilang. Status job disimpan di async_dir managed node (~/.ansible_async). Jika direktori dibersihkan, proses reboot (file hasil tidak bertahan lintas reboot), atau job ID sudah dikosongkan, async_status akan melaporkan job not found. Untuk task yang sangat lama, pertimbangkan menulis log sendiri ke file di server sebagai source of truth tambahan.
5. Async tidak bisa dipakai di handlers. Modul handler tidak mendukung parameter async — handler dijalankan setelah play/notify, dan job async di dalamnya tidak akan menunggu dengan benar. Jika kalian butuh restart service yang lama setelah perubahan, lakukan sebagai task biasa, bukan handler.
Caution
Jangan gunakan fire-and-forget (poll: 0) untuk task yang task berikutnya di playbook bergantung padanya. Jika task berikutnya butuh hasil task sebelumnya, gunakan poll > 0 (atau pola async_status + until). Fire-and-forget hanya aman untuk pekerjaan yang benar-benar boleh berjalan paralel dan tidak memblokir.
Ringkasan jebakan yang paling sering ditemui di lapangan:
1. Menggunakan async tanpa alasan jelas. Task normal yang berdurasi < 5 menit tidak butuh async. Async menambah kompleksitas dan siklus polling; gunakan hanya untuk task yang memang berdurasi panjang atau ingin dijalankan paralel.
2. async terlalu kecil. Job yang melebihi nilai async dianggap gagal meskipun berjalan normal. Beri margin besar.
3. Mengakses hasil job fire-and-forget langsung dari register. Seperti dijelaskan di atas, hasil final hanya ada setelah dicek dengan async_status.
4. Melupakan until saat menunggu async_status. Tanpa until: ... finished, task async_status hanya mengecek sekali — hampir pasti masih finished: 0 karena job belum selesai.
5. Menggunakan async untuk modul yang tidak mendukungnya. Error akan muncul saat runtime, dan perbaikannya biasanya memakan waktu karena tidak selalu jelas dari pesan errornya.
6. Meletakkan async pada handler. Handler tidak mendukung async; pindahkan ke task biasa.
Pada episode 16 ini, kita telah membahas cara menangani task yang berdurasi panjang — salah satu masalah paling nyata dalam operasional infrastruktur. Kalian sekarang memahami kenapa task sinkron rentan terhadap SSH timeout dan menghasilkan status yang ambigu, lalu bagaimana mekanisme async mengubah model eksekusinya: job dijalankan di background, koneksi SSH ditutup, dan status dipantau lewat async_status. Kita juga telah mempraktikkan async: 3600 + poll: 30 untuk OS upgrade, mode fire-and-forget poll: 0 untuk backup, pola async_status + until + retries, hingga reboot paralel dengan wait_for_connection. Terakhir, kita membahas keterbatasan async: modul yang tidak mendukungnya, risiko fire-and-forget, dan fakta bahwa async tidak bisa dipakai di handler.
Poin kunci yang perlu kalian bawa pulang:
async = batas waktu maksimum; poll = interval pengecekan; poll: 0 = fire-and-forget.ansible_job_id) adalah kunci untuk mengecek status job kapan pun.async_status + until: finished untuk menunggu job yang di-fire-and-forget.Mulai episode ini, kita secara perlahan "menjangkau ke dalam" Ansible: dari sekadar mengonsumsi modul bawaan, menjadi memahami bagaimana ekstensi bekerja. Di episode 17 selanjutnya kita akan melangkah lebih jauh ke fondasinya — Custom Modules & Custom Filters (Python Extension) — di mana kita akan menulis plugin filter Python untuk memanipulasi data dan membuat modul kustom menggunakan library AnsibleModule. Di sinilah kalian benar-benar berhenti dibatasi oleh apa yang tersedia, dan mulai membangun apa yang kalian butuhkan. Pastikan tetap semangat!