Error Handling #
Playbook yang hanya berjalan di kondisi sempurna tidak cukup tangguh untuk mengelola infrastruktur produksi. Di dunia nyata, kita akan menghadapi jaringan yang tidak stabil, repositori paket yang tiba-tiba offline, disk penuh, atau layanan pihak ketiga yang tidak merespons. Jika kita tidak merancang playbook dengan strategi penanganan kesalahan (error handling) yang matang, kegagalan kecil pada satu task dapat menghentikan seluruh proses deployment, membiarkan server target dalam kondisi tidak konsisten (setengah terkonfigurasi), dan menyulitkan proses pemulihan (recovery). Ansible menyediakan berbagai fitur bawaan untuk mendeteksi, mengabaikan, memodifikasi, dan memulihkan diri dari kegagalan secara otomatis guna menjaga integritas sistem kita.
Mengapa Strategi Penanganan Kesalahan Penting #
Ketika Ansible mengeksekusi playbook, perilakunya secara default sangat ketat: jika sebuah task gagal (mengembalikan status non-zero exit code atau mengalami error koneksi), Ansible akan langsung menghentikan semua task selanjutnya untuk host yang gagal tersebut, tetapi tetap melanjutkan eksekusi untuk host lain yang berhasil. Perilaku ini dirancang untuk keamanan agar kita tidak menumpuk konfigurasi baru di atas sistem yang sudah rusak. Namun, dalam skenario otomatisasi yang kompleks seperti deployment aplikasi zero-downtime atau migrasi database, penghentian mendadak ini bisa menjadi bencana jika tidak diantisipasi dengan rollback atau cleanup.
Berikut adalah diagram alir yang memvisualisasikan bagaimana Ansible menangani alur eksekusi saat menghadapi kegagalan task secara default:
flowchart TD
Start["Mulai Playbook"] --> T1["Task 1: Eksekusi Sukses"]
T1 --> T2{"Task 2: Apakah Sukses?"}
T2 -- "Ya" --> T3["Task 3: Lanjutkan Eksekusi"]
T2 -- "Tidak" --> StopHost["Hentikan Eksekusi pada Host yang Gagal"]
T3 --> End["Selesai (Semua Host Up-to-date)"]
StopHost --> FailState["Host Masuk State Gagal (Task Berikutnya Diabaikan)"]
Dengan memahami alur dasar ini, kita bisa mulai menerapkan mekanisme yang lebih fleksibel untuk mengontrol kapan sebuah task dianggap gagal dan bagaimana sistem harus merespons kegagalan tersebut.
Konstruksi block, rescue, dan always #
Ansible menyediakan struktur pengelompokan task yang disebut block. Struktur ini sangat mirip dengan blok penanganan eksepsi try-catch-finally yang biasa kita temukan di bahasa pemrograman modern seperti Java, Python, atau C#. Dengan menggunakan block, kita dapat mengelompokkan beberapa task yang saling terkait secara logis, mendefinisikan langkah pemulihan (rescue) jika salah satu task di dalam block tersebut gagal, dan menentukan task pembersihan (always) yang harus dieksekusi terlepas dari berhasil atau gagalnya block utama.
Menggunakan block untuk Mengelompokkan Task #
Block memungkinkan kita menerapkan direktif tingkat task (seperti become, when, atau vars) ke beberapa task sekaligus secara efisien. Namun, kegunaan utamanya adalah untuk membatasi ruang lingkup kesalahan. Jika ada task di dalam block yang mengembalikan error, Ansible akan menghentikan sisa task di dalam block tersebut dan langsung melompat ke bagian rescue.
Peran rescue dalam Rollback Otomatis #
Bagian rescue hanya akan dieksekusi jika salah satu task di dalam block mengalami kegagalan. Di dalam rescue, kita dapat menempatkan task yang bertugas melakukan rollback, seperti mengembalikan file konfigurasi cadangan, menyalakan kembali service yang sempat mati, atau mengirimkan notifikasi darurat ke tim operasional. Setelah task di dalam rescue selesai dieksekusi dengan sukses, Ansible akan menganggap play tersebut kembali berjalan lancar (tidak dianggap gagal) dan akan melanjutkan eksekusi ke task setelah blok tersebut.
Peran always untuk Cleanup #
Bagian always akan dieksekusi dalam kondisi apa pun: baik ketika semua task di dalam block berjalan sukses, maupun setelah bagian rescue selesai menangani kesalahan. Ini adalah tempat yang sangat ideal untuk menempatkan task pembersihan (cleanup) seperti menghapus file temporary, menutup koneksi database khusus, atau mencatat log audit deployment.
Berikut adalah contoh implementasi penerapan block, rescue, dan always untuk deployment aplikasi yang aman dengan rollback otomatis:
# ANTI-PATTERN: Menjalankan task secara linear tanpa penanganan error.
# Jika deployment gagal di tengah jalan, sistem dibiarkan dalam kondisi rusak/stuck.
- name: Deploy aplikasi secara naif
hosts: app_servers
tasks:
- name: Hentikan service aplikasi
ansible.builtin.systemd:
name: webapp
state: stopped
- name: Deploy kode baru dari Git
ansible.builtin.git:
repo: "[email protected]:kita/webapp.git"
dest: /var/www/webapp
version: release-1.2.0
- name: Jalankan migrasi database
ansible.builtin.command: /var/www/webapp/bin/migrate
# JANGAN: Jika migrasi gagal di sini, service webapp akan tetap mati selamanya
# dan kode baru yang setengah jalan ter-deploy dibiarkan begitu saja.
- name: Jalankan kembali service aplikasi
ansible.builtin.systemd:
name: webapp
state: started
Sekarang kita bandingkan dengan implementasi yang benar menggunakan struktur penanganan kesalahan terstruktur:
# BENAR: Membungkus alur deployment kritis dengan block-rescue-always.
- name: Deploy aplikasi dengan proteksi rollback
hosts: app_servers
vars:
backup_dir: /var/backups/webapp
app_dir: /var/www/webapp
tasks:
- name: Blok Eksekusi Utama Deployment
block:
- name: Buat direktori backup
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
- name: Backup kode aplikasi lama sebelum update
ansible.builtin.archive:
path: "{{ app_dir }}"
dest: "{{ backup_dir }}/app_prev.tar.gz"
format: gz
- name: Hentikan service aplikasi
ansible.builtin.systemd:
name: webapp
state: stopped
- name: Deploy kode baru dari Git
ansible.builtin.git:
repo: "[email protected]:kita/webapp.git"
dest: "{{ app_dir }}"
version: release-1.2.0
- name: Jalankan migrasi database
ansible.builtin.command: "{{ app_dir }}/bin/migrate"
register: migration_result
- name: Jalankan kembali service aplikasi
ansible.builtin.systemd:
name: webapp
state: started
rescue:
- name: Peringatan kegagalan terdeteksi
ansible.builtin.debug:
msg: "Terjadi error pada block utama! Memulai proses rollback otomatis..."
- name: Kembalikan kode aplikasi lama dari backup
ansible.builtin.unarchive:
src: "{{ backup_dir }}/app_prev.tar.gz"
dest: "{{ app_dir }}"
remote_src: true
- name: Jalankan kembali service dengan kode lama (rollback state)
ansible.builtin.systemd:
name: webapp
state: started
- name: Laporkan status kegagalan deployment
ansible.builtin.fail:
msg: "Deployment gagal dan sistem berhasil di-rollback ke versi sebelumnya."
always:
- name: Bersihkan file backup temporary jika ada
ansible.builtin.file:
path: "{{ backup_dir }}/app_prev.tar.gz"
state: absent
- name: Log status akhir deployment ke control node
delegate_to: localhost
ansible.builtin.lineinfile:
path: /var/log/ansible-deploy.log
line: "{{ ansible_date_time.iso8601 }} - Host {{ inventory_hostname }} selesai diproses."
create: true
mode: '0644'
Alur eksekusi dari blok kode di atas digambarkan melalui diagram alir berikut:
flowchart TD
subgraph Blok Utama ("block")
A1["Backup Kode Lama"] --> A2["Stop Service"]
A2 --> A3["Deploy Kode Baru"]
A3 --> A4["Migrasi DB"]
A4 --> A5["Start Service"]
end
subgraph Blok Pemulihan ("rescue")
R1["Kembalikan Kode dari Backup"] --> R2["Start Service Lama"]
R2 --> R3["Picu Kegagalan Playbook"]
end
subgraph Blok Pembersihan ("always")
L1["Hapus File Backup Temp"] --> L2["Tulis Log Deployment"]
end
Start["Mulai Task"] --> A1
A4 -- "Error Terjadi" --> R1
A5 -- "Sukses" --> L1
R3 --> L1
L2 --> End["Selesai"]
failed_when: Kustomisasi Kriteria Kegagalan #
Secara default, Ansible menganggap sebuah task gagal jika program atau modul yang dijalankan mengembalikan exit code selain 0. Namun, kriteria ini tidak selalu sesuai dengan kebutuhan riil operasional kita. Kadang-kadang, sebuah command sukses berjalan dengan exit code 0 tetapi mengeluarkan teks error di stdout yang menandakan operasi gagal. Sebaliknya, ada kalanya program menghasilkan exit code non-zero (misalnya grep mengembalikan exit code 1 jika tidak menemukan kecocokan teks), padahal secara logika alur kerja kita hal tersebut adalah kondisi normal yang ingin kita terima.
Di sinilah kita menggunakan direktif failed_when. Dengan failed_when, kita bisa mengevaluasi status kembalian (registered variable) dari suatu task menggunakan ekspresi logika Jinja2 untuk menentukan secara presisi kapan task tersebut harus dianggap gagal.
Mengidentifikasi Error Melalui Output Teks #
Misalnya, ketika kita menjalankan command cli untuk memeriksa kesehatan API eksternal, cli tersebut mungkin selalu mengembalikan exit code 0 selama jaringannya terhubung, meskipun server merespons dengan "Status: 500 Internal Server Error". Kita bisa memantau variabel stdout dari task tersebut:
# BENAR: Menentukan kegagalan berdasarkan string spesifik di dalam stdout
- name: Periksa kesehatan API eksternal
ansible.builtin.command: curl -s http://api.external.com/health
register: api_check
failed_when:
- "'ERROR' in api_check.stdout"
- "'500' in api_check.stdout"
changed_when: false
Menangani Toleransi Exit Code Tertentu #
Jika kita menggunakan utility seperti pg_isready untuk memeriksa status Postgres, kita tahu bahwa tool ini dapat mengembalikan exit code 1 jika server sedang memproses inisialisasi awal. Jika kita ingin menganggap exit code 1 sebagai hal yang aman dan hanya menganggap gagal jika exit code bernilai 2 (fatal error), kita bisa menggunakan failed_when secara fleksibel:
# BENAR: Memodifikasi toleransi exit code
- name: Periksa kesiapan database PostgreSQL
ansible.builtin.command: pg_isready -h localhost -p 5432
register: pg_status
failed_when: "pg_status.rc == 2"
changed_when: false
Mari kita bandingkan anti-pattern yang sering dilakukan developer dengan solusi yang lebih elegan menggunakan failed_when.
# ANTI-PATTERN: Menggunakan task tambahan dengan modul 'fail' untuk memeriksa status.
# Ini membuat kode sangat bertele-tele dan menghasilkan task tambahan di output logs.
- name: Jalankan query database secara naif
ansible.builtin.command: mysql -e "SELECT * FROM users;"
register: db_query
ignore_errors: true
- name: Validasi hasil query secara manual
ansible.builtin.fail:
msg: "Query database gagal dieksekusi!"
when: db_query.rc != 0 or 'Connection refused' in db_query.stderr
# BENAR: Menggabungkan validasi kegagalan langsung di dalam task menggunakan failed_when.
- name: Jalankan query database dengan efisien
ansible.builtin.command: mysql -e "SELECT * FROM users;"
register: db_query_optimal
failed_when:
- db_query_optimal.rc != 0 or 'Connection refused' in db_query_optimal.stderr
changed_when: false
ignore_errors: Lanjutkan Meski Gagal #
Direktif ignore_errors: true memberi tahu Ansible bahwa jika task ini mengalami kegagalan, abaikan saja dan lanjutkan ke task berikutnya seolah-olah tidak terjadi apa-apa. Hal ini berguna untuk task-task opsional yang sifatnya non-blocking, seperti membersihkan cache direktori opsional yang barangkali sudah dihapus, atau mencoba mengirim statistik opsional ke server log internal.
Penyalahgunaan ignore_errors: true adalah bahaya besar! Jangan menggunakannya sebagai jalan pintas untuk menyembunyikan konfigurasi yang salah atau error sistem yang tidak dimengerti. Jika kita mengabaikan error kritis (seperti kegagalan mounting disk atau kegagalan instalasi pustaka keamanan), playbook akan terus berjalan dan pada akhirnya menyebabkan sistem runtuh di task berikutnya tanpa indikasi error yang jelas di awal.
Alternatif Lebih Baik daripada ignore_errors #
Sebagian besar penggunaan ignore_errors sebenarnya dapat digantikan dengan pengecekan state menggunakan modul stat atau memeriksa kondisi dengan when.
Sebagai contoh, jika kita ingin menghapus file konfigurasi lama tetapi tidak ingin task gagal jika file tersebut memang sudah tidak ada, kita bisa menggunakan pendekatan modular daripada mengabaikan error secara buta:
# ANTI-PATTERN: Menggunakan ignore_errors untuk operasi file yang tidak pasti.
- name: Hapus file konfigurasi lama jika ada
ansible.builtin.file:
path: /etc/nginx/conf.d/old_site.conf
state: absent
ignore_errors: true # JANGAN: Jika ini error karena masalah permission disk, kita tidak akan tahu.
# BENAR: Memeriksa keberadaan file terlebih dahulu sebelum melakukan aksi.
- name: Periksa apakah file konfigurasi lama ada
ansible.builtin.stat:
path: /etc/nginx/conf.d/old_site.conf
register: old_site_file
- name: Hapus file konfigurasi jika terbukti ada
ansible.builtin.file:
path: /etc/nginx/conf.d/old_site.conf
state: absent
when: old_site_file.stat.exists
Dengan cara ini, jika terjadi masalah kegagalan sistem berkas sesungguhnya (seperti permission denied pada direktori /etc/nginx/), Ansible akan memunculkan error secara tepat sehingga kita bisa mendeteksi adanya malkonfigurasi hak akses di server.
force_handlers: Menjamin Pemeliharaan State #
Ketika sebuah task mengubah state sistem (seperti memperbarui template konfigurasi), task tersebut biasanya akan memicu trigger notify ke handler (misalnya "Restart Nginx"). Namun, sesuai aturan bawaan Ansible, semua handler yang terpicu baru akan dieksekusi di akhir bagian play. Masalah muncul jika di tengah-tengah eksekusi task setelah pemicuan template terjadi kegagalan sistem.
Secara default, jika sebuah play gagal di tengah jalan, Ansible akan mengabaikan seluruh handler yang dijadwalkan berjalan di akhir play. Akibatnya, server kita bisa berada dalam kondisi tidak konsisten: file konfigurasi baru sudah tertulis ke disk, tetapi service belum di-restart untuk memuat konfigurasi baru tersebut. Ini sangat berbahaya karena jika server di-restart di kemudian hari secara manual, service mungkin gagal menyala akibat konfigurasi baru yang tidak tervalidasi.
Menggunakan force_handlers #
Untuk mencegah skenario ini, kita dapat menetapkan direktif force_handlers: true di tingkat play. Direktif ini memaksa Ansible untuk tetap mengeksekusi semua handler yang sudah ternotifikasi, bahkan jika eksekusi playbook terhenti akibat kegagalan pada task setelahnya.
Kita bisa mengonfigurasi ini di dalam playbook kita:
# BENAR: Menetapkan force_handlers di tingkat play untuk menjamin konsistensi service
- name: Kelola konfigurasi server web webapp
hosts: web_servers
force_handlers: true # ✓ Menjamin semua handler yang ternotifikasi tetap dijalankan meski ada task berikutnya yang gagal
tasks:
- name: Update file konfigurasi Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: Restart Nginx Service
- name: Jalankan task verifikasi aset eksternal
ansible.builtin.command: /usr/local/bin/verify-assets.sh
# Jika task verifikasi ini gagal, handler 'Restart Nginx Service' akan tetap dieksekusi oleh Ansible
# sehingga perubahan konfigurasi Nginx di atas tidak ditinggalkan menggantung tanpa restart.
handlers:
- name: Restart Nginx Service
ansible.builtin.systemd:
name: nginx
state: restarted
Selain mendefinisikannya di tingkat playbook, kita juga bisa memaksa pengaturan ini secara global di dalam file konfigurasi ansible.cfg kita:
# ansible.cfg
[defaults]
# Mengaktifkan force_handlers secara default untuk seluruh playbook
force_handlers = True
any_errors_fatal dan max_fail_percentage untuk Skala Besar #
Ketika mengelola puluhan atau ratusan server sekaligus, mengizinkan eksekusi playbook berlanjut di host yang sehat sementara beberapa host lainnya gagal bisa menjadi keputusan yang kurang tepat. Terutama dalam proses deployment arsitektur microservices yang saling bergantung, atau saat melakukan update skema database terdistribusi. Kegagalan pada satu atau sebagian kecil node dapat merusak konsistensi cluster secara keseluruhan.
Ansible menyediakan dua cara canggih untuk mengontrol batas toleransi kesalahan di infrastruktur berskala besar: any_errors_fatal dan max_fail_percentage.
any_errors_fatal: Kematian Atomik #
Jika kita menyetel any_errors_fatal: true pada sebuah play, Ansible akan menghentikan seluruh eksekusi playbook secara instan di semua host aktif begitu ada satu host saja yang mengalami kegagalan task. Ini sangat krusial digunakan pada tahap persiapan awal (pre-tasks) atau ketika menjalankan migrasi database utama di mana kegagalan satu node harus membatalkan seluruh proses deployment demi menjaga integritas data global.
max_fail_percentage: Toleransi Batas Ambang #
Jika kita melakukan rolling update secara bertahap pada kluster web server yang besar (misalnya menggunakan direktif serial), menghentikan seluruh deployment hanya karena satu server mengalami kegagalan kecil mungkin terlalu sensitif. Kita bisa menentukan batas persentase kegagalan maksimum menggunakan max_fail_percentage. Selama jumlah server yang gagal masih di bawah persentase batas tersebut, Ansible akan terus melanjutkan eksekusi ke kelompok server berikutnya.
Berikut adalah ilustrasi komparatif penggunaannya dalam satu playbook:
# BENAR: Menggunakan any_errors_fatal pada setup database dan max_fail_percentage pada rolling update webapp.
- name: Inisialisasi Database Kritis
hosts: database_cluster
any_errors_fatal: true # ✓ Hentikan semua database node jika satu node saja gagal saat bootstrap
tasks:
- name: Verifikasi replikasi database aktif
ansible.builtin.command: check_replication_status.sh
changed_when: false
- name: Rolling Update Kluster Web Server
hosts: web_servers
serial: 10% # Proses update 10% host sekaligus secara bertahap
max_fail_percentage: 20 # ✓ Izinkan proses lanjut selama server yang gagal tidak melebihi 20% dari total host
tasks:
- name: Pasang pembaruan paket keamanan webapp
ansible.builtin.apt:
name: webapp-package
state: latest
Diagram alir berikut mendeskripsikan bagaimana keputusan eksekusi diambil berdasarkan kriteria max_fail_percentage:
flowchart TD
Start["Mulai Batch Baru (Serial 10%)"] --> Exec["Eksekusi Task di Batch Saat Ini"]
Exec --> Eval{"Apakah Ada Node Gagal?"}
Eval -- "Tidak" --> BatchSuccess["Batch Sukses"]
Eval -- "Ya" --> Calc{"Apakah Persentase Kegagalan > max_fail_percentage (20%)?"}
Calc -- "Ya" --> Terminate["Hentikan Playbook Secara Global (Fatal Error)"]
Calc -- "Tidak" --> BatchSuccess
BatchSuccess --> CheckNext{"Apakah Ada Batch Tersisa?"}
CheckNext -- "Ya" --> Start
CheckNext -- "Nu" --> End["Semua Batch Selesai Diproses"]
Pola Desain Rollback Otomatis di Produksi #
Untuk membangun automasi infrastruktur tingkat enterprise yang mandiri (self-healing), kita tidak boleh hanya mengandalkan deteksi error dasar. Kita harus menyusun playbook kita dengan arsitektur penanganan kegagalan yang lengkap, mencakup pengumpulan metrik status sebelum eksekusi (pre-flight checks), penyimpanan state versi sebelumnya secara dinamis, deployment aman menggunakan blok transaksi, verifikasi kesehatan pasca-deployment (health checks), serta pemulihan otomatis (rollback) jika verifikasi tersebut gagal.
Di bawah ini adalah contoh rancangan playbook produksi yang menerapkan semua konsep tersebut secara komprehensif:
# BENAR: Pola desain deployment aplikasi web terintegrasi dengan rollback otomatis dan verifikasi status kesehatan
---
- name: Deployment Mandiri dengan Sistem Pemulihan Otomatis
hosts: web_servers
become: true
force_handlers: true # Memastikan handler tetap berjalan untuk merapikan service
vars:
app_root: /opt/production_app
backup_root: /opt/backups_app
app_version_target: "v2.1.0"
health_check_url: "http://127.0.0.1:8080/health"
pre_tasks:
- name: Kumpulkan informasi versi aplikasi saat ini
ansible.builtin.slurp:
src: "{{ app_root }}/version.txt"
register: current_version_raw
ignore_errors: true
- name: Definisikan versi cadangan untuk rollback
ansible.builtin.set_fact:
app_version_previous: "{{ (current_version_raw.content | b64decode | trim) if current_version_raw.content is defined else 'none' }}"
- name: Tampilkan informasi versi terdeteksi
ansible.builtin.debug:
msg: "Mendeteksi aplikasi versi lama: {{ app_version_previous }}. Target update: {{ app_version_target }}"
tasks:
- name: Blok Transaksi Deployment Aplikasi
block:
- name: Buat direktori backup jika belum ada
ansible.builtin.file:
path: "{{ backup_root }}"
state: directory
mode: '0700'
- name: Backup direktori aplikasi saat ini jika versi sebelumnya ada
ansible.builtin.archive:
path: "{{ app_root }}"
dest: "{{ backup_root }}/app_backup_{{ app_version_previous }}.tar.gz"
format: gz
when: app_version_previous != 'none'
- name: Hentikan aplikasi lama secara aman
ansible.builtin.systemd:
name: production_app
state: stopped
- name: Bersihkan direktori aplikasi lama untuk mempersiapkan kode baru
ansible.builtin.file:
path: "{{ app_root }}"
state: absent
- name: Buat ulang direktori aplikasi kosong
ansible.builtin.file:
path: "{{ app_root }}"
state: directory
mode: '0755'
- name: Unduh dan ekstrak paket aplikasi versi baru
ansible.builtin.unarchive:
src: "https://artifactory.kita.internal/apps/release-{{ app_version_target }}.tar.gz"
dest: "{{ app_root }}"
remote_src: true
- name: Tulis versi baru ke file identitas aplikasi
ansible.builtin.copy:
content: "{{ app_version_target }}"
dest: "{{ app_root }}/version.txt"
mode: '0644'
- name: Jalankan pembaruan modul aplikasi
ansible.builtin.command: npm install --production
args:
chdir: "{{ app_root }}"
- name: Jalankan kembali service aplikasi dengan kode baru
ansible.builtin.systemd:
name: production_app
state: started
enabled: true
- name: Tunggu aplikasi melakukan booting awal
ansible.builtin.pause:
seconds: 5
- name: Uji kesehatan aplikasi (Health Check pasca deployment)
ansible.builtin.uri:
url: "{{ health_check_url }}"
status_code: 200
register: health_status
until: health_status.status == 200
retries: 5
delay: 3
# Jika request API health check ini gagal dalam 5 kali percobaan,
# task ini dianggap failed dan akan memicu eksekusi bagian rescue secara otomatis.
rescue:
- name: Peringatan Kritis - Uji kesehatan gagal! Memulai pemulihan sistem...
ansible.builtin.debug:
msg: "Kegagalan deployment pada host {{ inventory_hostname }}. Memulai pemulihan otomatis ke versi {{ app_version_previous }}."
- name: Jalankan Rollback jika versi lama tersedia
block:
- name: Hentikan service yang gagal berjalan
ansible.builtin.systemd:
name: production_app
state: stopped
- name: Hapus direktori kode aplikasi yang rusak
ansible.builtin.file:
path: "{{ app_root }}"
state: absent
- name: Buat kembali direktori aplikasi
ansible.builtin.file:
path: "{{ app_root }}"
state: directory
mode: '0755'
- name: Ekstrak kembali file cadangan versi lama
ansible.builtin.unarchive:
src: "{{ backup_root }}/app_backup_{{ app_version_previous }}.tar.gz"
dest: "{{ app_root }}"
remote_src: true
- name: Hidupkan kembali service aplikasi dengan versi lama yang stabil
ansible.builtin.systemd:
name: production_app
state: started
# Kita hanya menjalankan rescue block ini jika memang ada backup versi lama yang berhasil disimpan
when: app_version_previous != 'none'
- name: Laporkan kesalahan deployment akhir ke dashboard pemantau
ansible.builtin.fail:
msg: "Deployment ke versi {{ app_version_target }} gagal di host {{ inventory_hostname }}. Sistem telah dipulihkan ke versi {{ app_version_previous }}."
always:
- name: Bersihkan arsip cadangan lama untuk menghemat kapasitas penyimpanan server
ansible.builtin.file:
path: "{{ backup_root }}/app_backup_{{ app_version_previous }}.tar.gz"
state: absent
when: app_version_previous != 'none'
Melalui kerangka kerja di atas, infrastruktur kita tidak akan pernah dibiarkan dalam keadaan mati atau tidak berfungsi. Keberadaan block-rescue memastikan bahwa kegagalan instalasi modul dependencies (npm install) atau kegagalan booting port aplikasi (health check) akan segera direspon dengan memulihkan folder kode lama dari arsip tarball dan menyalakan kembali daemon aplikasi yang stabil.
Ringkasan #
block / rescue / alwaysmerupakan struktur fondasi try-catch-finally dalam Ansible untuk mengelompokkan task kritis dan mengeksekusi aksi pemulihan (rollback) otomatis ketika terjadi kesalahan.failed_whenmemungkinkan kita mendefinisikan aturan kegagalan task sendiri secara presisi menggunakan ekspresi Jinja2, alih-alih hanya mengandalkan exit code bernilai non-zero.ignore_errorsharus digunakan dengan sangat hati-hati dan selektif; hindari menggunakannya secara membabi buta untuk menutupi kesalahan konfigurasi yang sesungguhnya.force_handlers: truemenjamin semua handler (seperti restart service) yang ternotifikasi tetap dijalankan oleh Ansible meskipun ada task berikutnya dalam play yang mengalami error.any_errors_fatal: trueberguna untuk skenario deployment atomik di mana kegagalan pada satu node harus membatalkan eksekusi playbook secara global demi keamanan data.max_fail_percentagemenetapkan ambang batas toleransi kegagalan pada infrastruktur skala besar, memungkinkan deployment terus berlanjut jika jumlah server yang gagal masih di bawah batas toleransi.- Arsitektur deployment produksi yang baik selalu menyertakan langkah pre-flight checks untuk menyimpan state lama sebelum melakukan modifikasi berkas, sehingga proses rollback dapat berjalan secara akurat.
← Sebelumnya: Scheduled Task Berikutnya: Delegation & Local Action →