Playbook Anti Pattern #
Playbook yang bekerja bukan berarti playbook yang baik. Banyak playbook yang berjalan tanpa error tapi mengandung pola yang membuatnya rapuh, lambat, tidak idempoten, atau sulit di-maintain seiring waktu. Anti pattern dalam playbook sering muncul bukan karena penulis tidak kompeten, melainkan karena mereka memindahkan kebiasaan dari shell scripting ke Ansible tanpa menyesuaikan dengan cara kerja Ansible yang deklaratif. Artikel ini membahas anti pattern paling umum beserta cara memperbaikinya.
Anti Pattern 1: Terlalu Banyak command/shell #
Ini adalah anti pattern paling sering ditemukan — menggunakan command atau shell untuk sesuatu yang sudah punya module:
# ANTI-PATTERN: menggunakan shell untuk operasi yang punya module
- name: Install nginx
shell: apt-get install -y nginx
- name: Copy file konfigurasi
shell: cp /tmp/nginx.conf /etc/nginx/nginx.conf
- name: Restart nginx
shell: systemctl restart nginx
- name: Buat direktori
shell: mkdir -p /opt/app/logs
- name: Download file
shell: wget -O /tmp/app.tar.gz https://example.com/app.tar.gz
# BENAR: gunakan module yang tepat
- name: Install nginx
apt:
name: nginx
state: present
- name: Deploy konfigurasi nginx
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Buat direktori
file:
path: /opt/app/logs
state: directory
mode: '0755'
- name: Download file
get_url:
url: https://example.com/app.tar.gz
dest: /tmp/app.tar.gz
Module bawaan Ansible: idempoten, mengembalikan changed dengan benar, mendukung check mode, dan aman dijalankan berulang kali. command/shell tidak menjamin ini.
Anti Pattern 2: Mengabaikan Idempotency #
# ANTI-PATTERN: task yang menghasilkan changed setiap kali dijalankan
- name: Tambahkan baris ke file konfigurasi
shell: echo "export PATH=/opt/bin:$PATH" >> /etc/profile
# Setiap kali dijalankan, baris ini ditambahkan lagi dan lagi
- name: Buat user
command: useradd -m appuser
# Gagal jika user sudah ada — bukan idempoten
- name: Set permission
shell: chmod 755 /opt/app
# Selalu mengembalikan changed meski permission sudah benar
# BENAR: module yang idempoten secara natural
- name: Tambahkan PATH ke profile
lineinfile:
path: /etc/profile
line: "export PATH=/opt/bin:$PATH"
state: present
# Tidak menambahkan duplikat jika baris sudah ada
- name: Buat user appuser
user:
name: appuser
state: present
# Tidak gagal jika sudah ada, tidak melakukan perubahan yang tidak perlu
- name: Set permission /opt/app
file:
path: /opt/app
mode: '0755'
# Hanya changed jika permission memang berubah
Anti Pattern 3: Hardcode Nilai #
# ANTI-PATTERN: nilai hardcode di dalam task
- name: Deploy konfigurasi
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
vars:
db_host: 10.0.2.5 # Hardcode IP
db_port: 5432 # Hardcode port
max_connections: 100 # Hardcode nilai
- name: Buat direktori aplikasi
file:
path: /opt/myapp # Hardcode path
owner: myapp # Hardcode user
state: directory
# BENAR: semua nilai melalui variabel
# roles/myapp/defaults/main.yml
db_host: "{{ hostvars[groups['dbservers'][0]]['ansible_default_ipv4']['address'] }}"
db_port: 5432
max_connections: 100
app_dir: /opt/myapp
app_user: myapp
# roles/myapp/tasks/main.yml
- name: Deploy konfigurasi
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
# Semua nilai dari variabel
- name: Buat direktori aplikasi
file:
path: "{{ app_dir }}"
owner: "{{ app_user }}"
state: directory
Anti Pattern 4: Handler yang Tidak Digunakan #
# ANTI-PATTERN: restart langsung di task, bukan via handler
- name: Update konfigurasi nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: Restart nginx setelah update config
systemd:
name: nginx
state: restarted
# Nginx selalu di-restart meski config tidak berubah!
# Dan jika ada 5 task yang mengubah config, nginx di-restart 5 kali
# BENAR: gunakan notify dan handler
- name: Update konfigurasi nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Update SSL certificate
copy:
src: app.crt
dest: /etc/ssl/certs/app.crt
notify: Restart nginx # Nginx tetap hanya di-restart sekali di akhir play
# handlers/main.yml
handlers:
- name: Restart nginx
systemd:
name: nginx
state: restarted
Handler hanya dieksekusi jika ada notify yang dipanggil, dan hanya sekali di akhir play meski di-notify berkali-kali.
Anti Pattern 5: Mengabaikan Return Value #
# ANTI-PATTERN: mengabaikan return value, menggunakan shell dengan logika kondisional
- name: Cek apakah service berjalan
shell: systemctl is-active myapp
register: service_status
ignore_errors: true
- name: Start service jika tidak berjalan
shell: systemctl start myapp
when: service_status.rc != 0
# BENAR: gunakan module yang tepat dengan state yang diinginkan
- name: Pastikan service berjalan
systemd:
name: myapp
state: started
enabled: true
# Jika sudah berjalan: ok (tidak changed)
# Jika belum berjalan: start dan changed
# Tidak perlu pengecekan manual
Anti Pattern 6: Play Monolitik Tanpa Struktur #
# ANTI-PATTERN: satu play besar dengan ratusan task tanpa organisasi
- name: Setup semua server
hosts: all
tasks:
- name: Install nginx
...
- name: Konfigurasi nginx
...
- name: Install postgresql
...
- name: Setup database
...
- name: Deploy aplikasi
...
# ... 200 task lagi ...
# BENAR: gunakan role untuk organisasi dan reusability
- name: Setup server
hosts: all
roles:
- common
- name: Setup web servers
hosts: webservers
roles:
- nginx
- certbot
- name: Setup database servers
hosts: dbservers
roles:
- postgresql
- name: Deploy aplikasi
hosts: appservers
roles:
- myapp
Ringkasan #
- Selalu cari module bawaan Ansible sebelum menggunakan
commandataushell— jika ada module, gunakan module karena ia idempoten, mendukung check mode, dan mengembalikanchangeddengan benar.- Idempotency bukan opsional — playbook yang tidak idempoten akan menyebabkan drift yang tidak terduga saat dijalankan berulang kali.
- Semua nilai yang mungkin berbeda antar lingkungan atau konfigurasi harus menjadi variabel — tidak ada alasan untuk hardcode IP, path, atau nilai konfigurasi di dalam task.
- Handler adalah mekanisme yang tepat untuk restart service — ia hanya berjalan saat benar-benar ada perubahan dan hanya sekali meski di-notify berkali-kali.
- Gunakan
systemd:state: started bukan kombinasicommand: systemctl is-active+ kondisional — module sudah handle logika ini secara idempoten.- Pecah playbook monolitik ke role — setiap role punya tanggung jawab yang jelas, bisa di-test secara independen, dan bisa digunakan ulang.
← Sebelumnya: Best Practice CI/CD Berikutnya: Role Anti Pattern →