Belajar Ansible - Performance Tuning & Execution Strategies
Episode 15 of 31

Belajar Ansible - Performance Tuning & Execution Strategies

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.

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

Pendahuluan

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.

Pembahasan Utama

Ukur Dulu, Baru Optimalkan

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:

Mengukur durasi eksekusi playbook
time ansible-playbook -i inventory.yml playbook.yml

Hasil akhirnya akan terlihat seperti ini — perhatikan blok PLAY RECAP dan tiga angka waktu di bagian bawah:

bash
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.298s

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

ansible.cfg - mengaktifkan callback profile_tasks
[defaults]
callbacks_enabled = profile_tasks

Setelah diaktifkan, setiap akhir playbook akan menampilkan laporan durasi per task, dari yang paling lambat:

bash
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.843

Dari 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".

Parallelism dengan Parameter forks

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

ansible.cfg - meningkatkan parallelism
[defaults]
forks = 30
strategy = linear

Berikut panduan memilih nilai forks:

Nilai forksKapan Cocok Digunakan
5Default bawaan Ansible; cukup untuk lab dan percobaan
1020Fleet puluhan host dengan control node standar
3050Ratusan 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.

Mengaktifkan SSH Pipelining

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:

  1. Ansible membuka koneksi SSH ke managed node.
  2. Meng-upload file Python modul ke direktori sementara (/tmp/ansible-tmp-...) menggunakan SFTP.
  3. Menjalankan Python pada file tersebut via SSH.
  4. Menghapus file sementara.
  5. Menutup koneksi.

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

ansible.cfg - mengaktifkan SSH pipelining
[defaults]
pipelining = True
forks = 30

Important

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.

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

LinuxCek & nonaktifkan requiretty di managed node
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.

diff
 # ansible.cfg
 [defaults]
 pipelining = True
+forks = 30
-forks = 5

Note

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.

Fact Gathering & Fact Caching

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:

Playbook tanpa gathering facts
---
- name: Restart service tanpa perlu facts
  hosts: webservers
  gather_facts: false
  become: true
 
  tasks:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Ini 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).

ansible.cfg - fact caching dengan jsonfile
[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
gathering = smart
forks = 30
  • fact_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:

ansible.cfg - fact caching dengan Redis
[defaults]
fact_caching = redis
fact_caching_connection = localhost:6379:0

Perbandingan keduanya:

Aspekjsonfileredis
Setup tambahanTidak adaPerlu server Redis + library Python redis di control node
Media penyimpananFile JSON per host di direktori lokalKey-value dalam memori Redis
Kecepatan bacaCukup (I/O disk)Sangat cepat (in-memory)
Cocok untukFleet kecil–menengah, satu control nodeFleet besar, multi control node, HA
Risiko kegagalanSatu file rusak hanya memengaruhi satu hostRedis 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.

Execution Strategies

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.

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

Menetapkan strategy linear (eksplisit)
---
- 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: dist

Strategi free

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

Strategi free untuk task yang independen
---
- 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 -delete

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

StrategiMenunggu antar host?Kapan Dipakai
linearYa (barrier tiap task)Default; urutan task antar host penting
freeTidakHost independen, fleet heterogen, task per-host mandiri
host_pinnedTidakSeperti free, tapi task dijalankan secara berurutan per host (kompatibilitas dengan free)
debugYaMode 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 Bergiliran

Strategi 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: restarted
  • serial: 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:

Rolling update dengan ambang kegagalan
---
- name: Rolling update dengan guard failure
  hosts: webservers
  serial: 20%
  max_fail_percentage: 30
  become: true

Important

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.

Tabel Ringkasan Optimasi

Berikut ansible.cfg lengkap yang menggabungkan semua optimasi yang kita bahas, plus tabel referensi cepat:

ansible.cfg - konfigurasi optimasi lengkap
[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_tasks
Opsi ansible.cfgNilai ContohFungsi
forks30Jumlah host yang diproses paralel per task
pipeliningTrueKirim modul via stdin SSH, tanpa file sementara (butuh requiretty nonaktif)
fact_cachingjsonfile / redisBackend cache facts agar setup tidak berulang
fact_caching_connection/tmp/ansible_facts_cacheLokasi direktori / DSN koneksi cache
fact_caching_timeout86400Umur cache dalam detik
gatheringsmartJalankan setup hanya jika cache tidak valid
strategylinear / freeStrategi default seluruh playbook
callbacks_enabledprofile_tasksLaporan durasi per task

Kesalahan Umum dalam Tuning

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.

Penutup

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:

  • Selalu ukur dulu (profile_tasks) sebelum mengoptimalkan; optimasi tanpa data hanyalah tebakan.
  • forks dan pipelining adalah dua pengubah paling berdampak di ansible.cfg.
  • Fact caching mengubah gathering facts dari biaya berulang menjadi investasi sekali pakai.
  • serial: 20% atau serial: 1 adalah pola standar industri untuk deployment bergiliran.
  • Strategi 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!