Role Anti Pattern #
Role adalah unit reusability di Ansible. Tapi role yang ditulis dengan buruk bisa menjadi lebih sulit digunakan daripada tidak ada role sama sekali — ia punya asumsi tersembunyi, coupling dengan role lain, variabel yang wajib diisi tanpa dokumentasi, dan perilaku yang sulit diprediksi. Anti pattern dalam role sering muncul karena role “tumbuh organik” dari playbook yang difaktorisasi terburu-buru, tanpa desain yang jelas tentang batasan tanggung jawab dan kontrak antarmukanya.
Anti Pattern 1: Role yang Melakukan Terlalu Banyak #
# ANTI-PATTERN: role "setup-server" yang melakukan segalanya
roles/setup-server/
└── tasks/
└── main.yml ← Install OS packages, konfigurasi nginx, setup database,
deploy aplikasi, konfigurasi monitoring, setup backup...
(500+ baris)
# BENAR: satu role, satu tanggung jawab
roles/
├── common/ ← Konfigurasi OS dasar (user, timezone, packages)
├── nginx/ ← Instalasi dan konfigurasi nginx saja
├── postgresql/ ← Instalasi dan konfigurasi PostgreSQL saja
├── myapp/ ← Deploy aplikasi saja
└── monitoring/ ← Setup monitoring agent saja
# Playbook mengkomposisikan role:
- hosts: webservers
roles:
- common
- nginx
- myapp
- monitoring
Role yang terlalu besar tidak bisa di-test secara terisolasi, sulit dipahami, dan tidak bisa digunakan ulang di konteks yang berbeda.
Anti Pattern 2: Variabel Wajib Tanpa Dokumentasi #
# ANTI-PATTERN: role yang akan gagal tanpa dokumentasi yang jelas
# roles/myapp/tasks/main.yml
- name: Deploy konfigurasi
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
# Template ini menggunakan db_host, db_password, api_key
# Tapi tidak ada dokumentasi bahwa variabel ini wajib!
# Pengguna role hanya tahu setelah menjalankan dan mendapat error template
# BENAR: dokumentasikan dan validasi variabel wajib
# roles/myapp/meta/argument_specs.yml (Ansible >= 2.11)
argument_specs:
main:
short_description: Deploy dan konfigurasi myapp
options:
db_host:
type: str
required: true
description: Hostname atau IP database server
db_password:
type: str
required: true
description: Password database — gunakan Ansible Vault
no_log: true
api_key:
type: str
required: true
description: API key untuk autentikasi layanan eksternal
no_log: true
app_port:
type: int
required: false
default: 8080
description: Port yang digunakan aplikasi
# Alternatif untuk Ansible < 2.11: validasi manual di tasks
# roles/myapp/tasks/validate.yml
- name: Validasi variabel wajib
assert:
that:
- db_host is defined and db_host | length > 0
- db_password is defined
- api_key is defined
fail_msg: >
Role myapp membutuhkan variabel: db_host, db_password, api_key.
Lihat README role untuk dokumentasi lengkap.
Anti Pattern 3: Menggunakan Variables dari Role Lain Secara Langsung #
# ANTI-PATTERN: role myapp menggunakan variabel internal dari role postgresql
# roles/myapp/tasks/main.yml
- name: Tunggu database siap
wait_for:
host: "{{ postgresql_listen_addresses }}" # Variabel internal role postgresql!
port: "{{ postgresql_port }}" # Coupling tersembunyi!
timeout: 30
# BENAR: role mendefinisikan variabelnya sendiri
# roles/myapp/defaults/main.yml
db_host: localhost # Default yang masuk akal
db_port: 5432
# roles/myapp/tasks/main.yml
- name: Tunggu database siap
wait_for:
host: "{{ db_host }}" # Variabel role myapp sendiri
port: "{{ db_port }}"
timeout: 30
# Pengguna yang menggunakan keduanya bisa menyinkronkan:
# group_vars/dbservers.yml
db_host: "{{ ansible_default_ipv4.address }}"
db_port: "{{ postgresql_port }}" # Mapping eksplisit, bukan coupling tersembunyi
Anti Pattern 4: defaults/main.yml Kosong atau Tidak Ada #
# ANTI-PATTERN: role tanpa defaults
# Tidak ada roles/myapp/defaults/main.yml
# Semua variabel HARUS diset oleh pengguna role,
# tidak ada nilai default yang masuk akal
# roles/myapp/tasks/main.yml
- name: Deploy dengan variabel yang tidak punya default
template:
src: app.conf.j2
dest: "{{ app_conf_dir }}/app.conf" # Gagal jika app_conf_dir tidak diset
# BENAR: defaults yang komprehensif dengan nilai yang masuk akal
# roles/myapp/defaults/main.yml
app_user: myapp
app_group: myapp
app_dir: /opt/myapp
app_conf_dir: /etc/myapp
app_log_dir: /var/log/myapp
app_port: 8080
app_workers: "{{ ansible_processor_vcpus }}"
app_debug: false
app_log_level: info
# Nilai yang WAJIB diisi (tidak ada default yang masuk akal)
# Dokumentasikan dengan eksplisit bahwa tidak ada default:
# db_host: "" # WAJIB — tidak ada default
# db_password: "" # WAJIB — tidak ada default, gunakan vault
Anti Pattern 5: Task yang Seharusnya Jadi Handler #
# ANTI-PATTERN: restart service di setiap task yang mengubah konfigurasi
- name: Update nginx.conf
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: Restart nginx karena nginx.conf berubah
systemd:
name: nginx
state: restarted
- name: Update virtual host config
template:
src: vhost.conf.j2
dest: /etc/nginx/conf.d/app.conf
- name: Restart nginx lagi karena vhost berubah
systemd:
name: nginx
state: restarted
# nginx di-restart 2 kali meski tidak diperlukan!
# BENAR: gunakan handler
# roles/nginx/tasks/main.yml
- name: Update nginx.conf
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
- name: Update virtual host config
template:
src: vhost.conf.j2
dest: /etc/nginx/conf.d/app.conf
notify: Reload nginx
# roles/nginx/handlers/main.yml
handlers:
- name: Reload nginx
systemd:
name: nginx
state: reloaded # reload lebih baik dari restart untuk nginx
Anti Pattern 6: Role Tidak Bisa Dijalankan Ulang Tanpa Side Effect #
# ANTI-PATTERN: role yang membuat perubahan permanen pada setiap run
- name: Tambahkan entri ke /etc/hosts
shell: echo "10.0.1.5 db-primary.internal" >> /etc/hosts
# Menambahkan duplikat setiap kali role dijalankan!
- name: Generate SSH key pair
command: ssh-keygen -t ed25519 -f /home/appuser/.ssh/id_ed25519 -N ""
# Menimpa key yang sudah ada setiap kali role dijalankan!
# BENAR: idempoten secara natural
- name: Tambahkan entri ke /etc/hosts
lineinfile:
path: /etc/hosts
line: "10.0.1.5 db-primary.internal"
state: present
# Tidak menambahkan duplikat
- name: Generate SSH key pair jika belum ada
command: ssh-keygen -t ed25519 -f /home/appuser/.ssh/id_ed25519 -N ""
args:
creates: /home/appuser/.ssh/id_ed25519 # Hanya jika file belum ada
Ringkasan #
- Satu role, satu tanggung jawab — role yang melakukan terlalu banyak tidak bisa di-test terisolasi dan tidak bisa digunakan ulang secara bermakna.
- Dokumentasikan semua variabel menggunakan
meta/argument_specs.yml(Ansible ≥ 2.11) atau validasi eksplisit denganassert— pengguna tidak boleh menebak variabel apa yang dibutuhkan.- Jangan gunakan variabel internal role lain secara langsung — ini menciptakan coupling tersembunyi yang rapuh. Definisikan variabel interface role sendiri.
defaults/main.ymlharus komprehensif — berikan nilai default yang masuk akal untuk semua parameter yang bisa punya default; dokumentasikan yang wajib.- Handler untuk restart/reload service, bukan task langsung — handler hanya berjalan jika ada perubahan nyata dan hanya sekali meski di-trigger berkali-kali.
- Role harus idempoten — bisa dijalankan berulang kali tanpa efek samping yang tidak diinginkan. Gunakan
args: creates:,lineinfile, danstate: presentsecara konsisten.
← Sebelumnya: Playbook Anti Pattern Berikutnya: Variable Anti Pattern →