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. Kita perlu memahami bahwa Ansible didesain dengan filosofi deklaratif, di mana kita mendefinisikan state akhir yang diinginkan, bukan langkah-langkah imperatif untuk mencapai state tersebut. Artikel ini membahas anti pattern paling umum dalam penulisan playbook beserta cara memperbaikinya untuk menghasilkan infrastruktur yang andal dan mudah dikelola.

Alur Keputusan Desain Playbook #

Sebelum masuk ke pembahasan detail mengenai masing-masing anti-pattern, mari kita perhatikan diagram keputusan berikut. Diagram ini membantu kita dalam memilih pendekatan yang tepat saat merancang task di dalam playbook agar tetap mengikuti kaidah deklaratif dan idempoten.

flowchart TD
    A["Mulai Mendesain Task"] --> B{"Apakah ada module khusus di Ansible?"}
    B -- "Ya" --> C["Gunakan module bawaan (misal: apt, file, copy)"]
    B -- "Tidak" --> D["Gunakan module command/shell"]
    D --> E{"Apakah task bisa dibuat idempoten?"}
    E -- "Tidak" --> F["Beri flag: changed_when: false / failed_when"]
    E -- "Ya" --> G["Gunakan parameter creates/removes"]
    C --> H{"Apakah task memicu restart service?"}
    H -- "Ya" --> I["Gunakan notify ke handler"]
    H -- "Tidak" --> J["Selesaikan task biasa"]
    F --> K["Selesai"]
    G --> K
    I --> K
    J --> K

1. Terlalu Banyak Menggunakan command atau shell #

Ini adalah salah satu anti-pattern yang paling sering kita jumpai, terutama pada playbook yang ditulis oleh administrator sistem yang baru beralih dari shell scripting. Menggunakan module command atau shell secara berlebihan mengubah Ansible dari sebuah automation engine yang deklaratif menjadi sekadar remote script runner.

Mengapa Ini Berbahaya? #

Saat kita menggunakan command atau shell, Ansible tidak memiliki visibilitas terhadap kondisi sistem yang sebenarnya. Ansible hanya menjalankan perintah mentah di shell target. Hal ini menyebabkan beberapa masalah serius:

  1. Kehilangan Idempotensi: Perintah seperti apt-get install atau wget akan dijalankan setiap kali playbook dieksekusi, bahkan jika paket sudah terpasang atau file sudah terunduh. Hal ini membuang resource jaringan dan CPU.
  2. Tidak Mendukung Check Mode: Saat kita menjalankan playbook dengan opsi --check (dry-run), module command dan shell biasanya akan dilewati atau justru menyebabkan error jika perintah tersebut bergantung pada efek dari task sebelumnya.
  3. Kesulitan Menangani Error: Kita harus menulis regex manual untuk memeriksa stderr atau status code dari perintah tersebut, yang sering kali berbeda-beda antar sistem operasi.

Perbandingan Kode #

# ANTI-PATTERN: menggunakan shell untuk semua operasi
- name: Setup lingkungan web server
  hosts: webservers
  tasks:
    - name: Pasang nginx package
      shell: apt-get update && apt-get install -y nginx

    - name: Salin file konfigurasi utama nginx
      shell: cp /tmp/nginx.conf /etc/nginx/nginx.conf

    - name: Buat direktori logs aplikasi
      shell: mkdir -p /var/log/myapp

    - name: Restart service nginx
      shell: systemctl restart nginx

    - name: Unduh arsip aplikasi web
      shell: wget -O /tmp/app.tar.gz https://example.com/app.tar.gz
# BENAR: gunakan module khusus bawaan Ansible
- name: Setup lingkungan web server secara deklaratif
  hosts: webservers
  tasks:
    - name: Pastikan repositori ter-update dan nginx terpasang
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Deploy file konfigurasi nginx
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        mode: '0644'
      notify: Restart nginx

    - name: Pastikan direktori logs aplikasi telah dibuat
      file:
        path: /var/log/myapp
        state: directory
        mode: '0755'
        owner: www-data
        group: www-data

    - name: Unduh arsip aplikasi web dengan aman
      get_url:
        url: https://example.com/app.tar.gz
        dest: /tmp/app.tar.gz
        mode: '0644'

Dengan beralih ke module khusus, Ansible dapat mengetahui apakah paket nginx sudah terpasang atau belum. Jika sudah, task akan dilewati dengan status ok alih-alih changed, sehingga menghemat waktu eksekusi secara signifikan.


2. Mengabaikan Idempotensi Task #

Idempotensi adalah pilar utama dari konfigurasi manajemen modern. Sebuah playbook dikatakan idempoten jika kita menjalankannya berkali-kali pada server target, hasil akhirnya tetap sama dan tidak menimbulkan efek samping atau perubahan setelah eksekusi pertama. Anti-pattern ini sering kali merusak konsistensi environment server.

Dampak Buruk #

Playbook yang tidak idempoten akan terus-menerus mengubah konfigurasi sistem. Misalnya, menambahkan baris konfigurasi yang sama berulang kali ke dalam satu file. Hal ini dapat merusak aplikasi, membuat file konfigurasi menjadi sangat besar, dan menyulitkan proses audit perubahan sistem. Kita tidak akan pernah tahu apakah sistem berada dalam kondisi yang bersih atau kotor jika setiap kali kita menjalankan playbook, Ansible melaporkan adanya status changed.

Perbandingan Kode #

# ANTI-PATTERN: task yang merusak idempotensi
- name: Konfigurasi profile dan user
  hosts: webservers
  tasks:
    - name: Tambahkan bin path ke profile sistem
      shell: echo "export PATH=$PATH:/opt/myapp/bin" >> /etc/profile
      # Setiap run akan menambah baris baru di akhir file /etc/profile

    - name: Tambahkan user baru ke dalam sistem
      command: useradd -m -s /bin/bash appuser
      # Akan gagal pada run kedua karena user appuser sudah ada

    - name: Ubah permission folder aplikasi
      shell: chmod -R 755 /opt/myapp
      # Selalu melaporkan status 'changed' meski permission sudah benar
# BENAR: gunakan lineinfile, user, dan file module
- name: Konfigurasi profile dan user dengan idempoten
  hosts: webservers
  tasks:
    - name: Pastikan bin path ada di profile sistem
      lineinfile:
        path: /etc/profile
        line: "export PATH=$PATH:/opt/myapp/bin"
        state: present
        insertafter: EOF
      # Hanya menambahkan jika baris tersebut belum ada

    - name: Pastikan user appuser terdaftar di sistem
      user:
        name: appuser
        shell: /bin/bash
        state: present
        create_home: yes
      # Aman dijalankan berkali-kali tanpa error

    - name: Atur permission folder aplikasi secara terstruktur
      file:
        path: /opt/myapp
        state: directory
        mode: '0755'
        recurse: yes
      # Hanya changed jika ada perbedaan permission yang nyata

3. Melakukan Hardcode Nilai di Dalam Task #

Menuliskan nilai-nilai konfigurasi seperti alamat IP, port, nama database, path file, atau credential secara langsung di dalam task playbook merupakan anti-pattern yang sangat fatal. Pola ini membatasi kegunaan playbook hanya pada satu server atau satu environment saja.

Mengapa Kita Harus Menghindarinya? #

Infrastruktur modern biasanya terbagi menjadi beberapa environment seperti Development, Staging, dan Production. Jika nilai-nilai konfigurasi di-hardcode:

  1. Kita harus menyalin dan membuat playbook terpisah untuk setiap environment, yang melanggar prinsip DRY (Don’t Repeat Yourself).
  2. Keamanan data terancam karena credential sensitif terekspos langsung dalam file playbook yang tersimpan di repositori Git.
  3. Manajemen perubahan menjadi sangat lambat karena kita harus melacak dan mengubah setiap file task jika terjadi perubahan port atau IP server database.

Perbandingan Kode #

# ANTI-PATTERN: hardcode konfigurasi database dan app port
- name: Setup server backend
  hosts: backend
  tasks:
    - name: Konfigurasi file database local
      template:
        src: db.conf.j2
        dest: /etc/myapp/db.conf
      vars:
        db_host: "192.168.1.50"
        db_port: 5432
        db_user: "prod_user"
        max_connections: 500

    - name: Jalankan service aplikasi node
      shell: node /opt/myapp/server.js --port 8080
# BENAR: gunakan variabel dinamis dan default configurations
# defaults/main.yml (Disimpan di dalam role atau group_vars/all.yml)
app_port: 8080
app_root_dir: "/opt/myapp"
database_port: 5432
database_max_connections: 100

# group_vars/production.yml (Disimpan di file inventory khusus prod)
database_host: "db-prod.internal.net"
database_user: "prod_admin"
database_max_connections: 500

# tasks/main.yml (Playbook menggunakan template variabel)
- name: Setup server backend dengan parameter variabel
  hosts: backend
  tasks:
    - name: Render file konfigurasi database dari template
      template:
        src: db.conf.j2
        dest: "/etc/myapp/db.conf"
        mode: '0600'
      vars:
        db_host: "{{ database_host }}"
        db_port: "{{ database_port }}"
        db_user: "{{ database_user }}"
        max_connections: "{{ database_max_connections }}"

    - name: Jalankan service aplikasi node secara dinamis
      command: "node {{ app_root_dir }}/server.js --port {{ app_port }}"

Dengan memisahkan variabel ke dalam group_vars atau defaults, kita bisa menggunakan ulang playbook yang sama untuk production maupun staging hanya dengan mengganti file inventory-nya saja.


4. Mengabaikan Handlers dan Melakukan Restart Paksa #

Sering kali kita perlu me-restart layanan seperti Nginx, Apache, atau PostgreSQL setelah ada perubahan file konfigurasi. Pola anti-pattern yang umum adalah me-restart layanan tersebut secara langsung sebagai task biasa setelah file konfigurasi disalin.

Masalah yang Ditimbulkan #

  1. Layanan Selalu Di-restart: Meskipun file konfigurasi tidak mengalami perubahan (misalnya kita menjalankan playbook untuk kedua kalinya), task restart akan tetap berjalan. Hal ini menyebabkan downtime singkat yang tidak perlu pada layanan produksi kita.
  2. Restart Berulang Kali: Jika ada 5 task berbeda yang memperbarui konfigurasi nginx (seperti virtual hosts, SSL certs, mime types), nginx akan di-restart sebanyak 5 kali dalam satu sesi jalan playbook. Ini sangat tidak efisien.

Perbandingan Kode #

# ANTI-PATTERN: restart paksa secara langsung di dalam task
- name: Konfigurasi Nginx server
  hosts: webservers
  tasks:
    - name: Salin file konfigurasi utama
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        mode: '0644'

    - name: Restart service nginx secara paksa
      systemd:
        name: nginx
        state: restarted
      # Selalu berjalan dan me-restart nginx meski file config identik!

    - name: Salin file virtual host app
      copy:
        src: myapp.conf
        dest: /etc/nginx/sites-available/myapp.conf
        mode: '0644'

    - name: Restart nginx kedua kalinya
      systemd:
        name: nginx
        state: restarted
      # Nginx di-restart lagi untuk kedua kalinya dalam satu play!
# BENAR: gunakan notify untuk memicu restart via handler
- name: Konfigurasi Nginx server dengan handler
  hosts: webservers
  tasks:
    - name: Salin file konfigurasi utama
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        mode: '0644'
      notify: Restart nginx

    - name: Salin file virtual host app
      copy:
        src: myapp.conf
        dest: /etc/nginx/sites-available/myapp.conf
        mode: '0644'
      notify: Restart nginx

# handlers/main.yml
  handlers:
    - name: Restart nginx
      systemd:
        name: nginx
        state: restarted

Dengan menggunakan notify, Ansible akan mencatat bahwa nginx perlu di-restart. Handler Restart nginx hanya akan dieksekusi satu kali di akhir play, dan hanya jika salah satu atau kedua task copy di atas menghasilkan status changed. Jika tidak ada perubahan file konfigurasi, nginx tidak akan di-restart sama sekali.


5. Mengabaikan Return Value dan Menggunakan Logika Kondisional Manual #

Beberapa developer cenderung menulis task dengan cara memeriksa status sistem secara manual menggunakan perintah shell, menampung outputnya dengan register, lalu menggunakannya sebagai kondisi when untuk task berikutnya.

Mengapa Ini Kurang Tepat? #

Ansible telah menyediakan modul-modul sistem dengan logika kondisional bawaan yang jauh lebih matang. Menulis logika kondisional manual membuat playbook menjadi sangat panjang, sulit dibaca, dan rentan terhadap perubahan output format pada sistem operasi target.

Perbandingan Kode #

# ANTI-PATTERN: melakukan pengecekan status service manual
- name: Kelola service apache
  hosts: webservers
  tasks:
    - name: Periksa apakah apache sedang berjalan
      shell: systemctl is-active apache2
      register: apache_status
      ignore_errors: true
      changed_when: false

    - name: Jalankan apache jika tidak aktif
      shell: systemctl start apache2
      when: apache_status.rc != 0
# BENAR: gunakan status deklaratif dari systemd module
- name: Kelola service apache secara deklaratif
  hosts: webservers
  tasks:
    - name: Pastikan apache2 berjalan dan aktif saat boot
      systemd:
        name: apache2
        state: started
        enabled: yes

Modul systemd bawaan Ansible secara otomatis akan mendeteksi apakah service apache2 sedang berjalan. Jika tidak berjalan, Ansible akan menjalankannya. Jika sudah berjalan, Ansible tidak akan melakukan apa-apa. Kode kita menjadi jauh lebih bersih dan tidak memerlukan shell command sama sekali.


6. Playbook Monolitik Tanpa Struktur Terorganisasi #

Menumpuk ratusan task dalam satu berkas playbook tunggal (misal site.yml dengan panjang lebih dari 1000 baris) adalah kesalahan pengelolaan playbook yang fatal. Hal ini sering ditemui pada tim yang infrastrukturnya berkembang pesat secara organik tanpa arsitektur kode yang direncanakan.

Masalah yang Dihadapi #

  1. Sulit Menemukan Masalah: Saat terjadi error, melacak posisi task dalam file ribuan baris sangat menyita waktu.
  2. Kolaborasi Tim yang Buruk: Konflik Git merge sering terjadi jika beberapa developer memodifikasi file yang sama secara bersamaan.
  3. Redundansi Kode: Kita tidak bisa menggunakan ulang sebagian task (seperti konfigurasi firewall atau pembuatan user) di playbook lain tanpa menyalin kodenya secara utuh.

Perbandingan Struktur Direktori #

# ANTI-PATTERN: file tunggal raksasa
├── site.yml (berisi 1000+ baris tugas konfigurasi berantakan)
# BENAR: membagi kode ke dalam role dan group vars
├── site.yml
├── group_vars/
│   ├── all.yml
│   ├── webservers.yml
│   └── dbservers.yml
├── roles/
│   ├── common/
│   │   └── tasks/
│   │       └── main.yml
│   ├── nginx/
│   │   ├── tasks/
│   │   │   └── main.yml
│   │   └── handlers/
│   │       └── main.yml
│   └── database/
│       └── tasks/
│           └── main.yml

Implementasi Playbook yang Bersih #

# site.yml
- name: Terapkan konfigurasi dasar ke seluruh server
  hosts: all
  roles:
    - common

- name: Setup server database PostgreSQL
  hosts: dbservers
  roles:
    - database

- name: Setup server frontend dan nginx proxy
  hosts: webservers
  roles:
    - nginx

Dengan memecah playbook menjadi role, kode kita menjadi modular. Setiap role fokus pada satu komponen infrastruktur, sehingga mudah dikembangkan, di-test secara independen, dan didokumentasikan dengan baik.


7. Menggunakan Privilege Escalation (become) Secara Global #

Menyetel parameter become: true di tingkat paling atas playbook (level play) untuk memproses seluruh task di bawahnya merupakan kebiasaan buruk yang membahayakan keamanan server.

Mengapa Ini Berbahaya? #

Privilege escalation memberikan hak akses root penuh pada proses Ansible. Menjalankan setiap task sebagai root — termasuk mengunduh file dari internet, membaca repositori Git, atau melakukan kalkulasi lokal — meningkatkan risiko keamanan jika skrip yang diunduh terkompromi. Selain itu, file-file yang diunduh atau dibuat tanpa sengaja akan dimiliki oleh user root, bukan user aplikasi biasa, yang sering kali menimbulkan isu permission denied saat aplikasi berjalan.

Perbandingan Kode #

# ANTI-PATTERN: privilege escalation di-set secara global untuk semua task
- name: Deploy aplikasi backend
  hosts: appservers
  become: true # Menggunakan root untuk semua task!
  tasks:
    - name: Unduh codebase dari Git
      git:
        repo: 'https://github.com/company/app.git'
        dest: /home/deployer/app
        version: main
      # File hasil clone akan dimiliki oleh root, bukan deployer!

    - name: Install dependencies npm
      npm:
        path: /home/deployer/app
      # Menjalankan instalasi paket npm sebagai root berbahaya!

    - name: Konfigurasi file systemd service
      template:
        src: app.service.j2
        dest: /etc/systemd/system/app.service
      # Task ini memang membutuhkan root
# BENAR: gunakan become secara selektif di level task
- name: Deploy aplikasi backend dengan hak akses terbatas
  hosts: appservers
  tasks:
    - name: Unduh codebase dari Git
      git:
        repo: 'https://github.com/company/app.git'
        dest: /home/deployer/app
        version: main
      become: false
      # Dijalankan sebagai user deployer (tanpa root)

    - name: Install dependencies npm
      npm:
        path: /home/deployer/app
      become: false
      # Paket terinstall dengan owner user deployer

    - name: Konfigurasi file systemd service
      template:
        src: app.service.j2
        dest: /etc/systemd/system/app.service
      become: true
      # Gunakan privilege escalation hanya pada task yang membutuhkan root

8. Looping Task yang Tidak Efisien #

Ketika kita harus menginstal banyak paket atau menyalin banyak file, cara menulis loop sangat memengaruhi kecepatan eksekusi playbook. Menggunakan loop untuk memanggil modul paket sistem seperti apt atau yum berulang kali adalah kesalahan optimasi yang umum.

Efek Terhadap Performa #

Jika kita memiliki daftar 10 paket sistem dan menggunakan loop biasa di Ansible, Ansible akan memanggil perintah paket manager (seperti apt-get install) sebanyak 10 kali secara terpisah. Setiap panggilan membutuhkan waktu untuk mengunci database paket (dpkg lock), memeriksa dependensi, dan melakukan instalasi. Ini bisa memakan waktu hingga beberapa menit.

Perbandingan Kode #

# ANTI-PATTERN: melakukan looping paket satu per satu
- name: Install tools pendukung sistem
  hosts: all
  tasks:
    - name: Install utilitas CLI
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - curl
        - git
        - tmux
        - htop
        - unzip
      # Ansible memanggil apt-get install sebanyak 5 kali secara beruntun!
# BENAR: teruskan list langsung ke parameter name modul paket
- name: Install tools pendukung sistem dengan cepat
  hosts: all
  tasks:
    - name: Install utilitas CLI dalam satu transaksi apt
      apt:
        name:
          - curl
          - git
          - tmux
          - htop
          - unzip
        state: present
      # Ansible memanggil apt-get install satu kali dengan daftar paket lengkap!

Dengan meneruskan list langsung ke parameter name pada modul apt atau yum, modul tersebut akan menggabungkan instalasi dalam satu transaksi tunggal. Hal ini memangkas waktu eksekusi secara dramatis dari menit menjadi hitungan detik.


9. Penanganan Error yang Terlalu Longgar dengan ignore_errors #

Menyematkan flag ignore_errors: true pada task yang sering gagal merupakan pola anti-pattern penanganan error yang malas. Ini menyembunyikan masalah sebenarnya dan dapat menyebabkan kegagalan berantai yang sulit dideteksi di akhir proses deployment.

Bahaya Silent Failure #

Jika suatu task kritis (seperti pembuatan user database atau pengecekan space harddisk) gagal dan kegagalan tersebut diabaikan, task berikutnya yang bergantung pada output task tersebut tetap berjalan. Akibatnya, server kita bisa berada dalam kondisi yang tidak menentu atau setengah terkonfigurasi (partial state), yang sangat sulit didebug.

Perbandingan Kode #

# ANTI-PATTERN: abaikan error secara membabi buta
- name: Setup database skema aplikasi
  hosts: dbservers
  tasks:
    - name: Jalankan migrasi database
      command: /opt/app/bin/migrate-db
      ignore_errors: true
      # Jika migrasi gagal karena koneksi putus, playbook tetap berlanjut!

    - name: Jalankan aplikasi utama
      systemd:
        name: myapp
        state: started
      # Aplikasi berjalan tapi akan crash karena skema database belum siap
# BENAR: gunakan block, rescue, dan always untuk control flow yang aman
- name: Setup database skema aplikasi dengan error handling
  hosts: dbservers
  tasks:
    - name: Eksekusi migrasi dengan proteksi error
      block:
        - name: Jalankan migrasi database
          command: /opt/app/bin/migrate-db

        - name: Jalankan aplikasi utama
          systemd:
            name: myapp
            state: started
      rescue:
        - name: Kirim notifikasi kegagalan ke Slack
          uri:
            url: https://hooks.slack.com/services/T000/B000/XXXX
            method: POST
            body_format: json
            body:
              text: "Gagal melakukan migrasi database pada host {{ inventory_hostname }}!"
          ignore_errors: true

        - name: Gagalkan eksekusi play secara eksplisit
          fail:
            msg: "Playbook dihentikan karena migrasi database gagal. Cek log migrasi!"

Struktur block-rescue memberikan kita kontrol penuh untuk menangani kegagalan. Jika task di dalam blok gagal, Ansible akan menjalankan task di bagian rescue untuk melakukan cleanup atau notifikasi, lalu menghentikan proses play secara rapi tanpa merusak status server target.


10. Desentralisasi Task Konfigurasi File yang Berlebihan #

Mengedit satu berkas konfigurasi dengan memanggil modul lineinfile atau replace berkali-kali di sepanjang playbook merupakan anti-pattern struktural konfigurasi file.

Mengapa Harus Dihindari? #

Menggunakan lineinfile berulang kali untuk file yang sama membuat kita kehilangan gambaran besar struktur file tersebut. Sangat mudah terjadi kesalahan penulisan (typo), urutan baris yang tertukar, atau nilai konfigurasi lama yang tertinggal. Selain itu, performanya lambat karena file harus dibaca dan ditulis berkali-kali.

Perbandingan Kode #

# ANTI-PATTERN: memodifikasi konfigurasi sshd secara berulang
- name: Amankan ssh daemon
  hosts: all
  tasks:
    - name: Matikan login root
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'

    - name: Batasi port ssh
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^Port'
        line: 'Port 2222'

    - name: Matikan otentikasi password
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PasswordAuthentication'
        line: 'PasswordAuthentication no'
# BENAR: gunakan template untuk manajemen file konfigurasi secara utuh
- name: Amankan ssh daemon dengan template terpusat
  hosts: all
  tasks:
    - name: Deploy file konfigurasi sshd dari template j2
      template:
        src: sshd_config.j2
        dest: /etc/ssh/sshd_config
        owner: root
        group: root
        mode: '0600'
        validate: '/usr/sbin/sshd -t -f %s'
      notify: Restart ssh

Dengan menggunakan modul template bersama Jinja2, kita dapat mengelola seluruh konfigurasi dari satu file template secara terpusat. Kita juga bisa memanfaatkan parameter validate untuk memastikan konfigurasi yang kita buat tidak menyebabkan syntax error yang dapat mengunci akses SSH kita ke server.


Ringkasan #

  • Utamakan Module Deklaratif — Selalu gunakan modul bawaan Ansible khusus sebelum memutuskan menggunakan command/shell untuk menjaga keandalan sistem.
  • Terapkan Idempotency — Pastikan setiap task dirancang agar aman dijalankan berulang kali tanpa menghasilkan status changed palsu atau merusak file konfigurasi.
  • Hindari Hardcode — Pindahkan semua parameter konfigurasi dinamis ke defaults/main.yml atau group_vars agar playbook kita modular dan reusable.
  • Manfaatkan Handlers — Gunakan notify untuk me-restart layanan agar proses restart hanya berjalan satu kali di akhir play dan hanya ketika ada perubahan konfigurasi.
  • Batasi Sudo/Become — Terapkan privilege escalation hanya pada task atau role spesifik yang benar-benar membutuhkan hak akses root.
  • Optimalkan Transaksi Loop — Kirim daftar paket langsung ke parameter name modul apt/yum alih-alih melakukan looping task satu per satu untuk mempercepat eksekusi.
  • Implementasikan Block-Rescue — Gunakan blok kontrol error terstruktur untuk mengantisipasi kegagalan task kritis alih-alih mengabaikannya dengan ignore_errors.

← Sebelumnya: Best Practice CI/CD   Berikutnya: Role Anti Pattern →

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