Pada episode ini kita akan belajar mengoptimalkan kecepatan eksekusi Ansible mulai dari parallelism forks, SSH pipelining, fact caching, hingga strategi eksekusi linear, free, dan serial untuk rolling update.

Setelah di episode 14 sebelumnya kita membahas cara melindungi rahasia menggunakan Ansible Vault, playbook kalian sekarang tidak hanya lengkap, tapi juga aman: kredensial dan API key tersimpan terenkripsi dan siap dipakai dalam pipeline CI/CD. Tapi ada satu masalah yang muncul begitu skala infrastruktur membesar: kecepatan eksekusi.
Di episode-episode sebelumnya, kalian mungkin baru menjalankan playbook ke 2–3 server lab, jadi waktu eksekusi 1–2 menit tidak terasa. Sekarang bayangkan hal yang sama diterapkan ke 200 server di produksi. Setiap server membutuhkan setidaknya satu koneksi SSH per task, dan untuk gather_facts (modul setup), Ansible harus membuka koneksi SSH baru setiap kali playbook dijalankan. Hasilnya? Sebuah playbook sederhana yang tadinya 2 menit bisa meledak menjadi 30–40 menit. Di dunia kerja nyata, waktu eksekusi ini biasanya dipagari oleh change window atau maintenance window — jendela waktu yang sudah disepakati (biasanya tengah malam) di mana perubahan infrastruktur diperbolehkan. Jika playbook belum selesai dalam jendela itu, kalian harus membatalkan atau menunda perubahan — dan itu bukan skenario yang menyenangkan.
Di episode 15 ini kita akan membahas dua hal besar: performance tuning — cara mempercepat eksekusi tanpa mengubah logika playbook — dan execution strategies — cara mengatur bagaimana task didistribusikan ke banyak host. Ini adalah materi yang wajib dikuasai sebelum kalian naik ke ribuan host.
Prinsip pertama dalam optimasi apa pun adalah: jangan menebak, ukur. Sebelum mengubah ansible.cfg secara membabi buta, kalian harus tahu berapa lama playbook berjalan dan task mana yang paling lambat. Cara paling sederhana adalah membungkus perintah ansible-playbook dengan perintah time:
time ansible-playbook -i inventory.yml playbook.ymlHasil akhirnya akan terlihat seperti ini — perhatikan blok PLAY RECAP dan tiga angka waktu di bagian bawah:
PLAY RECAP *********************************************************************
web01 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web02 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
db01 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
real 2m34.512s
user 0m1.432s
sys 0m0.298sAngka real adalah total waktu yang kalian benar-benar tunggu. Tapi PLAY RECAP tidak memberi tahu kita task mana yang memakan waktu paling lama. Untuk itu, aktifkan callback profile_tasks:
[defaults]
callbacks_enabled = profile_tasksSetelah diaktifkan, setiap akhir playbook akan menampilkan laporan durasi per task, dari yang paling lambat:
Friday 07 August 2026 10:24:11 +0000 (0:00:00.547) 0:02:34.512 *********
===============================================================================
Gathering Facts --------------------------------------------------------- 0:01:04.223
Install Nginx ------------------------------------------------------------ 0:00:45.110
Copy website content ----------------------------------------------------- 0:00:31.891
Render virtualhost template ---------------------------------------------- 0:00:09.445
Start & enable Nginx ----------------------------------------------------- 0:00:03.843Dari laporan di atas, Gathering Facts menyedot lebih dari 1 menit dari total 2,5 menit. Ini adalah pola yang sangat umum — dan kabar baiknya, itu justru bagian yang paling mudah dioptimalkan, seperti yang akan kita bahas di bagian fact caching.
Tip
Biasakan mencatat baseline (durasi awal) sebelum mengubah konfigurasi, lalu bandingkan setelah setiap optimasi. Kalian hanya bisa membuktikan bahwa sebuah optimasi berhasil jika ada angka pembanding, bukan sekadar "rasanya lebih cepat".
forksKetika Ansible menjalankan sebuah playbook ke banyak host, ia tidak menghubungi semua host sekaligus. Secara default, Ansible hanya memproses 5 host secara paralel. Parameter yang mengontrol ini adalah forks di bagian [defaults] pada ansible.cfg.
Bayangkan forks seperti jumlah loket pembayaran tol. Semakin banyak loket yang dibuka, semakin banyak mobil (host) yang bisa dilayani bersamaan dalam satu waktu. Tapi membuka loket terlalu banyak tanpa sumber daya yang cukup justru membuat antrian di tiap loket semakin lambat — begitu pula dengan koneksi SSH yang dibuka Ansible.
[defaults]
forks = 30
strategy = linearBerikut panduan memilih nilai forks:
Nilai forks | Kapan Cocok Digunakan |
|---|---|
5 | Default bawaan Ansible; cukup untuk lab dan percobaan |
10 – 20 | Fleet puluhan host dengan control node standar |
30 – 50 | Ratusan host, control node dengan CPU multi-core yang memadai |
100+ | Ribuan host; wajib pantau CPU, memori, dan batas file descriptor |
Warning
forks yang terlalu besar bukan tanpa risiko. Setiap fork berarti proses Python + koneksi SSH terbuka di control node. Jika kalian set forks = 200 di sebuah VM kecil, yang terjadi bukan eksekusi lebih cepat, melainkan CPU pegged, memori habis, dan sebagian koneksi gagal terhubung (error Connection timed out atau Too many open files). Naikkan secara bertahap dan pantau top/htop di control node.
Satu hal penting yang sering disalahpahami: forks mengontrol jumlah host yang diproses bersamaan untuk satu task, bukan jumlah total task yang berjalan bersamaan. Dengan strategi linear (default), semua host harus menyelesaikan task ke-1 dulu sebelum ada host yang mulai task ke-2 — ini kita bahas lebih dalam di bagian execution strategies.
Ini salah satu optimasi dengan dampak paling besar untuk playbook yang menjalankan banyak task. Untuk memahami alasannya, kita harus melihat bagaimana Ansible mengeksekusi sebuah modul tanpa pipelining:
/tmp/ansible-tmp-...) menggunakan SFTP.Setiap task yang berjalan di setiap host berarti satu koneksi SSH + satu upload SFTP. Jika ada 30 host × 10 task, itu berarti 300 kali proses buka-tutup koneksi dan upload file. Overhead ini sangat nyata, terutama di jaringan dengan latensi tinggi (misalnya server yang tersebar lintas region).
Dengan SSH pipelining, Ansible tidak lagi membuat file sementara di remote. Kode Python modul dikirim langsung melalui stdin koneksi SSH, dieksekusi, lalu hasilnya dikembalikan — tanpa upload SFTP dan tanpa file yang harus dibersihkan. Efeknya: round-trip SSH per task berkurang drastis, dan untuk playbook dengan banyak task, waktu eksekusi bisa turun 20–50%.
[defaults]
pipelining = True
forks = 30Important
Pipelining membawa satu syarat penting yang sering membuat orang bingung: requiretty pada konfigurasi sudo harus dinonaktifkan. Ketika pipelining aktif, perintah sudo dijalankan tanpa TTY (karena inputnya datang dari pipe). Jika /etc/sudoers di managed node mengaktifkan Defaults requiretty, sudo menolak berjalan tanpa terminal dan playbook akan gagal dengan pesan seperti di bawah ini.
fatal: [web01]: FAILED! => {"msg": "sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"}Solusinya adalah memastikan file /etc/sudoers (atau file di /etc/sudoers.d/) tidak mengaktifkan requiretty. Karena distro modern seperti Ubuntu, Debian, dan RHEL 8+ secara default sudah tidak mengaktifkan requiretty, ini biasanya hanya masalah di server lawas atau konfigurasi yang diturunkan dari template lama. Kalian bisa mengoreksinya dengan:
sudo grep requiretty /etc/sudoers /etc/sudoers.d/ 2>/dev/null || echo "requiretty TIDAK aktif"Jika ada baris Defaults requiretty, hapus atau komentari, lalu simpan. Alternatifnya, kalian bisa mematikan pipelining hanya untuk host tertentu lewat variabel inventory ansible_ssh_pipelining: false — tetapi langkah ini sebaiknya jadi last resort, bukan kebiasaan.
# ansible.cfg
[defaults]
pipelining = True
+forks = 30
-forks = 5Note
Variabel ansible_ssh_pipelining juga bisa di-set per host/group di inventory untuk kasus di mana sebagian fleet tidak mendukung pipelining (misalnya server jaringan yang tidak memiliki shell interaktif penuh). Set false hanya untuk group yang bermasalah, biarkan sisanya menikmati percepatan.
Dari contoh laporan profile_tasks di atas, Gathering Facts adalah bagian termahal. Setiap kali sebuah playbook dijalankan dengan gather_facts: true (default Ansible), modul ansible.builtin.setup dieksekusi di setiap host untuk mengumpulkan ratusan fakta sistem — arsitektur CPU, memori, disk, OS, IP, dan sebagainya — yang kita pelajari di episode 7. Fakta-fakta ini jarang berubah, tapi Ansible tetap mengumpulkannya setiap kali, bahkan jika kalian hanya mengganti satu nilai variabel.
Ada dua strategi untuk menyelesaikan masalah ini:
Pertama, matikan gathering jika memang tidak dipakai. Jika playbook kalian tidak menyentuh ansible_facts sama sekali, tambahkan gather_facts: false di level play:
---
- name: Restart service tanpa perlu facts
hosts: webservers
gather_facts: false
become: true
tasks:
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restartedIni menghapus biaya setup sepenuhnya. Tapi ada satu konsekuensi yang harus kalian ingat: begitu gather_facts: false, variabel seperti ansible_facts['ansible_default_ipv4'] tidak lagi tersedia.
Kedua, aktifkan fact caching. Jika playbook kalian memang butuh facts, jangan kumpulkan berulang kali — simpan hasilnya di cache. Ansible menyediakan beberapa backend cache; dua yang paling populer adalah JSON file (tanpa dependensi tambahan) dan Redis (untuk skala besar).
[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
gathering = smart
forks = 30fact_caching = jsonfile memilih backend cache berupa file JSON.fact_caching_connection menentukan direktori tempat file cache disimpan.fact_caching_timeout adalah umur cache dalam detik (86400 = 24 jam).gathering = smart membuat Ansible hanya menjalankan setup jika belum ada cache yang valid.Untuk backend Redis, cukup ubah dua baris berikut:
[defaults]
fact_caching = redis
fact_caching_connection = localhost:6379:0Perbandingan keduanya:
| Aspek | jsonfile | redis |
|---|---|---|
| Setup tambahan | Tidak ada | Perlu server Redis + library Python redis di control node |
| Media penyimpanan | File JSON per host di direktori lokal | Key-value dalam memori Redis |
| Kecepatan baca | Cukup (I/O disk) | Sangat cepat (in-memory) |
| Cocok untuk | Fleet kecil–menengah, satu control node | Fleet besar, multi control node, HA |
| Risiko kegagalan | Satu file rusak hanya memengaruhi satu host | Redis down → semua cache hilang |
Tip
Efek samping yang sering tidak disadari dari fact caching: fakta dari host lain kini bisa diakses dari mana saja (tidak hanya host itu sendiri). Ini membuka pola yang sangat berguna, misalnya sebuah playbook yang menjalankan sesuatu di host web01 tapi butuh alamat IP db01 lewat hostvars['db01']['ansible_default_ipv4']['address'] — bahkan jika db01 tidak ada di play yang sama.
Sekarang kita masuk ke bagian kedua episode ini: execution strategies. Jika forks mengatur berapa banyak host yang diproses bersamaan, strategy mengatur bagaimana Ansible mendistribusikan task ke host-host tersebut.
linear (Default)Strategi linear adalah default Ansible. Semua host berjalan dalam "formasi": task ke-1 harus diselesaikan oleh semua host sebelum ada host yang mulai task ke-2. Bayangkan sebuah kelas ujian: tidak ada siswa yang boleh mengerjakan soal berikutnya sebelum seluruh kelas selesai mengerjakan soal sebelumnya.
Keuntungannya: perilakunya sangat bisa diprediksi, dan ini aman untuk playbook yang bergantung pada urutan task antar host (misalnya: task kedua di web01 mengandalkan task pertama di db01 yang sudah selesai).
Kekurangannya: host tercepat harus menunggu host terlambat di setiap task. Jika satu dari 30 server sedang lambat karena beban, seluruh fleet melambat bersamanya.
---
- name: Update semua server, urutan terjamin
hosts: all
strategy: linear
become: true
tasks:
- name: Update package list
ansible.builtin.apt:
update_cache: true
- name: Upgrade semua paket
ansible.builtin.apt:
upgrade: distfreeStrategi free melepaskan formasi itu. Setiap host mengerjakan seluruh daftar task secepat yang ia bisa, tanpa menunggu host lain. Host yang cepat menyelesaikan task 1–5 sementara host yang lambat masih di task 1 — itu diperbolehkan.
---
- name: Bersihkan cache di semua server, tiap host mandiri
hosts: all
strategy: free
become: true
tasks:
- name: Bersihkan cache package manager
ansible.builtin.shell:
cmd: apt-get clean
- name: Bersihkan log berumur lebih dari 30 hari
ansible.builtin.shell:
cmd: find /var/log -name "*.log" -mtime +30 -deleteKapan free masuk akal? Ketika task-task bersifat independen per host dan kalian punya fleet yang heterogen (sebagian server jauh lebih cepat dari yang lain). Contoh nyata: pembersihan log, penarikan data statistik per server, atau benchmarking.
Kapan free berbahaya? Ketika ada dependensi urutan antar host. Jika playbook kalian mengandalkan fakta bahwa semua host sudah melewati task tertentu sebelum ada yang lanjut, strategi free akan menghancurkan jaminan itu dan bisa memicu race condition yang sulit dilacak.
Perbandingan ringkas strategi yang tersedia:
| Strategi | Menunggu antar host? | Kapan Dipakai |
|---|---|---|
linear | Ya (barrier tiap task) | Default; urutan task antar host penting |
free | Tidak | Host independen, fleet heterogen, task per-host mandiri |
host_pinned | Tidak | Seperti free, tapi task dijalankan secara berurutan per host (kompatibilitas dengan free) |
debug | Ya | Mode debugging; memungkinkan eksekusi task satu per satu secara interaktif |
Caution
Strategi free juga mengubah perilaku handlers (episode 6): dengan free, handler bisa dieksekusi kapan pun saat task yang menotify-nya selesai — tidak dijamin berjalan di akhir play seperti pada linear. Jangan kombinasikan free dengan logika yang sensitif terhadap urutan restart/reload service.
serial: Rolling Update BergiliranStrategi ketiga bukan benar-benar strategi terpisah — serial adalah parameter play yang bekerja di atas strategy yang dipilih, dan fungsinya adalah membatasi berapa banyak host yang boleh memproses play pada satu waktu. Bayangkan serial seperti mengatur kapasitas pintu masuk: hanya segelintir host yang masuk dan menyelesaikan seluruh play, baru batch berikutnya menyusul.
Ini adalah fondasi dari rolling update — pola deployment yang menjaga layanan tetap tersedia. Alih-alih me-restart NGINX di 100 server sekaligus (yang berarti downtime total jika konfigurasi salah), kalian melakukannya 20% server dulu, verifikasi, lalu lanjut.
---
- name: Rolling update NGINX - 20% server per batch
hosts: webservers
serial: 20%
become: true
tasks:
- name: Update & restart Nginx
ansible.builtin.apt:
name: nginx
state: latest
notify: Restart Nginx
handlers:
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restartedserial: 20% membagi 100 server menjadi 5 batch @ 20 server. Play berjalan penuh di batch pertama, lalu batch kedua, dan seterusnya. Persentase dihitung dari jumlah host di play.serial: 1 adalah mode paling konservatif: satu server selesai sepenuhnya (termasuk handler) sebelum server berikutnya mulai. Ini menghilangkan downtime total, tetapi juga paling lambat.serial: [1, 10, 50] adalah progressive batching: mulai dengan 1 server (untuk menguji), lalu 10, lalu 50. Pola ini populer karena memberi "safety valve" — jika 1 server pertama gagal, playbook berhenti sebelum menyentuh sisanya.Kalian bisa mengombinasikan serial dengan max_fail_percentage untuk membatalkan playbook otomatis jika kegagalan dalam satu batch melebihi ambang batas:
---
- name: Rolling update dengan guard failure
hosts: webservers
serial: 20%
max_fail_percentage: 30
become: trueImportant
Dengan serial, PLAY RECAP akan muncul berkali-kali — satu kali untuk setiap batch. Jangan mengira itu bug. Selain itu, ingat bahwa serial mengatur eksekusi play secara keseluruhan, jadi semua task (termasuk handler) pada batch pertama harus tuntas sebelum batch kedua dimulai. Inilah yang membuatnya ideal untuk deployment zero-downtime: kalian bisa memverifikasi batch pertama benar-benar sehat sebelum sisanya ikut di-update.
Berikut ansible.cfg lengkap yang menggabungkan semua optimasi yang kita bahas, plus tabel referensi cepat:
[defaults]
forks = 30
pipelining = True
strategy = linear
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
gathering = smart
callbacks_enabled = profile_tasksOpsi ansible.cfg | Nilai Contoh | Fungsi |
|---|---|---|
forks | 30 | Jumlah host yang diproses paralel per task |
pipelining | True | Kirim modul via stdin SSH, tanpa file sementara (butuh requiretty nonaktif) |
fact_caching | jsonfile / redis | Backend cache facts agar setup tidak berulang |
fact_caching_connection | /tmp/ansible_facts_cache | Lokasi direktori / DSN koneksi cache |
fact_caching_timeout | 86400 | Umur cache dalam detik |
gathering | smart | Jalankan setup hanya jika cache tidak valid |
strategy | linear / free | Strategi default seluruh playbook |
callbacks_enabled | profile_tasks | Laporan durasi per task |
Agar pengalaman tuning kalian tidak berakhir dengan migrain, berikut daftar kesalahan yang paling sering terjadi di lapangan:
1. Mengaktifkan pipelining tanpa menonaktifkan requiretty. Ini penyebab kegagalan "privilege escalation prompt" paling umum setelah pipelining = True. Periksa dulu /etc/sudoers di managed node.
2. forks di set raksasa tanpa kapasitas. forks = 500 tidak membuat Ansible lebih cepat jika control node hanya punya 2 core. Mulai dari 20–30 dan ukur dengan profile_tasks.
3. gather_facts menyala padahal tidak dipakai. Kebiasaan default gather_facts: true membuat setiap playbook membayar biaya setup. Set gather_facts: false untuk play yang tidak butuh facts.
4. Fact caching dengan timeout terlalu pendek. Jika fact_caching_timeout cuma 300 detik, cache hampir selalu kedaluwarsa sebelum dipakai ulang, dan kalian tetap gathering facts terus-menerus — tidak ada yang dihemat.
5. Menggunakan free pada playbook yang sensitif urutan. Task yang punya dependensi antar host akan menjadi rawan race condition.
6. Lupa bahwa serial mengeksekusi play berulang. Jumlah notifikasi PLAY RECAP akan sama dengan jumlah batch; pahami ini agar tidak panik melihat output berulang.
Pada episode 15 ini, kita telah membahas cara membuat Ansible jauh lebih efisien di skala besar. Kalian belajar mengukur performa dengan time dan callback profile_tasks, mengatur parallelism dengan forks, mengaktifkan SSH pipelining (plus syarat requiretty yang wajib dipahami), dan menghindari gathering facts berulang dengan fact caching berbasis jsonfile maupun redis. Terakhir, kita mengeksplorasi execution strategies: linear untuk urutan yang terjamin, free untuk host yang mandiri, dan serial — senjata utama untuk rolling update tanpa downtime.
Poin kunci yang perlu kalian bawa pulang:
profile_tasks) sebelum mengoptimalkan; optimasi tanpa data hanyalah tebakan.forks dan pipelining adalah dua pengubah paling berdampak di ansible.cfg.serial: 20% atau serial: 1 adalah pola standar industri untuk deployment bergiliran.free itu kuat, tapi gunakan hanya untuk task yang benar-benar independen per host.Namun, ada satu kelas masalah yang tidak bisa diselesaikan oleh tuning mana pun: task yang memang berdurasi sangat panjang — OS upgrade skala besar, migrasi database, atau backup multi-terabyte. Menjalankan task seperti ini secara sinkron hampir pasti membuat koneksi SSH timeout, dan hasilnya adalah kegagalan yang ambigu. Di episode 16 selanjutnya kita akan membahas Asynchronous Actions & Polling — mekanisme Ansible untuk menjalankan task panjang di background dengan parameter async dan poll, plus mode fire-and-forget yang membebaskan playbook dari menunggu. Pastikan tetap semangat!