Condition & Loop #
Otomatisasi infrastruktur yang cerdas tidak boleh berjalan secara kaku dan linear. Di lingkungan produksi yang nyata, kita akan selalu menghadapi variasi kondisi server target. Kita mungkin perlu menginstal paket perangkat lunak tertentu hanya di sistem operasi CentOS, melewati (skip) tugas pembuatan database jika layanan database belum terinstal, atau mengulang instalasi daftar pustaka yang panjang pada server web tanpa menuliskan baris kode yang sama berulang kali.
Untuk menangani dinamika ini, Ansible menyediakan dua fitur kontrol alur yang sangat penting: kondisional (when) dan iterasi (loop). Memahami cara menggabungkan evaluasi logika kondisional Jinja2 dengan kontrol perulangan tingkat lanjut akan memberikan kita fleksibilitas penuh untuk merancang playbook yang adaptif, aman, dan efisien. Artikel ini akan membedah sintaksis kondisional, menjejalkan parameter iterasi modern, serta mengupas teknik optimasi output perulangan.
Logika Kondisional dengan when
#
Kata kunci when digunakan untuk menentukan apakah suatu task akan dijalankan atau dilewati oleh Ansible untuk host tertentu. Kondisi yang ditulis di dalam when dievaluasi sebagai ekspresi Jinja2 yang mengembalikan nilai boolean (true atau false).
Aturan Emas: Bebas Kurung Kurawal Ganda (No Curly Braces) #
Salah satu kesalahan paling umum yang dilakukan oleh pemula (dan bahkan developer berpengalaman) adalah menuliskan kurung kurawal ganda {{ }} di dalam baris pernyataan when.
Kita harus mengingat aturan dasar ini: pernyataan when sudah berada di dalam lingkungan evaluasi Jinja2 secara implisit. Menambahkan {{ }} di dalam when adalah sebuah anti-pattern yang akan memicu kesalahan pembacaan (syntax parser error) atau perilaku evaluasi variabel yang tidak terduga pada versi Ansible modern.
Mari kita bandingkan contoh penulisan yang salah dan benar di bawah ini:
# ANTI-PATTERN: Menuliskan kurung kurawal ganda di dalam when
- name: Menginstal paket utilitas (Cara Salah)
apt:
name: curl
state: present
when: "{{ ansible_os_family == 'Debian' }}"
# ✗ Ansible akan mengevaluasi kurung kurawal secara ganda,
# yang dapat memicu error parser pada versi Ansible 2.10 ke atas.
# BENAR: Menuliskan ekspresi kondisional secara bersih tanpa kurung kurawal
- name: Menginstal paket utilitas (Cara Benar)
apt:
name: curl
state: present
when: ansible_os_family == 'Debian'
# ✓ Variabel dibaca secara langsung karena modul when mengevaluasi string sebagai ekspresi Jinja2 native.
Evaluasi Kondisi Berbasis Facts Populer #
Dalam merancang ekspresi kondisional, kita sering kali mengandalkan data karakteristik sistem yang dikumpulkan secara otomatis oleh Ansible pada awal play melalui proses gathering facts.
Berikut adalah tabel referensi variabel facts populer yang sering kita jadikan parameter kondisi di lingkungan produksi:
| Nama Variabel Facts | Jenis Data | Contoh Nilai | Deskripsi Penggunaan |
|---|---|---|---|
ansible_os_family |
String | Debian, RedHat, Suse |
Mengelompokkan tugas berdasarkan keluarga distribusi Linux utama. |
ansible_distribution |
String | Ubuntu, CentOS, Rocky |
Menentukan aksi spesifik untuk distribusi tertentu. |
ansible_distribution_major_version |
String | 20, 22, 8, 9 |
Memeriksa versi rilis utama (misalnya untuk konfigurasi khusus RHEL 9). |
ansible_memtotal_mb |
Integer | 2048, 16384 |
Mengatur parameter alokasi memori aplikasi berdasarkan RAM fisik server. |
ansible_processor_vcpus |
Integer | 2, 4, 16 |
Menentukan jumlah worker process (seperti worker_processes di Nginx). |
ansible_virtualization_type |
String | kvm, docker, virtualbox |
Menghindari instalasi modul kernel tertentu jika berada di dalam container. |
ansible_architecture |
String | x86_64, aarch64 |
Memilih arsitektur paket biner aplikasi yang tepat (seperti ARM vs Intel). |
Berikut adalah contoh implementasi kondisional memanfaatkan tabel facts di atas untuk mengoptimalkan konfigurasi kernel dan paket:
# Mengatur alokasi memori swap hanya untuk server dengan spesifikasi rendah
- name: Membuat file swap tambahan
command: /usr/local/bin/create_swap.sh
when:
- ansible_memtotal_mb < 4096
- ansible_virtualization_type != 'docker'
# ✓ Swap tidak akan dibuat jika RAM >= 4GB atau jika server berjalan di dalam container Docker.
Menulis Kondisi Kompleks: AND, OR, dan NOT #
Ansible mendukung penggunaan operator logika standar untuk menggabungkan beberapa kriteria evaluasi kondisional.
1. Logika AND (Semua Kondisi Harus Terpenuhi) #
Ada dua cara menuliskan logika AND di dalam when. Kita bisa menggunakan operator and eksplisit dalam satu baris, atau menuliskannya sebagai format daftar (list) YAML. Format daftar YAML sangat direkomendasikan karena membuat baris kode kita jauh lebih rapi dan mudah dibaca.
# Format List YAML untuk Logika AND (Sangat Direkomendasikan)
- name: Mengonfigurasi modul kernel khusus virtualisasi Intel
modprobe:
name: kvm_intel
state: present
when:
- ansible_os_family == 'RedHat'
- ansible_architecture == 'x86_64'
- ansible_virtualization_type == 'kvm'
# ✓ Semua tiga kondisi di atas wajib bernilai true agar task ini dijalankan.
2. Logika OR (Salah Satu Kondisi Terpenuhi) #
Untuk menggunakan logika OR, kita menuliskan operator or di dalam baris ekspresi kondisional.
# Menggunakan operator OR
- name: Memasang utilitas jaringan tambahan
apt:
name: net-tools
state: present
when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
# ✓ Task akan berjalan jika distribusi adalah Ubuntu, Debian, atau keduanya.
3. Logika NOT (Negasi/Kebalikan) #
Kita bisa menggunakan operator not untuk membalikkan hasil evaluasi boolean.
# Menggunakan operator NOT
- name: Menghapus file sementara untuk server non-production
file:
path: /tmp/debug_logs.txt
state: absent
when: not (env == 'production')
# ✓ File akan dihapus hanya jika variabel env TIDAK bernilai 'production'.
4. Menulis Kondisi Multi-baris yang Kompleks #
Jika kita harus menggabungkan operator AND dan OR dengan pengelompokan tanda kurung yang panjang, gunakan indikator blok scalar YAML when: > agar kode tetap rapi tanpa scroll horizontal:
# Blok scalar untuk kondisi multi-baris
- name: Menerapkan patch keamanan kritis
apt:
name: security-patch
state: latest
when: >
(ansible_distribution == 'Ubuntu' and ansible_distribution_version == '22.04')
or
(ansible_os_family == 'RedHat' and ansible_distribution_major_version | int >= 8)
Iterasi Modern: loop vs with_items (Legacy)
#
Sebelum versi Ansible 2.5 dirilis, developer menggunakan berbagai varian kata kunci berawalan with_ (seperti with_items, with_dict, with_subelements) untuk melakukan perulangan. Sejak versi 2.5 ke atas, Ansible menyatukan seluruh fungsi perulangan tersebut ke dalam satu kata kunci modern: loop.
Meskipun with_items masih didukung demi kompatibilitas ke belakang (backward compatibility), menggunakan with_items pada proyek baru dikategorikan sebagai tindakan yang kurang tepat (legacy/deprecated).
Mengapa Memilih loop?
#
- Sederhana:
loophanya menerima list datar (flat list) satu dimensi secara langsung. Hal ini membuat perilakunya sangat mudah diprediksi. - Deklaratif: Kita dapat menggabungkan
loopdengan berbagai filter Jinja2 (sepertiflatten,dict2items, atausubelements) untuk memanipulasi struktur data sebelum diiterasi. Hal ini memisahkan logika perulangan dengan pemrosesan data secara bersih.
Mari kita lihat perbandingan penulisan kodenya:
# LEGACY (with_items): Gaya lama yang tidak direkomendasikan
- name: Membuat beberapa direktori proyek (Gaya Lama)
file:
path: "{{ item }}"
state: directory
with_items:
- /var/www/app1
- /var/www/app2
# MODERN (loop): Gaya baru yang sangat direkomendasikan
- name: Membuat beberapa direktori proyek (Gaya Modern)
file:
path: "{{ item }}"
state: directory
loop:
- /var/www/app1
- /var/www/app2
Mengontrol Output Iterasi dengan loop_control
#
Ketika kita melakukan perulangan pada daftar yang berisi struktur data kompleks seperti dictionary (kamus data), visualisasi keluaran pada layar terminal (stdout) akan menampilkan seluruh isi dictionary tersebut untuk setiap iterasi. Ini akan membuat log eksekusi playbook kita menjadi sangat panjang, kotor, dan sulit dibaca.
Ansible menyediakan parameter loop_control untuk mengendalikan perilaku visual dan fungsional dari proses iterasi kita.
1. Merapikan Output dengan label
#
Gunakan opsi label untuk menentukan informasi ringkas apa saja dari properti dictionary yang ingin kita tampilkan di stdout terminal.
# Menggunakan loop_control untuk menyaring visualisasi log terminal
- name: Membuat akun pengguna sistem dengan parameter lengkap
user:
name: "{{ item.username }}"
uid: "{{ item.uid }}"
shell: "{{ item.shell }}"
state: present
loop:
- { username: 'john', uid: 2001, shell: '/bin/bash', comment: 'Lead Developer' }
- { username: 'jane', uid: 2002, shell: '/bin/zsh', comment: 'SecOps Engineer' }
loop_control:
label: "{{ item.username }} (UID: {{ item.uid }})"
# ✓ Output terminal hanya akan menampilkan: item=john (UID: 2001) dan item=jane (UID: 2002).
# Detail properti sensitif atau panjang lainnya tidak akan mengotori layar.
2. Melacak Indeks Perulangan dengan index_var
#
Terkadang, di dalam iterasi kita membutuhkan nomor urut index (dimulai dari 0). Kita bisa mendaftarkan variabel penampung index menggunakan opsi index_var.
# Melacak index baris untuk penomoran file
- name: Menyalin konfigurasi dengan penomoran unik
template:
src: virtualhost.conf.j2
dest: "/etc/nginx/sites-enabled/{{ idx + 1 }}-{{ item }}.conf"
loop:
- frontend
- backend
- api
loop_control:
index_var: idx
# ✓ File yang terbentuk akan memiliki prefix angka urut: 1-frontend.conf, 2-backend.conf, dst.
3. Menghindari Tabrakan Namespace dengan loop_var (Nested Loop)
#
Ketika kita menulis perulangan bersarang (nested loops) menggunakan include_tasks, variabel bawaan item pada perulangan luar akan tertimpa oleh variabel item pada perulangan dalam. Hal ini akan menyebabkan error pembacaan variabel.
Untuk mencegah bentrokan ini, kita wajib mendefinisikan ulang nama variabel iterasi menggunakan opsi loop_var.
# ==============================================================================
# BERKAS: main.yml (Playbook Utama)
# ==============================================================================
- name: Melakukan konfigurasi virtual host per port
include_tasks: configure_ports.yml
loop:
- { domain: 'app.example.com', directory: '/var/www/app' }
- { domain: 'api.example.com', directory: '/var/www/api' }
loop_control:
loop_var: site_item
# ✓ Kita mengganti nama variabel default 'item' menjadi 'site_item' untuk loop tingkat pertama.
# ==============================================================================
# BERKAS: configure_ports.yml (Task yang disertakan)
# ==============================================================================
- name: Mengaktifkan port listen untuk domain {{ site_item.domain }}
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 80
- 443
# ✓ Di sini kita aman menggunakan variabel 'item' bawaan untuk loop dalam
# tanpa takut mengganggu variabel 'site_item' milik loop luar.
Kombinasi: Evaluasi when di dalam loop
#
Satu skenario yang sangat sering kita gunakan adalah menggabungkan pernyataan when dengan pernyataan loop. Ketika kedua kata kunci ini ditempelkan pada satu task yang sama, penting untuk memahami alur evaluasi internal Ansible.
[!IMPORTANT] Ansible mengevaluasi kondisi
whenuntuk setiap item di dalam loop secara individual (satu per satu), bukan mengevaluasi loop secara keseluruhan. Ini berarti perulangan tetap berjalan untuk seluruh item, tetapi task hanya akan memproses item yang memenuhi kriteria di dalamwhen.
Berikut adalah visualisasi alur pengambilan keputusan evaluasi per-item di dalam perulangan kondisional:
flowchart TD
Start(["Mulai Iterasi Loop"]) --> GetItem["Ambil Item X dari Daftar Loop"]
GetItem --> EvalCondition{"Evaluasi Kondisi 'when' terhadap Item X"}
EvalCondition -- "True" --> ExecTask["Jalankan Aksi Modul untuk Item X"]
ExecTask --> CheckNext{"Ada Item Berikutnya?"}
EvalCondition -- "False" --> SkipItem["Lewati (Skip) Item X"]
SkipItem --> CheckNext
CheckNext -- "Ya" --> GetItem
CheckNext -- "Tidak" --> EndLoop(["Selesai Iterasi Loop"])
Mari kita lihat contoh implementasinya di bawah ini. Kita ingin membuat direktori proyek, tetapi hanya direktori yang memiliki bendera properti active: true saja yang akan dibuat.
# Penerapan kondisional per-item di dalam loop
- name: Membuat direktori proyek yang aktif
file:
path: "{{ item.path }}"
state: directory
owner: deployer
group: deployer
mode: '0755'
loop:
- { path: '/var/www/prod_site', active: true }
- { path: '/var/www/staging_site', active: false }
- { path: '/var/www/dev_site', active: true }
when: item.active
# ✓ Hasil eksekusi: direktori prod_site dan dev_site akan dibuat,
# sedangkan staging_site akan dilewati dengan status 'skipped'.
Ringkasan #
- Jinja2 Tanpa Kurung Kurawal: Hindari penulisan
{{ }}di dalam opsiwhen. Pernyataan kondisional Ansible mengevaluasi variabel secara langsung.- Variabel Facts: Manfaatkan tabel facts sistem seperti
ansible_os_familyatauansible_memtotal_mbuntuk menulis kondisi adaptif berdasarkan spesifikasi server.- Logika Bersih: Gunakan format daftar YAML untuk logika AND yang kompleks agar kode playbook kita mudah dibaca secara vertikal.
- Standarisasi Loop: Gunakan kata kunci modern
loopalih-alihwith_itemslama demi menjaga kompatibilitas masa depan dan kerapian sintaksis.- Label Output: Gunakan
loop_control.labeluntuk membatasi panjang log eksekusi terminal saat melakukan perulangan pada list dictionary.- Looping Bersarang: Gunakan opsi
loop_control.loop_varpada nested loop untuk menghindari tabrakan namespace variabelitem.- Evaluasi Per Item: Pemasangan
whendi dalamloopmemicu evaluasi kondisi pada setiap item satu per satu, bukan menyaring atau menghentikan seluruh perulangan.
← Sebelumnya: Notify & Listen Berikutnya: Scope & Precedence →