Performance Anti Pattern #
Kecepatan dan efisiensi eksekusi adalah kunci keberhasilan otomatisasi infrastruktur skala besar. Saat kita menulis playbook Ansible untuk pertama kalinya, fokus utama kita biasanya hanya pada kebenaran logika — memastikan bahwa server terkonfigurasi dengan benar. Namun, seiring bertambahnya jumlah server yang dikelola (managed nodes), efisiensi playbook mulai diuji. Playbook yang awalnya selesai dalam 2 menit untuk 3 server dapat membengkak menjadi 45 menit ketika dijalankan untuk 100 server. Lambatnya eksekusi ini sering kali bukan disebabkan oleh kapasitas server yang tidak memadai, melainkan oleh keputusan desain playbook yang memicu operasi berulang dan tidak efisien.
Feedback loop yang lambat akan memperlambat siklus pengembangan, menghambat proses deployment darurat, dan memperluas jendela risiko kegagalan (maintenance window). Sebagian besar bottleneck di Ansible dapat diidentifikasi dan diperbaiki dengan mudah tanpa harus mengubah logika akhir sistem. Dengan memahami bagaimana Ansible berinteraksi dengan server remote di balik layar, kita dapat mengoptimalkan playbook agar berjalan berkali-kali lipat lebih cepat. Dalam artikel ini, kita akan mengeksplorasi berbagai anti pattern performa, mengapa pola tersebut memperlambat kita, serta cara menerapkan solusi optimasi yang tepat.
1. Mengaktifkan Gathering Facts Tanpa Menggunakannya #
Secara default, Ansible akan menjalankan modul setup di awal setiap play untuk mengumpulkan fakta (facts) tentang server target, seperti detail sistem operasi, alamat IP, alokasi memori, hingga status disk. Proses ini dikenal sebagai gathering facts.
Mengapa Ini Berbahaya? #
Proses pengumpulan fakta membutuhkan waktu sekitar 1 hingga 3 detik per host. Ansible harus melakukan SSH handshake, mentransfer modul pengumpul fakta, mengeksekusinya di server remote, dan mengembalikan data JSON yang sangat besar ke control node. Jika playbook kita memiliki banyak play dan mengelola ratusan server, namun kita sama sekali tidak menggunakan variabel bawaan Ansible (ansible_*) di dalam logika task kita, maka proses ini membuang waktu dan resource komputasi yang sangat besar secara sia-sia.
# ANTI-PATTERN: Membiarkan gathering facts menyala di play yang tidak membutuhkannya
- name: Restart Layanan Aplikasi Web
hosts: webservers
# gather_facts secara default bernilai true jika tidak didefinisikan!
tasks:
- name: Restart layanan systemd nginx
systemd:
name: nginx
state: restarted
become: true
# Playbook ini berjalan lambat di ratusan host karena mengumpulkan data hardware
# yang sama sekali tidak dibaca oleh modul systemd di atas.
Solusi mendasar adalah dengan menonaktifkan gather_facts pada play yang hanya melakukan tugas-tugas operasional atau konfigurasi statis yang tidak bergantung pada kondisi hardware server remote.
# BENAR: Menonaktifkan gathering facts untuk tugas operasional sederhana
- name: Restart Layanan Aplikasi Web secara Cepat
hosts: webservers
gather_facts: false # ✓ BENAR: Menghemat waktu bootstrap koneksi SSH awal
tasks:
- name: Restart layanan systemd nginx
systemd:
name: nginx
state: restarted
become: true
Jika playbook kita memang membutuhkan informasi fakta server tetapi tersebar di beberapa play yang berbeda, kita dapat mengaktifkan sistem fact caching. Dengan cara ini, fakta server hanya dikumpulkan sekali di awal, disimpan dalam cache lokal (seperti file JSON atau database Redis), dan digunakan kembali pada eksekusi play berikutnya tanpa perlu melakukan kueri ulang ke server remote.
# BENAR: Mengonfigurasi Fact Caching di ansible.cfg
# File: ansible.cfg
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
fact_caching_timeout = 86400 # Cache berlaku selama 24 jam
2. Menggunakan Loop untuk Task yang Mendukung Operasi Batch #
Ansible menyediakan directive loop (atau with_items pada versi lama) untuk menjalankan satu task secara berulang dengan parameter input yang berbeda-beda.
Mengapa Ini Berbahaya? #
Jika kita menggunakan loop pada modul-modul sistem paket manager seperti apt, yum, atau pip, Ansible akan mengeksekusi modul tersebut secara individual sebanyak jumlah item di dalam loop. Artinya, jika ada 10 paket yang ingin diinstal, Ansible akan memanggil perintah paket manager 10 kali secara terpisah. Setiap panggilan membutuhkan waktu penyelesaian (round-trip) SSH baru, inisialisasi modul, penguncian database paket (seperti apt lock), dan pemeriksaan dependensi. Hal ini membuat waktu instalasi menjadi sangat lambat.
# ANTI-PATTERN: Menggunakan loop untuk instalasi paket satu per satu
- name: Instalasi Paket Sistem Pemeliharaan
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql-client
- redis-tools
- python3-pip
- git
become: true
# ✗ JANGAN: Ansible akan memanggil perintah apt-get install sebanyak 5 kali terpisah!
Sebagai gantinya, kita harus mengirimkan daftar (list) paket secara langsung ke parameter modul. Sebagian besar modul bawaan Ansible seperti apt, yum, package, dan pip telah dioptimalkan untuk menerima list dan mengeksekusinya dalam satu operasi batch tunggal.
# BENAR: Melakukan instalasi massal (batch install) dalam satu pemanggilan modul
- name: Instalasi Paket Sistem Pemeliharaan secara Batch
apt:
name:
- nginx
- postgresql-client
- redis-tools
- python3-pip
- git
state: present
update_cache: true
become: true
# ✓ BENAR: Hanya satu pemanggilan perintah apt-get install. Jauh lebih cepat dan efisien.
[!TIP] Pola optimasi batch ini juga berlaku untuk modul-modul lain. Misalnya, saat menyalin banyak file statis, alih-alih menggunakan
loopdengan modulcopyyang memakan waktu SSH berulang kali, kita bisa menggunakan modulsynchronizeyang memanfaatkan utilitas rsync di balik layar untuk mentransfer file secara massal dalam satu koneksi tunggal.
3. Membiarkan Nilai Forks pada Konfigurasi Bawaan (Default forks = 5) #
Secara default, Ansible dikonfigurasi untuk memproses tugas secara paralel pada maksimal 5 server sekaligus. Parameter ini diatur oleh opsi forks di konfigurasi ansible.cfg.
Mengapa Ini Berbahaya? #
Jika kita memiliki inventory yang berisi 100 server remote, Ansible hanya akan mengeksekusi task pada 5 server pertama, menunggu semuanya selesai, lalu melanjutkan ke 5 server berikutnya. Untuk menyelesaikan satu task di seluruh host, Ansible membutuhkan 20 batch antrean (100 / 5 = 20). Jika satu task membutuhkan waktu 10 detik, total waktu eksekusi task tersebut menjadi 200 detik. Ini menciptakan antrean bottleneck yang sangat panjang padahal resource CPU dan memori di control node kita masih sangat longgar.
# ANTI-PATTERN: Membiarkan batas forks default membatasi paralelisasi
# File: ansible.cfg (Atau tanpa konfigurasi kustom sama sekali)
[defaults]
# forks = 5 # ✗ JANGAN: Batas bawaan yang terlalu rendah untuk infrastruktur modern
Kita harus menaikkan nilai forks sesuai dengan kapasitas hardware control node (CPU dan RAM) serta bandwidth jaringan kita. Sebagai aturan praktis, kita dapat menaikkan nilai forks ke angka 20 hingga 50 untuk mengoptimalkan paralelisasi tanpa membebani control node secara berlebihan.
# BENAR: Meningkatkan nilai forks di ansible.cfg
# File: ansible.cfg
[defaults]
forks = 30 # ✓ BENAR: Memproses 30 server secara bersamaan, mempercepat eksekusi hingga 600%
Untuk menentukan angka forks yang optimal bagi infrastruktur kita, lakukan pengujian bertahap sambil memantau penggunaan resource sistem pada control node menggunakan perintah berikut:
# Monitor utilisasi resource CPU dan memory control node saat playbook berjalan
ansible-playbook -i inventory/production/ site.yml &
watch -n1 'ps aux | grep ansible | head -5; echo "---"; uptime'
4. Mengabaikan Optimasi Koneksi SSH (Pipelining dan Multiplexing) #
Secara default, proses eksekusi task Ansible pada target host melalui protokol SSH melibatkan beberapa tahapan: Ansible menghasilkan file script python sementara, membungkusnya, mentransfer file tersebut ke direktori sementara di host target menggunakan SFTP/SCP, memberikan izin eksekusi, menjalankan file tersebut, dan akhirnya menghapus file tersebut setelah selesai.
Mengapa Ini Berbahaya? #
Siklus salin-dan-jalankan file script ini terjadi untuk setiap task pada setiap host. Ini menghasilkan overhead transfer file dan inisialisasi koneksi SSH yang sangat tinggi, terutama jika playbook kita memiliki puluhan task pendek. Tanpa optimasi koneksi SSH, sebagian besar waktu eksekusi playbook kita dihabiskan untuk menegosiasikan enkripsi koneksi dan mentransfer file script kecil.
Berikut adalah diagram perbandingan antara proses eksekusi default (tanpa optimasi) dibandingkan dengan proses eksekusi yang telah dioptimalkan menggunakan Pipelining dan SSH Multiplexing (ControlPersist):
sequenceDiagram
autonumber
participant C as "Control Node (Ansible)"
participant S as "Managed Node (Server)"
Note over C,S: Tanpa Optimasi SSH (Default)
C->>S: "Inisiasi TCP & Kriptografi Handshake SSH"
S-->>C: "Sesi SSH Terbuka"
C->>S: "Transfer file script modul python (.py)"
S-->>C: "Transfer selesai"
C->>S: "Jalankan script python remote"
S-->>C: "Hasil eksekusi modul"
C->>S: "Tutup Koneksi SSH"
Note over C,S: Dengan Optimasi (Pipelining & ControlPersist)
C->>S: "Inisiasi TCP & Kriptografi Handshake SSH (Sekali saja)"
S-->>C: "Sesi Multiplexing Terbuka di socket lokal"
C->>S: "Eksekusi langsung via stdin (Pipelined)"
S-->>C: "Hasil eksekusi modul"
C->>S: "Eksekusi task berikutnya via socket yang sama"
S-->>C: "Hasil eksekusi modul"
Note over C,S: Soket tetap terbuka selama ControlPersist (e.g. 60s)
Untuk mengatasi masalah overhead koneksi ini, kita harus mengaktifkan dua fitur penting di konfigurasi SSH Ansible: Pipelining dan SSH Multiplexing (ControlPersist).
# BENAR: Mengaktifkan Pipelining dan Multiplexing SSH di ansible.cfg
# File: ansible.cfg
[defaults]
forks = 20
[ssh_connection]
pipelining = True
# ✓ BENAR: Pipelining mengeksekusi modul python langsung melalui stdin SSH tanpa mentransfer file.
# Ini dapat mengurangi waktu eksekusi task hingga 30% - 50%.
ssh_args = -o ControlMaster=auto -o ControlPersist=120s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
# ✓ BENAR: Mengaktifkan multiplexing SSH. Koneksi kriptografi awal ke server remote akan disimpan
# sebagai file soket di /tmp/. Task-task berikutnya akan menggunakan kembali terowongan (tunnel) SSH
# yang sudah ada tanpa perlu melakukan handshake ulang. ControlPersist=120s menjaga terowongan tetap
# terbuka selama 2 menit setelah task terakhir selesai.
[!WARNING] Saat mengaktifkan
pipelining = True, pastikan konfigurasi keamanan di managed node (server remote) tidak memiliki opsirequirettydi file/etc/sudoers. Jika opsi tersebut aktif, eksekusi privilege escalation via sudo akan gagal karena pipelining tidak menyediakan pseudo-TTY. Nonaktifkanrequirettyuntuk user ansible untuk memanfaatkan fitur ini.
5. Menjalankan Task Lokal Berulang Tanpa run_once
#
Ada kalanya kita perlu menjalankan task yang berinteraksi dengan control node (localhost), seperti mengambil kredensial dari API eksternal (misalnya HashiCorp Vault), membuat direktori backup lokal, atau mencatat notifikasi ke Slack.
Mengapa Ini Berbahaya? #
Jika kita menggunakan directive delegate_to: localhost tanpa menyertakan run_once: true, Ansible akan mengeksekusi task tersebut secara berulang sebanyak jumlah target host yang ada di dalam play kita. Jika kita mengelola 50 server, Ansible akan mengirimkan 50 request API yang identik ke Vault atau Slack. Hal ini dapat memicu pemblokiran batas request (rate limiting) dari API target, membuang bandwidth, dan memperlambat playbook secara drastis karena pemrosesan antrean yang tidak perlu.
# ANTI-PATTERN: Menjalankan task delegasi lokal berulang kali untuk setiap host
- name: Ambil Token Otorisasi API dari Server Pusat
hosts: appservers
tasks:
- name: Dapatkan token akses sementara
uri:
url: "https://vault.company.internal/v1/auth/token"
method: POST
body_format: json
body:
role_id: "app-role"
secret_id: "app-secret"
register: vault_response
delegate_to: localhost
# ✗ JANGAN: Task ini dipanggil berulang kali untuk setiap host di appservers!
# Jika ada 100 host, 100 request HTTP akan dikirim ke Vault dengan hasil yang persis sama.
Solusinya adalah dengan menambahkan parameter run_once: true pada task delegasi lokal tersebut. Ansible hanya akan mengeksekusi task tersebut satu kali saja, lalu membagikan hasil variabel yang diregistrasikan ke seluruh host lain yang ada di dalam play.
# BENAR: Menggunakan run_once untuk membatasi eksekusi task lokal tunggal
- name: Ambil Token Otorisasi API secara Efisien
hosts: appservers
tasks:
- name: Dapatkan token akses sementara (Cukup sekali saja)
uri:
url: "https://vault.company.internal/v1/auth/token"
method: POST
body_format: json
body:
role_id: "app-role"
secret_id: "app-secret"
register: vault_response
delegate_to: localhost
run_once: true # ✓ BENAR: Dieksekusi 1 kali saja untuk seluruh play
no_log: true
- name: Terapkan token ke file konfigurasi aplikasi
template:
src: "config.json.j2"
dest: "/etc/myapp/config.json"
# Hasil 'vault_response' dari host pertama otomatis dapat diakses oleh host lain
6. Penjadwalan Serial Tanpa Paralelisasi Tugas (Serial vs Free Strategy) #
Secara default, Ansible menggunakan strategi eksekusi linear. Dalam strategi ini, Ansible akan menyelesaikan task pertama di seluruh host yang aktif sebelum beralih ke task kedua secara bersamaan.
Mengapa Ini Berbahaya? #
Jika salah satu host mengalami penundaan (delay) yang lama saat menjalankan suatu task (misalnya karena masalah performa disk atau koneksi jaringan lambat), seluruh antrean playbook akan tertahan. Server-server lain yang sudah menyelesaikan task pertama lebih cepat terpaksa menunggu server lambat tersebut selesai sebelum bisa melanjutkan ke task berikutnya.
Kita dapat mengoptimalkan alur kerja ini dengan menyesuaikan strategi eksekusi atau menggunakan parameter serial dengan bijak.
# ALTERNATIF: Menggunakan Strategi Free untuk Tugas Independen
- name: Pengumpulan Log dan Analisis Sistem
hosts: database_servers
strategy: free # ✓ BENAR: Setiap host akan mengeksekusi semua task secepat mungkin
# tanpa harus menunggu host lain menyelesaikan task yang sama. Cocok untuk play
# yang bertugas melakukan pengumpulan data atau verifikasi status independen.
tasks:
- name: Kumpulkan statistik disk
command: df -h
- name: Kumpulkan status memory
command: free -m
[!IMPORTANT] Jangan gunakan
strategy: freejika task-task di dalam playbook kita memiliki dependensi urutan yang ketat di antara host yang berbeda (misalnya: mematikan load balancer -> update app server -> menyalakan load balancer). Untuk skenario deployment aplikasi tanpa downtime (rolling update), gunakan opsiserialdengan proporsi persentase yang seimbang agar tidak memperlambat rilis namun tetap membatasi blast radius kegagalan.
# BENAR: Rolling Update dengan Proporsi Serial yang Seimbang
- name: Deploy Aplikasi Web Cluster
hosts: webservers
serial: "25%" # ✓ BENAR: Memproses 25% server sekaligus dalam satu batch.
# Jika batch pertama berhasil, lanjut ke batch berikutnya. Ini memberikan keamanan
# tanpa harus mengorbankan performa eksekusi seperti halnya 'serial: 1'.
tasks:
- name: Update source code
git:
repo: "https://github.com/app/web.git"
dest: "/var/www/html"
7. Mengabaikan Profiling Task untuk Menganalisis Bottleneck #
Mengoptimalkan performa playbook tanpa melakukan pengukuran terlebih dahulu adalah tindakan spekulatif yang sering kali salah sasaran. Kita mungkin menghabiskan waktu berjam-jam untuk merestrukturisasi task yang sebenarnya hanya memakan waktu 2 detik dari total 15 menit waktu eksekusi.
Mengapa Ini Berbahaya? #
Tanpa visualisasi data durasi eksekusi, kita tidak tahu di mana letak hambatan sebenarnya. Apakah karena proses pembaruan repositori paket manager? Ataukah karena modul template yang lambat memproses logika Jinja2? Kita memerlukan sistem profiling otomatis untuk mengukur durasi setiap task.
Kita dapat mengaktifkan plugin callback bawaan Ansible untuk melakukan profiling waktu eksekusi tugas secara detail tanpa perlu menambahkan tool pihak ketiga.
# BENAR: Mengaktifkan Callback Profiling di ansible.cfg
# File: ansible.cfg
[defaults]
# Aktifkan callback bawaan untuk profiling tugas, role, dan timer total
callbacks_enabled = profile_tasks, profile_roles, timer
Setelah callback diaktifkan, setiap kali kita menjalankan playbook, Ansible akan menampilkan ringkasan durasi task terlama di akhir output terminal.
# Contoh Output Terminal Profiling setelah Playbook Selesai:
Thursday 18 June 2026 15:45:10 +0700 (0:00:03.245) 0:08:42.123
===================================================================
Install all packages in batch ------------------------------- 185.34s ← Target Utama Optimasi!
Gathering Facts ---------------------------------------------- 72.15s ← Aktifkan Fact Caching!
Compile application assets from source ----------------------- 54.12s
Verify database migration state ------------------------------ 12.04s
Template configuration file ----------------------------------- 8.11s
Dengan data ini, kita tahu persis task mana yang harus kita optimalkan terlebih dahulu. Fokuskan upaya optimasi kita pada task-task teratas yang memakan persentase waktu terbesar.
Ringkasan #
- Matikan
gather_facts: false— Untuk semua play operasional yang tidak membaca properti hardware target host. Gunakan fact caching jika fakta tetap dibutuhkan di beberapa play.- Hindari Loop Paket Manager — Kirim daftar paket sebagai list langsung ke argumen modul (
apt,yum,pip) untuk mengeksekusinya dalam satu batch tunggal, alih-alih mengulangnya satu per satu.- Tingkatkan Batas
forks— Jangan biarkan konfigurasiforksdefault di angka 5 membatasi proses paralelisasi pada infrastruktur skala besar. Set minimal di angka 20 hingga 50.- Aktifkan Pipelining dan ControlPersist — Konfigurasikan opsi SSH pada
ansible.cfguntuk meminimalkan overhead cryptographic handshake dan proses transfer file script sementara.- Batasi Delegasi Lokal dengan
run_once— Selalu sertakanrun_once: truepada task yang menggunakandelegate_to: localhostuntuk mencegah pengiriman request berulang yang identik ke API eksternal.- Pilih Strategi Eksekusi yang Sesuai — Gunakan
strategy: freeuntuk tugas audit atau pengumpulan data independen, dan terapkan proporsiserialberbasis persentase untuk rolling update yang efisien.- Lakukan Profiling Durasi secara Teratur — Aktifkan callback
profile_tasksdanprofile_rolesdiansible.cfguntuk mendapatkan visualisasi data durasi guna memandu prioritas optimasi performa.
← Sebelumnya: Security Anti Pattern Berikutnya: CI/CD Anti Pattern →