Project Structure #
Struktur project yang terorganisir dengan baik merupakan pondasi utama dari keberhasilan otomatisasi infrastruktur. Playbook yang ditulis dengan sangat cerdas tetap akan menjadi beban pemeliharaan jika anggota tim kesulitan menemukan file konfigurasi yang relevan. Ketika project Ansible kita tumbuh dari mengelola beberapa server menjadi ratusan server lintas wilayah, struktur direktori yang ad-hoc akan dengan cepat berubah menjadi “ansible spaghetti”. Kita perlu menerapkan konvensi tata letak direktori, manajemen variabel, dan pemisahan environment yang disiplin agar project tetap mudah dipahami, aman, dan dapat diskalakan tanpa hambatan.
1. Layout Direktori Standard untuk Skala Enterprise #
Dalam membangun infrastruktur berbasis kode (Infrastructure as Code), kita memerlukan standarisasi layout direktori. Hal ini bertujuan agar setiap engineer yang bergabung ke dalam tim dapat langsung memahami di mana letak playbook, role, inventory, dan variabel tanpa harus melakukan pencarian manual yang melelahkan.
Berikut adalah struktur direktori terlengkap yang kita rekomendasikan untuk project Ansible berskala menengah hingga besar:
ansible-infrastructure/
├── ansible.cfg # Konfigurasi default Ansible khusus untuk project ini
├── requirements.yml # Definisi dependensi collection dan role dari Ansible Galaxy
├── README.md # Panduan orientasi cepat dan dokumentasi cara menjalankan playbook
├── .gitignore # File untuk mengecualikan file lokal, cache, dan secret dari Git
│
├── inventory/ # Direktori utama untuk seluruh konfigurasi environment
│ ├── development/ # Environment Development
│ │ ├── hosts.ini # Daftar host untuk dev
│ │ └── group_vars/ # Variabel spesifik grup di environment dev
│ │ ├── all.yml # Variabel global dev
│ │ ├── webservers.yml # Variabel khusus grup webservers dev
│ │ └── vault.yml # Variabel rahasia dev (terenkripsi)
│ ├── staging/ # Environment Staging
│ │ ├── hosts.ini # Daftar host untuk staging
│ │ └── group_vars/ # Variabel spesifik grup di environment staging
│ │ ├── all.yml
│ │ ├── webservers.yml
│ │ └── vault.yml
│ └── production/ # Environment Production
│ ├── hosts.ini # Daftar host untuk prod
│ └── group_vars/ # Variabel spesifik grup di environment prod
│ ├── all.yml
│ ├── webservers.yml
│ └── vault.yml
│
├── group_vars/ # Variabel global yang dibagikan ke semua environment (shared)
│ └── all.yml # Berisi variabel non-sensitif umum seperti zona waktu
│
├── playbooks/ # Semua file playbook utama diletakkan di sini
│ ├── site.yml # Master playbook untuk orkestrasi seluruh infrastruktur
│ ├── provision.yml # Playbook khusus untuk penyediaan (provisioning) OS/VM baru
│ ├── deploy.yml # Playbook untuk deployment aplikasi ke server
│ ├── patch.yml # Playbook untuk pembaruan security patch OS
│ └── rollback.yml # Playbook darurat untuk mengembalikan versi aplikasi
│
├── roles/ # Direktori untuk role custom buatan internal kita
│ ├── common/ # Role dasar yang wajib dijalankan di setiap host
│ ├── nginx/ # Role untuk instalasi dan konfigurasi Nginx
│ ├── postgresql/ # Role untuk database PostgreSQL
│ └── myapp/ # Role spesifik untuk deployment modul aplikasi kita
│
├── collections/ # Direktori lokal untuk instalasi collection (di-ignore dari Git)
│ └── ansible_collections/
│
├── tests/ # Pengujian otomatis playbook dan role
│ ├── test-vars.yml # Variabel dummy untuk pengujian
│ └── integration/ # Skenario pengujian integrasi
│
├── scripts/ # Kumpulan script pembantu (helper) operasional
│ ├── encrypt-vault.sh # Helper untuk mengenkripsi file secret secara cepat
│ └── rotate-vault-password.sh # Script otomatisasi rotasi password vault
│
└── .github/ # Pipeline integrasi berkelanjutan (CI/CD)
└── workflows/
├── validate.yml # Pipeline validasi syntax dan linting
└── deploy.yml # Pipeline deployment otomatis
Dengan memisahkan berkas ke dalam direktori fungsional seperti di atas, kita mengisolasi risiko perubahan. Sebagai contoh, jika kita hanya ingin mengubah variabel konfigurasi staging, kita tahu pasti bahwa perubahan tersebut dibatasi pada direktori inventory/staging/ dan tidak akan menyentuh kode program utama yang berada di dalam roles/.
2. Isolasi Environment Melalui Struktur Inventory #
Salah satu kesalahan fatal yang sering kita temukan di lapangan adalah pencampuran host dari berbagai environment (dev, staging, prod) di dalam satu file inventory tunggal. Hal ini sangat berisiko menimbulkan insiden salah sasaran deployment, di mana playbook yang awalnya ditujukan untuk server development malah dieksekusi ke server production karena kesalahan pengetikan parameter target.
Kita harus menerapkan isolasi mutlak menggunakan direktori inventory terpisah untuk masing-masing lingkungan kerja.
A. Struktur File Hosts.ini #
Di setiap direktori environment, kita meletakkan file host yang hanya merujuk pada server di lingkungan tersebut.
Contoh inventory/production/hosts.ini:
[webservers]
prod-web-01 ansible_host=10.10.10.11
prod-web-02 ansible_host=10.10.10.12
[dbservers]
prod-db-01 ansible_host=10.10.10.20
prod-db-02 ansible_host=10.10.10.21
[load_balancers]
prod-lb-01 ansible_host=10.10.10.5
[production:children]
webservers
dbservers
load_balancers
B. Mekanisme Penggabungan Variabel (Variable Merging) #
Ansible memiliki hirarki evaluasi variabel yang sangat ketat. Dengan menyusun direktori group_vars/ di bawah masing-masing environment, kita dapat memastikan bahwa variabel dengan nama yang sama akan bernilai berbeda sesuai dengan konteks environment yang kita jalankan.
Berikut adalah diagram alur bagaimana Ansible mengevaluasi dan menggabungkan variabel dari berbagai tingkat direktori saat kita mengeksekusi perintah deployment:
flowchart TD
A["Mulai Eksekusi Playbook"] --> B["Evaluasi group_vars/all.yml Global"]
B --> C["Evaluasi group_vars/all.yml di Inventory Environment"]
C --> D["Evaluasi group_vars/[group_name].yml Environment"]
D --> E["Evaluasi host_vars/[host_name].yml Environment"]
E --> F["Evaluasi Variabel Role (defaults/main.yml)"]
F --> G["Evaluasi Variabel Role (vars/main.yml)"]
G --> H["Evaluasi Variabel Playbook (vars atau vars_files)"]
H --> I["Evaluasi Extra Vars (--extra-vars)"]
I --> J["Variabel Final Terbentuk"]
Dengan model ini, kita meletakkan nilai default global yang aman di group_vars/all.yml di root project (misalnya app_port: 8080), kemudian kita menimpa nilai tersebut khusus untuk production di inventory/production/group_vars/all.yml (misalnya app_port: 443) tanpa merusak konfigurasi di environment development.
3. Standardisasi Konfigurasi ansible.cfg di Root Project #
Secara default, Ansible akan mencari file konfigurasi global di /etc/ansible/ansible.cfg atau ~/.ansible.cfg. Mengandalkan konfigurasi global ini adalah anti-pattern yang berbahaya karena setiap developer di tim kita mungkin memiliki versi konfigurasi global yang berbeda-beda. Hal ini dapat menyebabkan perbedaan perilaku eksekusi antar mesin developer.
Kita wajib menyertakan file ansible.cfg lokal di root direktori project. File ini akan otomatis dibaca oleh Ansible ketika dijalankan dari direktori tersebut.
Berikut adalah konfigurasi ansible.cfg yang optimal dan aman untuk kebutuhan operasional sehari-hari:
# ansible.cfg
[defaults]
# Mengarahkan lokasi inventory default ke direktori manajemen kita
inventory = inventory/
# Menentukan jalur pencarian role, baik role lokal maupun global
roles_path = roles:~/.ansible/roles
# Menentukan jalur pencarian collection yang di-install
collections_paths = collections:~/.ansible/collections
# User default yang digunakan untuk koneksi SSH ke target host
remote_user = ansible-deploy
# Lokasi ssh private key untuk otentikasi otomatis
private_key_file = ~/.ssh/ansible_deploy_key
# Menolak koneksi jika host key server target berubah (keamanan man-in-the-middle)
host_key_checking = true
# Menonaktifkan pembuatan berkas .retry yang mengotori direktori kerja kita
retry_files_enabled = false
# Mengubah format output terminal menjadi YAML agar lebih mudah dibaca daripada JSON satu baris
stdout_callback = yaml
# Mengaktifkan plugin callback untuk menganalisis waktu eksekusi task
callbacks_enabled = profile_tasks, timer
# Batasi jumlah fork paralel untuk menghemat bandwidth dan kapasitas RAM kontroler
forks = 20
# Mengoptimalkan pengumpulan facts sistem secara cerdas
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
fact_caching_timeout = 3600
[ssh_connection]
# Mengaktifkan pipelining untuk mempercepat eksekusi task dengan meminimalkan koneksi SSH baru
pipelining = true
# Parameter tambahan SSH untuk menjaga koneksi tetap hidup dan menggunakan ControlMaster
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
[vault]
# Mengatur peta kunci rahasia Ansible Vault per-environment secara otomatis
vault_identity_list = [email protected]_pass_dev, [email protected]_pass_staging, [email protected]_pass_prod
Penjelasan Parameter Kunci: #
pipelining = true: Secara default, Ansible mentransfer modul python ke server target, menjalankannya, lalu menghapusnya lewat koneksi SSH terpisah untuk setiap task. Pipelining meminimalkan overhead ini dengan mengirimkan modul langsung lewat stdin SSH, meningkatkan kecepatan eksekusi hingga 4x lipat.stdout_callback = yaml: Mengubah output dari bentuk default yang berantakan menjadi format YAML berindentasi yang sangat bersih, sehingga kita bisa mendeteksi error dengan cepat.vault_identity_list: Memungkinkan kita menggunakan kata sandi vault yang berbeda untuk setiap environment tanpa harus mengetikkan flag--vault-password-filesecara manual setiap kali menjalankan playbook.
4. Konvensi Penamaan yang Konsisten #
Konsistensi penamaan (naming convention) adalah bahasa bersama yang menyatukan tim. Tanpa aturan penamaan yang jelas, kita akan melihat variasi nama yang membingungkan seperti setupNginx, install-nginx, dan nginx_install di dalam project yang sama.
Kita menerapkan aturan penamaan standar berikut di seluruh bagian project:
| Komponen | Aturan Penamaan | Contoh BENAR | Contoh SALAH |
|---|---|---|---|
| Inventory Groups | Lowercase, pisahkan dengan underscore, gunakan nama peran fungsional. | db_servers, web_servers |
DB-SERVERS, web, ws |
| Variabel | Lowercase, diawali dengan prefix nama role (namespace) untuk mencegah bentrokan. | nginx_port, postgresql_version |
port, version, NginxPort |
| Playbook | Lowercase, gunakan format kata kerja diikuti objek dengan tanda hubung. | deploy-app.yml, patch-os.yml |
main.yml, deploy.yml |
| Role | Lowercase, gunakan nama komponen langsung tanpa imbuhan kata kerja. | nginx, postgresql |
setup-nginx, install-postgresql |
| File Vault | Selalu simpan dengan nama vault.yml di dalam direktori group_vars. |
group_vars/all/vault.yml |
secrets.txt, prod_pass.yml |
A. Pola Namespacing Variabel #
Bentrokan variabel (variable collision) adalah bug yang sangat sulit dilacak di Ansible karena variabel bersifat global dalam cakupan runtime playbook. Jika role nginx dan role postgresql sama-masing mendefinisikan variabel bernama port, salah satu akan menimpa nilai yang lain secara tidak sengaja.
# ANTI-PATTERN: Variabel tanpa namespace yang rentan bentrok
port: 80
version: 1.14.2
config_dir: /etc/nginx
# BENAR: Menggunakan prefix nama role sebagai namespace
nginx_port: 80
nginx_version: 1.14.2
nginx_config_dir: /etc/nginx
B. Pemisahan Variabel Sensitif (Vault) #
Jangan pernah mencampur variabel biasa dengan variabel sensitif di dalam satu file. Kita harus mengisolasi semua secret ke dalam file khusus bernama vault.yml dan mereferensikannya menggunakan variabel penampung (bridge variables).
# inventory/production/group_vars/all/vars.yml
# BENAR: Variabel non-sensitif dideklarasikan di file biasa
db_user: myapp_admin
db_host: prod-db-01.internal
db_password: "{{ vault_db_password }}" # Merujuk ke variabel terenkripsi di vault.yml
# inventory/production/group_vars/all/vault.yml
# BENAR: File ini dienkripsi sepenuhnya menggunakan ansible-vault
vault_db_password: "SangatRahasiaDanPanjang123!"
5. site.yml sebagai Master Entry Point #
Seiring berkembangnya infrastruktur, kita akan memiliki puluhan playbook untuk berbagai keperluan. Tanpa adanya satu titik masuk utama yang jelas, operator baru akan kebingungan menentukan playbook mana yang harus dijalankan untuk melakukan setup awal seluruh infrastruktur dari nol.
Kita harus membuat sebuah file bernama site.yml di dalam direktori playbooks/ (atau di-link ke root project). File ini bertindak sebagai orkestrator tertinggi yang memanggil playbook atau role lain secara berurutan.
Berikut adalah contoh implementasi playbooks/site.yml:
# playbooks/site.yml
# Master Playbook - Dokumentasi hidup dari arsitektur infrastruktur kita
---
- name: Terapkan konfigurasi dasar ke seluruh server
hosts: all
gather_facts: true
become: true
roles:
- common
- security-hardening
- name: Konfigurasi dan klasterisasi Database PostgreSQL
hosts: db_servers
become: true
roles:
- postgresql
- postgresql-backup
- name: Setup Web Server dan Aplikasi Node.js
hosts: web_servers
become: true
roles:
- nodejs
- myapp
- name: Konfigurasi Load Balancer Nginx terluar
hosts: load_balancers
become: true
roles:
- nginx
Mengapa Pendekatan Ini Sangat Kuat? #
- Dokumentasi Infrastruktur: Hanya dengan membaca file
site.yml, siapapun dapat langsung memahami topologi infrastruktur kita—apa saja jenis server yang kita miliki dan role apa yang berjalan di atasnya. - Kapasitas Eksekusi Parsial: Jika kita hanya ingin memperbarui server database tanpa menyentuh load balancer, kita dapat menggunakan flag limitasi bawaan Ansible:
Hal ini jauh lebih aman daripada mengelola banyak playbook kecil yang terpisah-pisah tanpa orkestrator terpusat.
ansible-playbook -i inventory/production/ playbooks/site.yml --limit db_servers
6. README.md yang Bersifat Actionable #
README.md sering kali diabaikan dan hanya berisi deskripsi satu kalimat yang tidak membantu. README.md yang buruk memaksa developer baru melakukan trial-and-error yang membuang waktu dan berisiko merusak sistem.
Kita harus menulis README.md yang berorientasi pada tindakan (actionable), berisi perintah-perintah konkrit yang bisa disalin-tempel (copy-paste) untuk mulai bekerja.
Berikut adalah template README.md standar yang wajib ada di setiap root project Ansible kita:
# Ansible Infrastructure Codebase
Repositori ini mengelola seluruh otomatisasi penyediaan, konfigurasi, dan deployment infrastruktur cloud kita.
## 1. Prasyarat Sistem
Sebelum menjalankan playbook, pastikan mesin lokal kita telah memiliki:
- **Python**: Versi 3.11 atau lebih tinggi
- **Ansible**: Versi 2.15 atau lebih tinggi
- **SSH Key**: Private key terdaftar di `~/.ssh/ansible_deploy_key` dengan hak akses sudo di server target.
## 2. Persiapan Awal (Setup)
Jalankan perintah berikut di direktori root untuk menginstal dependensi eksternal dan menyiapkan kunci enkripsi vault lokal:
```bash
# Menginstal dependency collection dan role eksternal
ansible-galaxy install -r requirements.yml
# Membuat file password vault lokal untuk environment development (jangan commit file ini!)
echo "password_dev_kita" > .vault_pass_dev
chmod 600 .vault_pass_dev
```
## 3. Panduan Operasional Sehari-hari
### A. Validasi Kode (Dry-run)
Selalu jalankan pemeriksaan syntax dan simulasi perubahan sebelum melakukan deployment sesungguhnya:
```bash
# Cek syntax playbook
ansible-playbook -i inventory/staging/ playbooks/site.yml --syntax-check
# Simulasi perubahan (Check mode dengan diff visual)
ansible-playbook -i inventory/staging/ playbooks/site.yml --check --diff
```
### B. Deployment Aplikasi ke Staging
```bash
ansible-playbook -i inventory/staging/ playbooks/deploy.yml -e "app_version=v2.4.0"
```
### C. Deployment Infrastruktur Penuh ke Production
```bash
ansible-playbook -i inventory/production/ playbooks/site.yml
```
## 4. Manajemen Rahasia (Ansible Vault)
Kita mengunci variabel sensitif per-environment. Untuk mengedit file vault production:
```bash
ansible-vault edit inventory/production/group_vars/all/vault.yml --vault-id [email protected]_pass_prod
```
Dengan README.md yang terstruktur seperti ini, waktu orientasi (onboarding) developer baru dapat dipangkas dari hitungan hari menjadi hanya beberapa menit saja.
7. Pola Struktur untuk Project Kecil vs Skala Besar #
Seiring bertambahnya jumlah playbook dan role, satu repository monorepo bisa menjadi terlalu padat dan sulit dikelola karena terlalu banyak tim yang mengubah file yang sama. Kita harus tahu kapan harus mempertahankan arsitektur sederhana dan kapan harus memecahnya ke arsitektur terdistribusi.
Berikut adalah diagram alur keputusan (Decision Tree) untuk memandu kita memilih pola arsitektur project yang paling efisien berdasarkan skala tim dan jumlah server:
flowchart TD
A["Mulai Evaluasi Skala Project"] --> B{"Berapa jumlah host target?"}
B --|"< 50 Server"| C{"Berapa tim yang mengelola?"}
B --|"> 50 Server"| D["Gunakan Pola Monorepo Terstruktur"]
C --|"Satu Tim Terpusat"| E["Gunakan Pola Single Repository (Sederhana)"]
C --|"Multi Tim Lintas Divisi"| D
D --> F{"Apakah role sering dibagikan ke project lain?"}
F --|"Ya"| G["Pecah Role menjadi Ansible Collections Terpisah"]
F --|"Tidak"| H["Pertahankan Monorepo dengan CI/CD yang Ketat"]
A. Pola Project Kecil (< 50 Server) #
Untuk skala kecil, kita tidak perlu memisahkan inventory ke sub-folder yang terlalu dalam jika server kita sangat dinamis. Cukup gunakan satu file inventory dengan pemisahan grup host yang jelas di dalamnya. Namun, pastikan variabel tetap terorganisir dengan rapi.
B. Pola Skala Enterprise (> 50 Server / Multi-Tim) #
Pada skala ini, kita merekomendasikan pemecahan repositori. Kode program utama (role generic seperti nginx atau mysql) harus ditarik keluar dari repositori infrastruktur utama dan dipublikasikan sebagai Ansible Collection atau repository role mandiri. Repositori infrastruktur utama kemudian hanya bertindak sebagai “lem” yang mengimpor role-role tersebut lewat file requirements.yml dan mengatur konfigurasi variabelnya.
Contoh requirements.yml:
# requirements.yml
---
roles:
# Mengimpor role open-source dari Ansible Galaxy dengan versi ter-pin
- name: geerlingguy.nginx
version: 3.1.1
- name: geerlingguy.postgresql
version: 3.0.0
collections:
# Mengimpor collection cloud provider untuk provisioning
- name: amazon.aws
version: 6.0.0
- name: community.general
version: 8.2.0
Hal ini menjaga repositori utama kita tetap bersih, ringan, dan meminimalkan konflik commit Git antar anggota tim yang berbeda divisi.
Anti-Pattern yang Harus Dihindari #
Dalam menyusun struktur project Ansible, terdapat beberapa praktek buruk (anti-pattern) yang sering dilakukan namun sangat merugikan dalam jangka panjang:
1. Menyimpan Kunci SSH atau Password Vault Plaintext di Git #
Ini adalah celah keamanan paling kritis. Menuliskan password mentah atau meletakkan private key SSH di dalam repositori akan terekspos ke semua orang yang memiliki akses baca ke Git, meningkatkan risiko kebocoran data.
# ANTI-PATTERN: Menyimpan password mentah di file YAML
ansible_ssh_pass: "RahasiaSangatKuat123"
db_root_password: "super_secret_db_pass"
# BENAR: Merujuk ke variabel vault yang terenkripsi aman
ansible_ssh_pass: "{{ vault_ansible_ssh_pass }}"
db_root_password: "{{ vault_db_root_password }}"
2. Menggunakan Direktori Library Ad-Hoc Tanpa Standar #
Menaruh modul custom python di sembarang tempat akan membuat Ansible gagal mendeteksi modul tersebut di mesin developer lain.
# ANTI-PATTERN: Lokasi file modul yang acak
ansible-infrastructure/
├── my_custom_module.py
└── playbooks/
└── deploy.yml
# BENAR: Taruh modul di folder library bawaan di root agar otomatis dimuat
ansible-infrastructure/
├── library/
│ └── my_custom_module.py
└── playbooks/
└── deploy.yml
Checklist Review Struktur Project #
Kita harus memverifikasi kepatuhan arsitektur folder kita menggunakan kriteria checklist berikut sebelum melakukan merger kode ke cabang utama (main branch):
DIREKTORI & STRUKTUR:
□ File ansible.cfg berada di root project dan tidak mengandalkan konfigurasi global /etc/ansible/.
□ Dependensi luar dideklarasikan secara tertulis di requirements.yml dengan versi yang terkunci (pinned).
□ Direktori roles/ hanya berisi role internal, sedangkan role luar diinstal ke direktori eksternal terpisah.
□ Folder tests/ berisi playbook minimal untuk memvalidasi seluruh role secara mandiri.
INVENTORY & VARIABEL:
□ Setiap environment memiliki direktori inventory tersendiri yang terisolasi secara fisik.
□ Tidak ada variabel global sensitif yang ditaruh di group_vars/all.yml root tanpa enkripsi.
□ Variabel sensitif berada di file vault.yml dan telah terenkripsi menggunakan ansible-vault.
□ Semua nama variabel menggunakan prefix nama role (namespace) untuk mencegah bentrokan nama.
DOKUMENTASI & ENTRY POINT:
□ Terdapat file site.yml di root atau folder playbooks sebagai master orkestrator utama.
□ File README.md memuat perintah copy-paste yang jelas untuk setup awal dan eksekusi playbook.
□ File .gitignore mengecualikan cache lokal (.ansible-lint, .cache, .tmp) dan berkas password vault.
Ringkasan #
- Folder Inventory Terpisah — Kita wajib memisahkan file inventory per environment (dev, staging, prod) ke dalam sub-folder tersendiri demi mencegah kesalahan fatal salah sasaran eksekusi playbook ke production.
- Konfigurasi Lokal ansible.cfg — Selalu sertakan file
ansible.cfgdi root project untuk memastikan seluruh anggota tim dan pipeline CI/CD mengeksekusi Ansible dengan parameter perilaku yang identik.- Namespacing Variabel — Hindari bentrokan variabel dengan selalu memberikan prefix nama role pada setiap variabel yang kita buat (contoh:
nginx_portalih-alihport).- Pemisahan Secret — Pisahkan variabel sensitif ke file
vault.ymlterenkripsi dan gunakan variabel jembatan di file konfigurasi biasa untuk merujuk nilainya.- site.yml Sebagai Peta Utama — Gunakan playbook master
site.ymlsebagai titik masuk orkestrator tunggal untuk mendokumentasikan dan menjalankan konfigurasi seluruh server kita secara teratur.- README.md Berorientasi Aksi — Buat petunjuk instalasi dan eksekusi yang konkrit di README.md agar developer baru dapat langsung berkontribusi tanpa hambatan teknis.
- Pin Versi Dependensi — Selalu kunci versi role dan collection di
requirements.ymlagar sistem kita terhindar dari bug akibat perubahan versi library di masa mendatang.