Dependency #
Dalam pengembangan arsitektur otomasi skala besar, sangat jarang sebuah role berdiri sendiri secara terisolasi. Sebuah role yang bertugas men-deploy aplikasi web Node.js kita, misalnya, hampir pasti membutuhkan runtime Node.js terinstal terlebih dahulu. Begitu pula role database PostgreSQL yang membutuhkan konfigurasi dasar keamanan OS dan direktori penyimpanan yang disiapkan oleh role dasar.
Jika kita tidak memiliki mekanisme untuk mengelola hubungan antar role ini, kita terpaksa harus mengingat dan menuliskan daftar role tersebut secara manual dengan urutan yang benar di setiap playbook yang kita buat. Hal ini sangat tidak praktis dan rentan terhadap kesalahan manusia. Untuk mengatasi masalah inilah Ansible menyediakan fitur manajemen dependensi (dependency management) yang memungkinkan kita mendeklarasikan hubungan antar role secara deklaratif dan otomatis.
Deklarasi Dependency Melalui meta/main.yml #
Di dalam Ansible, dependensi antar role dideklarasikan secara eksklusif di dalam file meta/main.yml yang terletak di direktori role bersangkutan. Kita menggunakan kunci dependencies untuk mendefinisikan daftar role lain yang harus dieksekusi sebelum role utama kita berjalan.
Mari kita lihat struktur deklarasi dependensi dasar pada sebuah role aplikasi bernama myapp:
# roles/myapp/meta/main.yml
# BENAR: Mendeklarasikan dependensi role lain dengan variabel pendukung
---
galaxy_info:
author: "Tim Infrastruktur"
description: "Role untuk deployment aplikasi internal kita"
company: "Contoh Corp"
license: "MIT"
min_ansible_version: "2.10"
platforms:
- name: Ubuntu
versions:
- "22.04"
# Daftar role yang harus dijalankan SEBELUM role myapp berjalan
dependencies:
- role: common
- role: nodejs
- role: postgresql
Ketika kita memanggil role myapp di dalam playbook kita, Ansible secara otomatis akan mendeteksi daftar dependencies ini. Ansible kemudian akan mengunduh (jika belum ada) dan mengeksekusi role common, diikuti nodejs, lalu postgresql, sebelum akhirnya menjalankan task-task yang ada di dalam role myapp.
Mengirimkan Variabel ke Dependensi #
Salah satu fitur paling kuat dari sistem dependensi Ansible adalah kemampuan untuk mengirimkan (pass) variabel ke role dependensi tersebut. Ini memungkinkan kita untuk menyesuaikan perilaku role dependensi agar sesuai dengan kebutuhan spesifik role utama kita.
Mari kita lihat contoh pengiriman variabel yang kompleks di meta/main.yml:
# roles/myapp/meta/main.yml
---
dependencies:
- role: common
vars:
common_enable_firewall: true
common_allowed_ports:
- 80
- 443
- role: nodejs
vars:
nodejs_version: "18.x"
nodejs_install_global_packages:
- pm2
- yarn
- role: postgresql
vars:
postgresql_databases:
- name: "myapp_prod"
owner: "myapp_user"
postgresql_users:
- name: "myapp_user"
password: "{{ vault_db_password }}"
Dengan metode ini, role nodejs dan postgresql yang bersifat generik dapat dikustomisasi secara dinamis khusus untuk kebutuhan deployment myapp. Variabel yang dikirimkan melalui sistem dependensi ini memiliki cakupan (scope) yang terisolasi untuk eksekusi dependensi tersebut, sehingga tidak akan merusak konfigurasi role yang sama di bagian play lainnya secara tidak sengaja.
Mekanisme dan Urutan Resolusi Dependency Rekursif #
Ansible menyelesaikan dependensi role menggunakan algoritma pencarian mendalam secara rekursif (recursive depth-first search). Ini berarti jika Role A bergantung pada Role B, dan Role B sendiri memiliki dependensi terhadap Role C, maka Ansible akan menyelesaikan seluruh rantai dependensi tersebut hingga ujung terdalam sebelum mengeksekusi Role A.
Mari kita visualisasikan rantai resolusi dependensi rekursif ini:
Playbook memanggil Role A
│
├── Evaluasi dependensi Role A -> Menemukan Role B
│ │
│ └── Evaluasi dependensi Role B -> Menemukan Role C
│ │
│ ▼
│ [Langkah 1] Jalankan Role C (Ujung dependensi terdalam)
│ │
│ ▼
│ [Langkah 2] Jalankan Role B (Dependensi C selesai)
│ │
│ ▼
│ [Langkah 3] Jalankan Role A (Dependensi B selesai)
Untuk memberikan pemahaman visual yang lebih terstruktur mengenai alur resolusi dependensi ini, mari kita perhatikan diagram flowchart berikut:
flowchart TD
Start["Playbook memanggil Role Utama (A)"] --> CheckDep{"Apakah Role A memiliki dependensi di meta/main.yml?"}
CheckDep -- "Ya" --> GetDep["Ambil daftar dependensi (misal: B)"]
GetDep --> CheckDepB{"Apakah Role B memiliki dependensi?"}
CheckDepB -- "Ya" --> GetDepB["Ambil daftar dependensi (misal: C)"]
GetDepB --> RunC["Jalankan task-task di dalam Role C"]
RunC --> RunB["Jalankan task-task di dalam Role B"]
CheckDepB -- "Tidak" --> RunB
RunB --> RunA["Jalankan task-task di dalam Role A"]
CheckDep -- "Tidak" --> RunA
RunA --> End["Selesai: Seluruh alur eksekusi role terpenuhi"]
Posisi Dependensi dalam Siklus Hidup Playbook #
Penting bagi kita untuk memahami posisi eksekusi dependensi role di dalam siklus hidup (lifecycle) eksekusi playbook Ansible. Urutan jalannya proses dalam sebuah play didefinisikan sebagai berikut:
pre_tasks: Task yang didefinisikan di bagian ini akan dieksekusi paling pertama sebelum apa pun.- Handlers dari
pre_tasks: Jika ada handler yang dipicu olehpre_tasks, mereka akan dijalankan di sini. - Dependensi Role (
dependencies): Ansible akan mengeksekusi semua dependensi role yang dideklarasikan dimeta/main.ymlsecara berurutan. - Role Utama (
roles): Setelah dependensi selesai, task dari role utama dieksekusi. tasks: Task biasa yang ditulis langsung di dalam playbook.- Handlers dari
rolesdantasks: Semua handler yang dipicu selama fase eksekusi role dan task akan dieksekusi di sini. post_tasks: Task yang didefinisikan untuk berjalan paling akhir setelah seluruh proses utama selesai.- Handlers dari
post_tasks: Di eksekusi di bagian paling akhir dari siklus.
Memahami urutan ini sangat penting untuk mencegah bug di mana task di playbook utama berasumsi bahwa sesuatu sudah dikonfigurasi oleh role, padahal secara siklus eksekusi hal tersebut belum berjalan.
Mengontrol Duplikasi Eksekusi dengan allow_duplicates #
Secara default, Ansible menerapkan mekanisme deduplikasi (deduplication) pada role. Jika ada beberapa role yang bergantung pada role yang sama, Ansible hanya akan mengeksekusi role dependensi tersebut satu kali saja. Ini adalah perilaku bawaan yang sangat logis dan aman.
Sebagai contoh, bayangkan kita memiliki Playbook dengan struktur seperti ini:
- Role
webserverbergantung pada Rolecommon. - Role
databasejuga bergantung pada Rolecommon.
Tanpa adanya deduplikasi, role common (yang mungkin melakukan update package apt dan konfigurasi firewall) akan dijalankan dua kali secara mubazir. Deduplikasi memastikan server kita tidak melakukan pekerjaan yang sama berulang kali, sehingga menghemat waktu eksekusi playbook kita secara signifikan.
Menggunakan allow_duplicates: true #
Namun, ada kalanya kita justru ingin menjalankan role yang sama beberapa kali dengan parameter yang berbeda. Misalnya, kita memiliki role bernama vhost yang bertugas membuat konfigurasi virtual host Nginx. Kita ingin memanggil role vhost ini beberapa kali untuk domain yang berbeda di server yang sama.
Untuk menonaktifkan fitur deduplikasi bawaan ini, kita harus menambahkan parameter allow_duplicates: true di dalam file meta/main.yml milik role yang ingin dijalankan berulang kali tersebut.
Mari kita lihat perbedaan implementasi kodenya:
# roles/vhost/meta/main.yml
# BENAR: Mengizinkan role vhost ini dijalankan berkali-kali dalam satu playbook
---
allow_duplicates: true
dependencies: []
Sekarang, kita bisa memanggil role vhost ini sebagai dependensi beberapa kali di role utama atau langsung di playbook kita tanpa diblokir oleh sistem deduplikasi Ansible:
# playbook.yml
# BENAR: Memanggil role vhost berkali-kali untuk domain yang berbeda
- name: Setup Multi Domain Nginx
hosts: webservers
roles:
- role: vhost
vars:
vhost_domain: "blog.kita.com"
vhost_root: "/var/www/blog"
- role: vhost
vars:
vhost_domain: "shop.kita.com"
vhost_root: "/var/www/shop"
Untuk memperjelas bagaimana Ansible memutuskan apakah akan mengeksekusi suatu role dependensi atau mengabaikannya karena deduplikasi, mari kita pelajari diagram logika keputusan berikut:
flowchart TD
Start["Ansible memproses pemanggilan Role Dependensi"] --> CheckRun{"Apakah Role ini sudah pernah dieksekusi sebelumnya dalam play ini?"}
CheckRun -- "Tidak" --> Execute["Eksekusi Role Dependensi"]
CheckRun -- "Ya" --> CheckDup{"Apakah 'allow_duplicates: true' dideklarasikan di meta/main.yml role tersebut?"}
CheckDup -- "Ya" --> Execute
CheckDup -- "Tidak" --> Skip["Deduplication Aktif: Lewati eksekusi role untuk menghemat waktu"]
Mengelola Dependency Eksternal Menggunakan requirements.yml #
Ketika kita bekerja dalam tim atau mengelola infrastruktur yang kompleks, kita sering kali perlu menggunakan role yang dibuat oleh komunitas (seperti dari Ansible Galaxy) atau role yang dibagikan antar tim internal melalui repositori Git (GitLab/GitHub) perusahaan.
Menyalin kode sumber (source code) role pihak ketiga secara langsung ke dalam direktori roles/ proyek Git kita adalah sebuah anti-pattern yang buruk. Hal ini akan menyebabkan:
- Code Bloat: Repositori Git kita menjadi sangat besar karena menyimpan kode pihak ketiga yang bukan milik kita.
- Kesulitan Upgrade: Sangat sulit untuk memperbarui role tersebut ke versi terbaru jika pembuat aslinya merilis perbaikan bug atau fitur baru.
- Kehilangan Riwayat (History): Kita kehilangan pelacakan versi asli dari role tersebut.
Solusi: requirements.yml #
Solusi terbaik untuk masalah ini adalah dengan menggunakan file deklarasi dependensi eksternal bernama requirements.yml. File ini bertindak seperti package.json di Node.js atau requirements.txt di Python. Kita menulis daftar role eksternal yang kita butuhkan beserta versinya di file ini.
Mari kita lihat contoh penulisan file requirements.yml yang komprehensif:
# requirements.yml
# BENAR: Mendokumentasikan dependensi eksternal dari Galaxy dan Git dengan versi ter-pin
---
roles:
# 1. Mengunduh role dari Ansible Galaxy resmi
- name: geerlingguy.nginx
version: "3.2.0"
- name: geerlingguy.postgresql
version: "3.4.0"
# 2. Mengunduh role dari repositori Git internal perusahaan kita
- name: common-security
src: "[email protected]:badricreativetech/ansible-role-common-security.git"
scm: git
version: "v1.5.2"
# 3. Mengunduh role dari GitLab menggunakan token autentikasi
- name: db-backup
src: "https://gitlab-ci-token:{{ lookup('env', 'GITLAB_TOKEN') }}@gitlab.kita.com/infra/role-db-backup.git"
scm: git
version: "main"
collections:
# Kita juga bisa mendefinisikan dependensi collection Ansible di sini
- name: community.general
version: "8.1.0"
- name: amazon.aws
version: "6.5.0"
Cara Memasang Dependensi #
Setelah kita menulis file requirements.yml di root proyek kita, kita dapat menginstruksikan Ansible untuk mengunduh semua dependensi tersebut sebelum kita menjalankan playbook utama kita.
Gunakan perintah terminal berikut untuk menginstal semua role dan collection yang terdaftar:
# Menginstal semua role dan collection sekaligus ke direktori default
ansible-galaxy install -r requirements.yml
Jika kita ingin mengisolasi instalasi role ke direktori lokal di dalam proyek kita (agar tidak bercampur dengan role global di sistem), kita bisa menggunakan opsi --roles-path:
# Menginstal role langsung ke subdirektori roles lokal proyek kita
ansible-galaxy role install -r requirements.yml --roles-path ./roles
[!IMPORTANT] Selalu Lakukan Pin Versi (Version Pinning)
Sangat krusial bagi kita untuk selalu menentukan parameterversionsecara spesifik direquirements.yml(misalnya"3.2.0"atau tag Git"v1.5.2"). Hindari menggunakan branch dinamis sepertimasterataumainkecuali untuk keperluan development. Jika kita tidak me-pin versi, update otomatis dari pembuat role luar dapat secara tidak sengaja merusak (breaking changes) konfigurasi production kita saat kita melakukan deployment ulang di masa mendatang.
Best Practice vs Anti-Pattern dalam Manajemen Dependency #
Untuk memastikan sistem otomasi kita tetap stabil, mudah dipelihara, dan efisien, berikut adalah tabel perbandingan komprehensif mengenai apa yang harus kita lakukan (best practice) dan apa yang harus kita hindari (anti-pattern) dalam mengelola dependensi role di Ansible:
Kasus Studi Nyata #
Mari kita pelajari contoh kasus yang sering merusak repositori Ansible di lingkungan enterprise:
# roles/roleA/meta/main.yml
# ANTI-PATTERN: Menulis circular dependency yang membuat Ansible mengalami error fatal
---
dependencies:
- role: roleB
# roles/roleB/meta/main.yml
# ANTI-PATTERN: roleB memanggil kembali roleA secara melingkar
---
dependencies:
- role: roleA
Ketika kita mencoba menjalankan playbook yang memanggil salah satu dari role di atas, Ansible akan terjebak dalam loop pencarian dependensi yang tidak pernah berakhir hingga akhirnya memuntahkan pesan error: [ERROR]: CIDETECTED: Circular dependency detected between roleA and roleB.
Solusi yang Benar:
Kita harus menganalisis task apa yang sebenarnya dibutuhkan bersama oleh roleA dan roleB. Pindahkan task bersama tersebut ke dalam role ketiga yang netral (misalnya role_common), lalu buat roleA dan roleB bergantung pada role_common secara linear tanpa saling memanggil satu sama lain.
# roles/roleA/meta/main.yml
# BENAR: Dependensi linear searah ke role dasar
---
dependencies:
- role: role_common
# roles/roleB/meta/main.yml
# BENAR: Dependensi linear searah ke role dasar
---
dependencies:
- role: role_common
Dengan menjaga arah dependensi tetap searah dan tidak melingkar, kita menjamin stabilitas eksekusi playbook kita dan menghindari error kompilasi yang membingungkan.
Ringkasan #
meta/main.ymladalah file deklarasi sentral untuk mendefinisikan dependensi role melalui parameterdependencies:.- Kita dapat mengirimkan
varsspesifik ke role dependensi untuk menyesuaikan perilakunya tanpa memengaruhi cakupan global playbook.- Ansible meresolusi dependensi secara rekursif menggunakan pendekatan depth-first search sebelum mengeksekusi role utama.
- Secara default, Ansible menerapkan deduplikasi agar role dependensi yang sama tidak dijalankan dua kali, gunakan
allow_duplicates: trueuntuk menonaktifkan fitur ini.- Gunakan
requirements.ymluntuk mengelola, mendokumentasikan, dan mengunduh dependensi role eksternal dari Ansible Galaxy atau Git secara terpisah dari repositori proyek kita.- Pin versi dependensi Anda di
requirements.ymlsecara eksplisit guna menjamin stabilitas environment production kita dari perubahan tidak terduga.