Category: Ansible

  • Mengenal Otomatisasi Homelab Proxmox dengan Terraform dan Ansible

    Mengenal Otomatisasi Homelab Proxmox dengan Terraform dan Ansible

    Membuat virtual machine di Proxmox melalui web interface cukup mudah. Namun, jika VM yang dibuat semakin banyak, proses mengatur CPU, RAM, disk, jaringan, dan sistem operasi secara manual akan terasa berulang.

    Artikel ini membahas cara mengotomatisasi homelab Proxmox menggunakan Terraform, Cloud-init, dan Ansible.

    Gambaran Homelab

    Dalam skenario ini, Terraform digunakan untuk membuat tiga virtual machine Ubuntu di Proxmox.

    Setelah VM tersedia, Cloud-init memasukkan SSH key. Selanjutnya, Ansible terhubung ke masing-masing VM untuk menginstal Docker dan menjalankan container.

    Alur Otomatisasi

    File konfigurasi
           │
           ▼
    Terraform
    Membuat 3 VM Ubuntu
           │
           ▼
    Cloud-init
    Memasukkan SSH key
           │
           ▼
    Ansible
    Menginstal Docker
           │
           ▼
    Container berjalan
    

    Fungsi Setiap Komponen

    KomponenFungsi
    ProxmoxMenjalankan virtual machine
    TerraformMembuat dan mengelola VM
    Cloud-initMenyiapkan konfigurasi awal dan SSH key
    AnsibleMengatur server setelah VM tersedia
    DockerMenjalankan aplikasi dalam container

    Peran Terraform

    Terraform digunakan untuk menyediakan infrastruktur di Proxmox.

    Konfigurasi VM seperti nama, CPU, RAM, disk, dan jaringan dituliskan dalam file Terraform. Dengan demikian, VM tidak perlu dibuat satu per satu melalui web interface.

    Terraform juga menyimpan state untuk mencatat infrastruktur yang sudah dibuat dan perubahan yang perlu diterapkan.

    Peran Cloud-init

    Cloud-init menjalankan konfigurasi awal ketika VM pertama kali dibuat.

    Dalam artikel ini, Cloud-init digunakan untuk memasukkan SSH key ke dalam VM Ubuntu. SSH key tersebut memungkinkan Ansible masuk ke server tanpa menggunakan password secara manual.

    Peran Ansible

    Setelah VM aktif dan dapat diakses melalui SSH, Ansible menjalankan konfigurasi di dalam server.

    Tugas Ansible dalam skenario ini meliputi:

    • Menghubungkan ke VM melalui SSH.
    • Menginstal Docker.
    • Menyiapkan kebutuhan server.
    • Menjalankan container.

    Terraform dan Ansible

    Terraform dan Ansible memiliki tugas yang berbeda.

    TerraformAnsible
    Membuat infrastrukturMengonfigurasi server
    Membuat virtual machineMenginstal aplikasi
    Mengelola resource ProxmoxMenjalankan Docker dan container
    Menggunakan stateMenggunakan inventory dan playbook

    Secara sederhana:

    Terraform membuat server, sedangkan Ansible mengatur isi server.

    Keuntungan Pendekatan Ini

    Dengan menyimpan konfigurasi sebagai kode, homelab menjadi:

    • Lebih konsisten.
    • Mudah dibuat ulang.
    • Tidak bergantung pada langkah manual.
    • Dapat disimpan dalam version control.
    • Lebih mudah dikelola ketika jumlah VM bertambah.

    Rencana Percobaan di Homelab

    Untuk mempraktikkan skenario ini, saya akan menggunakan satu VM sebagai controller dan tiga VM Ubuntu sebagai target. Seluruh server berada pada network 192.168.14.0/24.

    Rancangan IP Address

    HostnameFungsiIP Address
    automation-controllerMenjalankan Terraform dan Ansible192.168.14.80
    ubuntu-target01VM target pertama192.168.14.81
    ubuntu-target02VM target kedua192.168.14.82
    ubuntu-target03VM target ketiga192.168.14.83

    Konfigurasi jaringan yang digunakan:

    KonfigurasiNilai
    Network192.168.14.0/24
    Gateway192.168.14.31
    Proxmox bridgevmbr0
    Metode IPStatic melalui Cloud-init

    Topologi Homelab

                        Network 192.168.14.0/24
                                  │
                        Proxmox VE / vmbr0
                                  │
              ┌───────────────────┴───────────────────┐
              │                                       │
    automation-controller                     VM Target Ubuntu
       192.168.14.80                    ┌────────────────────────┐
       Terraform                        │ target01 192.168.14.81 │
       Ansible                          │ target02 192.168.14.82 │
                                       │ target03 192.168.14.83 │
                                       └────────────────────────┘
    

    Alur Percobaan

    1. Menyiapkan VM automation-controller.
    2. Menginstal Terraform dan Ansible pada controller.
    3. Menyiapkan template Ubuntu Cloud-init di Proxmox.
    4. Menghubungkan Terraform dengan Proxmox.
    5. Menjalankan Terraform untuk membuat tiga VM target.
    6. Cloud-init mengatur hostname, IP address, user, dan SSH key.
    7. Ansible mengakses seluruh VM target melalui SSH.
    8. Ansible menginstal Docker dan menjalankan container.
    9. Memastikan seluruh VM dan container berjalan dengan baik.

    Pembagian Tugas

    TahapTools
    Membuat VM di ProxmoxTerraform
    Mengatur IP dan SSH keyCloud-init
    Menginstal DockerAnsible
    Menjalankan aplikasiDocker container

    Hasil yang Diharapkan

    Setelah percobaan selesai, tiga VM Ubuntu dapat dibuat secara otomatis tanpa konfigurasi manual melalui web interface Proxmox.

    Seluruh VM memiliki IP statis, dapat diakses melalui SSH dari controller, dan sudah menjalankan Docker serta container yang ditentukan melalui Ansible.

    Kesimpulan

    Artikel ini menunjukkan cara menggabungkan Proxmox, Terraform, Cloud-init, dan Ansible untuk membangun homelab otomatis.

    Terraform membuat tiga VM Ubuntu, Cloud-init menyiapkan SSH key, kemudian Ansible menginstal Docker dan menjalankan container.

    Dengan pembagian tersebut, proses pembangunan homelab menjadi lebih terstruktur, dapat diulang, dan lebih mudah dikelola.

    Referensi

    Proxmox Homelab Automation with Ansible & Terraform
    https://proxmox.mrplanb.com/proxmox-ansible-terraform-homelab

  • Upgrade Carbonio 26.3 ke 26.6.0 via Ansible

    Upgrade Carbonio 26.3 ke 26.6.0 via Ansible

    Hari ini Carbonio rilis versi 26.6.0, jadi langsung saya coba upgrade di server single-server saya pakai metode resmi berbasis Ansible. Prosesnya sebenarnya cukup mulus, cuma di akhir saya sempat kena masalah carbonio-tasks yang tidak jalan. Berikut catatan lengkapnya, dari kondisi awal sampai troubleshoot.

    Cek Versi Awal

    Sebelum mulai, saya cek dulu versi yang terpasang. Kebiasaan yang selalu saya lakukan, biar tahu titik awalnya dan bisa dibandingkan setelah upgrade.

    root@mail:~# su - zextras -c "zmcontrol -v"
    Carbonio Release 26.3.2
    Advanced module version:
    
    CLI versions:
    
    zextras_version       26.3.1
    zextras_commit 97dbab6a7491843684b80e95ecb2fb0d1d2442b0
    zal_version           26.3.1
    zal_commit     6db124701ef6e49a44951a025c432fc4f4f2bd9d
    
    Server versions:
    
    version               26.3.1
    commit         97dbab6a7491843684b80e95ecb2fb0d1d2442b0
    system_type           carbonio
    product               Carbonio
    zal_version           26.3.1
    zal_commit     6db124701ef6e49a44951a025c432fc4f4f2bd9d
    

    Posisi saya di 26.3.x dan target hari ini 26.6.0.

    Install atau Update Collection carbonio_upgrade

    Metode upgrade via Ansible dijalankan dari Control Node, bukan dari server Carbonio-nya. Pertama, pastikan collection upgrade selalu versi terbaru. Flag -U artinya install kalau belum ada, atau update ke versi terbaru kalau sudah ada.

    root@ansible:~# ansible-galaxy collection install \ zxbot.carbonio_upgrade -U

    Cek Inventory

    Sebelum jalan, saya cek dulu file inventory. Ini file yang sama yang dipakai waktu instalasi awal, isinya daftar node dan group sesuai infrastruktur. Untuk single-server semuanya menunjuk ke satu host yang sama.

    Sebelum menjalankan playbook, ada beberapa hal yang wajib kalian sadari. Ini bukan sekadar formalitas, saya sudah baca di dokumentasi resminya dan memang perlu diperhatikan:

    HalKenapa penting
    Playbook menghapus dependency yang tidak terpakaiTambahkan --extra-vars skip_autoremove=1 kalau tidak mau ada paket yang otomatis dibuang
    Playbook update SEMUA paket dari repo yang aktifBukan cuma paket Carbonio. Nonaktifkan atau komentari repo yang tidak ingin diikutkan
    Bisa di-run ulangKalau proses gagal di tengah jalan, jalankan lagi. Task yang sudah sukses akan di-skip
    Legacy Chats tidak disentuhKalau masih pakai Chats lama, tidak akan dihapus, dijadikan read-only, atau dimigrasi otomatis

    Poin soal “update semua paket dari repo aktif” ini yang menurut saya paling penting. Kalau ada repo pihak ketiga aktif, pastikan aman diikutkan, atau matikan dulu.

    Jalankan Upgrade

    Masuk ke direktori yang berisi inventory, lalu jalankan playbook upgrade.

    root@ansible:~# cd single-server-deploy
    root@ansible:~/single-server-deploy# ansible-playbook \ zxbot.carbonio_upgrade.carbonio_upgrade -u root -i \ inventory.yaml

    Setelah itu tinggal ditunggu. Prosesnya cukup lama karena mengunduh dan memasang banyak paket, jalankan bootstrap database, lalu register ulang ke Carbonio Mesh. Sabar saja, jangan diinterupsi di tengah jalan.

    Hasil

    Setelah menunggu cukup lama, hasilnya sesuai harapan:

    zextras@mail:~$ zmcontrol -v
    Carbonio Release 26.6.0

    Upgrade dari 26.3.x ke 26.6.0 berhasil.

    Penutup

    Upgrade via Ansible ini menurut saya cara paling nyaman untuk Carbonio, karena bootstrap database dan register Mesh sudah otomatis di dalam playbook. Yang perlu diwaspadai justru dua hal: perilaku autoremove dan update semua paket dari repo aktif.

    Catatan: perintah bootstrap dan sidecar di atas berbasis dokumentasi resmi dan forum Zextras. Kalau nama service atau path di versi kalian berbeda, selalu konfirmasi ke dokumentasi resmi versi yang kalian pakai sebelum dijalankan.

    Referensi

    • Upgrade Using Ansible (Carbonio Admin Guide): https://docs.zextras.com/carbonio/html/upgrade/ansible.html
    • Advanced Administration Tasks / pending-setups (Carbonio Admin Guide): https://docs.zextras.com/carbonio/html/admincli/advancedadmin.html
    • Tasks component (Carbonio CE Admin Docs): https://docs.zextras.com/carbonio-ce/html/architecture/components/component-tasks.html
  • Merancang Instalasi Otomatis Zimbra dengan Ansible

    Merancang Instalasi Otomatis Zimbra dengan Ansible

    Hari ini saya memulai untuk membangun project saya, yaitu project instalasi zimbra secara otomatis, dan disini saya menggunakan Ansible sebagai mesin otomasi nya. Instalasi Zimbra secara manual itu melelahkan. Kalian harus SSH ke server, menyiapkan hostname dan DNS, menjalankan install.sh, lalu menjawab puluhan pertanyaan interaktif di zmsetup.pl satu per satu. Ulangi proses ini untuk setiap server, dan satu salah ketik bisa membuat Anda mengulang dari awal.

    Di artikel ini saya akan menceritakan rancangan saya, bagaimana saya membangun sebuah dashboard yang menginstalasi Zimbra secara otomatis ke server target, cukup dengan mengisi parameter wajib lewat form. Mesin di belakangnya adalah Ansible. Dashboard mendukung dua edisi: Zimbra OSE (Open Source Edition) dan Zimbra NE (Network Edition).

    Kenapa Ansible?

    Ansible cocok untuk kasus ini karena beberapa alasan:

    • Agentless. Tidak perlu memasang apa pun di server target; cukup akses SSH dan Python (sudah ada di hampir semua distro Linux).
    • Idempotent. Menjalankan ulang playbook tidak merusak yang sudah benar — sangat penting untuk instalasi yang panjang.
    • Deklaratif. Anda mendeskripsikan kondisi akhir yang diinginkan, bukan urutan perintah manual.
    • Mudah dipasangkan dengan dashboard. Dashboard cukup men-generate file variabel dan inventory, lalu memanggil ansible-playbook.

    Arsitektur Solusi

    Alur kerjanya sederhana:

    [ Dashboard (form) ]
            │  generate
            ▼
    group_vars/all.yml  +  inventory/hosts.ini
            │  trigger
            ▼
    ansible-playbook site.yml
            │  SSH
            ▼
    [ Server target: Zimbra terpasang ]

    Dashboard hanya bertugas mengumpulkan parameter dari user, menuliskannya ke dua file, lalu menjalankan playbook. Seluruh logika instalasi hidup di dalam Ansible.

    Metode Instalasi Unattended

    Kunci otomasi Zimbra adalah membuat installer yang biasanya interaktif menjadi unattended. Saya mengikuti pendekatan resmi dari repo Zimbra (zinstaller), yang terbagi dua fase:

    Fase 1 — Install software saja. Jalankan installer dengan flag software-only dan suplai jawaban lewat answer file:

    ./install.sh -s < keystroke_answers

    Fase 2 — Konfigurasi unattended. Setelah paket terpasang, jalankan setup dengan file konfigurasi, bukan wizard interaktif:

    /opt/zimbra/libexec/zmsetup.pl -c /tmp/zim-install-config

    File zim-install-config berisi key konfigurasi resmi Zimbra (hostname, domain, password admin, komponen yang diaktifkan, dan seterusnya). Template inilah yang diisi dari parameter dashboard.

    Perbedaan OSE vs NE

    Bedanya terutama pada lisensi:

    • OSE — tidak butuh lisensi sama sekali.
    • NE 10.1.x — aktivasi memakai license KEY via zmlicense -a <key>.
    • NE ≤ 10.0 — memakai file lisensi (license.xml) yang ditunjuk lewat variabel LICENSEFILE.

    Dashboard cukup menampilkan field lisensi secara kondisional ketika user memilih edisi NE.

    Struktur Project Ansible

    instaru/
    ├── site.yml
    ├── inventory/
    │   └── hosts.ini
    ├── group_vars/
    │   └── all.yml          # semua parameter wajib di sini
    ├── roles/
    │   ├── preflight/       # cek hostname, DNS, resource, swap
    │   ├── zimbra_install/  # download, install.sh -s
    │   └── zimbra_config/   # render config, zmsetup.pl -c
    └── templates/
        └── zim-install-config.j2

    Parameter wajib (group_vars/all.yml)

    Inilah variabel yang dikumpulkan dashboard dari user:

    # Identitas
    zimbra_edition: "ose"          # ose | ne
    zimbra_version: "10.1.0"
    zimbra_hostname: "mail.example.com"
    zimbra_domain: "example.com"
    
    # Kredensial
    admin_password: "{{ vault_admin_password }}"
    ldap_password: "{{ vault_ldap_password }}"
    
    # Lisensi (hanya untuk NE)
    zimbra_license_key: ""         # NE 10.1.x
    zimbra_license_file: ""        # NE <=10.0
    
    # Komponen
    enable_proxy: true
    enable_spell: true
    timezone: "Asia/Jakarta"
    

    Simpan semua password di Ansible Vault, jangan plaintext. Dashboard bisa mengenkripsi nilai sensitif sebelum menulisnya ke file.

    Inventory & topologi

    Topologi dipilih lewat grup inventory. Untuk single-server semua peran di satu host; untuk multi-server, peran dipecah dan LDAP harus diinstall pertama.

    # Single server

    [zimbra_single]
    mail.example.com 

    atau

    # Multi-server

    [zimbra_ldap]
    ldap.example.com
    
    [zimbra_store] (mailbox/mbox)
    store.example.com
    
    [zimbra_mta]
    mta.example.com
    
    [zimbra_proxy]
    proxy.example.com

    Playbook Utama

    # site.yml
    - name: Validasi & persiapan server
      hosts: all
      become: true
      roles:
        - preflight
    
    - name: Install & konfigurasi Zimbra
      hosts: all
      become: true
      roles:
        - zimbra_install
        - zimbra_config
    

    Role preflight penting: ia memvalidasi bahwa hostname FQDN sudah benar, DNS A dan MX sudah resolve, RAM mencukupi, dan swap tersedia — kesalahan paling umum pada instalasi Zimbra ada di tahap ini, jadi lebih baik gagal cepat sebelum installer berjalan.

    Catatan dependensi: playbook ini membutuhkan koleksi community.general dan ansible.posix. Pasang dengan:

    ansible-galaxy collection install community.general ansible.posix

    Menjalankannya

    Dari dashboard, eksekusi akhirnya sesederhana ini:

    ansible-playbook -i inventory/hosts.ini site.yml --ask-vault-pass

    Dashboard menjalankan perintah ini sebagai background job, lalu menampilkan progres dengan men-stream output Ansible kembali ke UI. User cukup menunggu sampai status berubah menjadi selesai.

    Penutup

    Jika ini berhasil, maka saya tinggal membuat dashboard terintegrasi yang dapat melakukan instalasi zimbra hanya dengan beberapa input saja (ip, hostname, dll) dan akses ssh ke server target.

    Langkah pengembangan berikutnya yang saya rencanakan: validasi parameter real-time di sisi dashboard, dukungan rollback otomatis bila instalasi gagal di tengah jalan, dan template multi-server siap pakai.

  • Struktur File Ansible untuk Instalasi Zimbra Single Server

    Struktur File Ansible untuk Instalasi Zimbra Single Server

    Beberapa waktu terakhir saya iseng oprek oprek menyusun ulang project Ansible saya untuk instalasi Zimbra single server. Alasannya sederhana, dulu saya buat struktur seadanya, satu playbook panjang, variabel campur aduk, dan ujungnya susah dipakai ulang. Jadi di postingan ini saya bagikan bagaimana saya menata file dan folder Ansible untuk instalasi Zimbra single server, lengkap dengan alasan kenapa tiap bagian saya pisah seperti itu.

    Postingan ini fokus pada strukturnya saja, bukan isi task detail per file. Kalau ada bagian yang mau saya dalami di tulisan terpisah, silakan kasih tahu saya di komentar.

    Gambaran umum

    Begini struktur akhir yang saya pakai sekarang.

    tree zimbra-ansible

    Output tree zimbra-ansible/ di terminal saya jadikan screenshot pertama, biar pembaca punya gambaran utuh sebelum saya bahas tiap bagian.

    ansible.cfg

    File ansible.cfg di root project saya pakai untuk override default Ansible khusus project ini, jadi tidak perlu menyentuh konfigurasi global di /etc/ansible/ansible.cfg. Isinya kira-kira soal lokasi inventory, path role, retry files yang saya matikan, dan beberapa setting koneksi SSH seperti host_key_checking = False saat lab.

    Manfaat utama menempatkan ansible.cfg di project, ketika ada anggota tim lain yang clone repository, mereka langsung dapat konfigurasi yang sama tanpa perlu setup global. Praktik komunitas memang menyarankan project-level config untuk repository yang akan dibagi.

    Inventory dan group_vars

    Folder inventory/ di project saya isinya hanya hosts.ini. File ini berisi daftar host beserta pengelompokannya. Untuk Zimbra single server isinya simpel, hanya satu host di bawah grup zimbra_servers. Walaupun cuma satu mesin, saya tetap pakai grup karena memudahkan kalau suatu hari struktur berkembang ke multi-server.

    Folder group_vars/ saya letakkan di root project, bukan di dalam inventory/. Ansible mendukung dua pola ini, dan saya pilih yang di root karena lebih mudah dilihat dan diedit. File all.yml di dalamnya menyimpan variabel yang berlaku untuk semua host, misalnya zimbra_version, zimbra_domain, zimbra_admin_email, timezone, dan URL download installer.

    Untuk skala single server, satu file all.yml sudah cukup. Kalau nanti host bertambah dan tiap grup butuh variabel berbeda, tinggal saya tambah file seperti group_vars/zimbra_servers.yml. Begitu pula host_vars/ belum saya buat karena belum perlu, tapi sudah siap saya tambahkan ketika ada host yang butuh override individual.

    site.yml

    File site.yml di root saya pakai sebagai entry point utama playbook. Isinya menarget grup zimbra_servers dan memanggil role zimbra_single. Di sini saya juga set become: true dan gather_facts: true, ditambah beberapa pre_tasks ringan seperti update cache paket atau memastikan Python tersedia di target.

    Untuk project sekecil ini saya belum pecah site.yml ke beberapa playbook di folder playbooks/. Kalau besok saya tambah role lain misalnya monitoring atau backup, baru saya pisah jadi playbooks/zimbra.yml, playbooks/monitoring.yml, lalu site.yml saya jadikan orchestrator yang berisi import_playbook saja.

    Role zimbra_single

    Bagian paling besar adalah role itu sendiri. Saya namai zimbra_single karena memang khusus untuk topologi single server. Nantinya kalau saya bikin multi-server, saya buat role terpisah seperti zimbra_mta atau zimbra_proxy agar tanggung jawab masing-masing role jelas, ketimbang memaksa satu role besar mengakomodasi semuanya dengan logika percabangan yang ribet.

    defaults dan vars

    Saya isi defaults/main.yml dengan variabel yang user role boleh ubah, misalnya path download installer, nama user dan grup Zimbra, dan daftar komponen yang diinstall. Sifatnya nilai default, prioritas paling rendah, jadi mudah di-override dari group_vars atau host_vars.

    Folder vars/ saya sengaja biarkan kosong untuk sekarang. Bedanya dengan defaults, isi vars/main.yml itu prioritasnya tinggi dan susah di-override tanpa sengaja. Saya rencanakan untuk konstanta internal role yang tidak boleh diutak-atik user, misalnya nama service Zimbra atau path binary zmcontrol. Karena belum ada kebutuhan konkret, saya biarkan dulu agar tidak menambah noise.

    tasks

    Folder tasks/ adalah jantung role. File main.yml saya buat tipis, isinya hanya include_tasks ke file-file lain berdasarkan urutan eksekusi. Pemecahan ini disengaja agar tiap file punya tanggung jawab tunggal dan mudah dibaca.

    File preflight.yml melakukan pengecekan awal sebelum instalasi dimulai. Cek RAM minimum, free disk space, versi OS yang didukung, dan resolusi DNS hostname. Kalau salah satu gagal, playbook stop di sini sebelum sempat merusak apa-apa. Ini investasi waktu yang worth it karena Zimbra cukup picky soal prasyarat.

    File os_debian.yml dan os_redhat.yml saya pecah berdasarkan OS family. Isinya menangani persiapan OS yang spesifik per distro, mulai dari instalasi paket dependensi, disable service yang bentrok seperti postfix atau sendmail bawaan, sampai konfigurasi firewall (UFW untuk Debian, firewalld untuk RHEL). Saya pilih pendekatan pecah file per OS ketimbang satu file panjang dengan when: ansible_os_family == "...", karena lebih bersih saat dibaca dan lebih mudah dirawat kalau salah satu distro butuh logika kompleks. Pemanggilannya tetap pakai when di main.yml, jadi hanya satu file yang dijalankan tergantung OS target.

    File download.yml mengunduh tarball installer Zimbra dari URL yang didefinisikan di variabel, lalu mengekstraknya ke direktori kerja. Saya tambahkan verifikasi checksum SHA256 di sini untuk memastikan file tidak korup atau diganti di tengah jalan.

    File install.yml menjalankan installer dalam mode unattended menggunakan config file. Ini bagian paling tricky karena installer Zimbra interaktif by default. Saya pakai template zinstall.cfg.j2 untuk menghasilkan config file yang dibaca oleh install.sh -s -c. Nilai yang ditemplate antara lain admin password, domain, time zone, hostname, dan komponen yang dipilih.

    File config.yml berisi konfigurasi setelah instalasi selesai. Set admin password lewat zmprov, atur quota default, konfigurasi domain dan COS, restart service kalau perlu, dan hal-hal lain yang sifatnya tuning post-install. Saya konsolidasikan ke satu file karena task-nya saling terkait dan dijalankan beruntun. Kalau ke depan jumlahnya membengkak, baru saya pecah lagi misalnya jadi ssl.yml, quota.yml, dan seterusnya.

    handlers

    Folder handlers/main.yml menyimpan handler yang dipanggil pakai notify. Untuk Zimbra saya punya handler restart zimbra yang menjalankan zmcontrol restart sebagai user zimbra, dan reload firewall. Pemakaian handler ini menjaga idempotensi, restart hanya terjadi kalau memang ada perubahan, bukan setiap kali playbook dijalankan.

    templates

    File-file di templates/ berekstensi .j2 karena diproses Jinja2. Saya punya dua, hosts.j2 untuk men-generate /etc/hosts yang Zimbra-friendly (urutan baris dan FQDN harus benar, kalau tidak installer langsung gagal), dan zinstall.cfg.j2 untuk config file installer Zimbra yang dipakai mode unattended.

    Praktik yang saya ikuti, semua nilai yang berubah-ubah saya parameterkan lewat variabel, bukan hardcode di template. Misalnya {{ zimbra_admin_password }}, {{ zimbra_domain }}, {{ ansible_fqdn }}. Dengan begini template bisa dipakai ulang untuk berbagai environment tanpa harus diedit ulang.

    requirements.yml

    Di root project saya punya requirements.yml yang mendaftarkan collection eksternal yang dipakai role, misalnya community.general dan ansible.posix. Anggota tim lain yang clone repo cukup jalankan ansible-galaxy collection install -r requirements.yml dan dependency mereka beres.

    Praktik ini termasuk yang sering dilupakan tapi sangat penting. Tanpa requirements.yml, playbook bisa error karena modul tidak ketemu, dan debugging-nya menyebalkan, apalagi kalau yang menjalankan bukan kalian sendiri.

    Hal yang belum saya tambahkan

    Ada beberapa hal yang belum ada di struktur sekarang tapi sebenarnya layak dipertimbangkan ke depan.

    Pertama, Ansible Vault untuk menyimpan secret seperti password admin Zimbra dan password LDAP. Pola yang biasa dipakai, buat file group_vars/vault.yml yang dienkripsi pakai ansible-vault encrypt, lalu variabel di dalamnya diberi prefix vault_. Di file plaintext tinggal dibuat alias seperti zimbra_admin_password: "{{ vault_zimbra_admin_password }}". Untuk lab saya masih pakai variabel plaintext, tapi sebelum dipakai di environment yang lebih serius, vault wajib ditambahkan.

    Kedua, folder meta/ di dalam role yang berisi metadata seperti author, license, dan platform yang didukung. Kalau suatu hari role ini saya publish ke Ansible Galaxy atau saya share ke tim lain, file ini yang dibaca pertama kali. Tidak urgent untuk lab, tapi bagus dimiliki.

    Ketiga, README di dalam role itu sendiri. README di root sudah ada, tapi role yang reusable idealnya punya README sendiri yang mendokumentasikan variabel yang tersedia dan contoh pemakaian.

    Penutup

    Struktur di atas bukan satu-satunya cara, tapi cukup mewakili praktik yang umum dipakai komunitas Ansible. Inti yang saya pegang ada empat, pisahkan inventory dari role agar reusable, taruh variabel sesuai hierarki, pecah tasks per tanggung jawab, dan jangan pernah commit secret tanpa enkripsi.

    Kalau kalian baru mulai dengan Ansible, saran saya jangan langsung niru struktur lengkap. Mulai dari yang sederhana, satu playbook, satu inventory, satu role. Begitu kompleksitas naik, baru pelan-pelan pecah sesuai kebutuhan. Struktur yang baik itu yang menjawab masalah nyata, bukan struktur yang rapi tapi over-engineered untuk skala kecil.

    Kalau ada bagian yang ingin saya bahas lebih detail, misalnya isi zinstall.cfg.j2 lengkap atau task instalasi Zimbra step by step, kasih tahu saya di komentar.