Belajar Ansible - Asynchronous Actions & Polling
Episode 16 of 31

Belajar Ansible - Asynchronous Actions & Polling

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.

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

Pendahuluan

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.

Pembahasan Utama

Mengapa Task Panjang Gagal: Anatomi SSH Timeout

Untuk memahami kenapa task panjang gagal, kita harus paham bagaimana Ansible menjalankan task secara sinkron. Alurnya seperti ini:

  1. Control node membuka koneksi SSH ke managed node.
  2. Modul Python dikirim dan dieksekusi (ingat episode 2: modul dikirim via SSH, dieksekusi, lalu hasil JSON dikembalikan).
  3. Control node memblokir dan menunggu — koneksi SSH tetap terbuka — sampai proses Python di remote selesai dan menulis JSON ke stdout.
  4. Koneksi ditutup.

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:

bash
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=0

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

ansible.cfg - tuning SSH connection
[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.

Konsep Async & Poll

Mekanisme async di Ansible bekerja dengan membalik logika eksekusi:

  1. Ansible menjalankan modul di managed node, tapi memintanya untuk berjalan sebagai proses background.
  2. Modul langsung mengembalikan job ID (ansible_job_id) beserta lokasi file hasil (di direktori async_dir, default ~/.ansible_async di remote).
  3. Koneksi SSH ditutup — tidak ada koneksi yang digantung.
  4. Control node (jika 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:

ModeasyncpollPerilaku
Sinkron (default)Tidak di-setTidak di-setControl node menunggu satu koneksi SSH sampai selesai
Async + pollingdi-set> 0 (default 10)Job jalan di background, koneksi ditutup, status dicek tiap poll detik
Async fire-and-forgetdi-set0Job 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.

Contoh 1: Long-Running Task dengan Async & Poll

Skenario paling umum adalah OS upgrade. Jalankan dengan async: 3600 (batas maksimum 1 jam) dan poll: 30 (cek status tiap 30 detik):

Upgrade OS dengan async & poll
---
- 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: 30

Saat playbook dijalankan, kalian akan melihat baris polling seperti ini — perhatikan bahwa koneksi dibuka-tutup berulang kali, bukan digantung:

bash
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=0

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

Contoh 2: Fire-and-Forget dengan poll: 0

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

  • Task panjang yang tidak menghalangi task lain (misalnya backup yang boleh jalan bersamaan dengan deploy aplikasi).
  • Kalian ingin menjalankan banyak task panjang secara paralel di beberapa host.
  • Task memicu reboot server — tidak ada gunanya playbook menunggu koneksi yang pasti mati.

Contoh playbook: kick-off backup besar, lalu playbook lanjut ke pekerjaan lain sementara backup berjalan:

Fire-and-forget backup dengan poll: 0
---
- 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.

Mengecek Status dengan ansible.builtin.async_status

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

Cek status job async
---
- 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: 15

Mari kita bedah bagian pengecekan ini:

  • async_status menanyakan status job ke managed node berdasarkan jid.
  • Variabel backup_result.finished akan bernilai 1 ketika job selesai (baik sukses maupun gagal).
  • Kombinasi 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:

PythonOutput async_status
{
    "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:

Reboot paralel dengan async
---
- 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

Tabel Referensi Parameter Async

ParameterTipeDefaultFungsi
asyncintTidak adaBatas waktu maksimum (detik) yang diizinkan untuk sebuah job
pollint10Interval (detik) pengecekan status; 0 = fire-and-forget
registerMenyimpan hasil task, termasuk ansible_job_id
async_status.jidstrJob ID yang ingin dicek statusnya
async_status.modestrstatusstatus untuk cek, cleanup untuk hapus file hasil job
async_dirstr~/.ansible_asyncDirektori di managed node tempat file hasil job disimpan
until / retries / delayPola polling manual saat dipakai bersama async_status

Keterbatasan & Risiko Async

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.

Kesalahan Umum dalam Menggunakan Async

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.

Penutup

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.
  • Job ID (ansible_job_id) adalah kunci untuk mengecek status job kapan pun.
  • Gunakan async_status + until: finished untuk menunggu job yang di-fire-and-forget.
  • Async menyelesaikan masalah koneksi, bukan menggantikan logika playbook yang baik.

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!

Belajar Ansible - Asynchronous Actions & Polling | Belajar Ansible