Playbook Anti Pattern

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 command atau shell — jika ada module, gunakan module karena ia idempoten, mendukung check mode, dan mengembalikan changed dengan 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 kombinasi command: 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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact