Rollback Strategy #
Deployment yang tidak bisa di-rollback adalah deployment yang belum siap untuk production. Tidak peduli seberapa banyak testing yang dilakukan, selalu ada kemungkinan masalah yang hanya muncul saat kode berjalan di production dengan traffic nyata — query plan database yang berbeda di data riil, network latency antar region, atau race condition yang hanya muncul di bawah load. Kemampuan untuk kembali ke versi yang bekerja dengan cepat dan andal adalah properti sistem yang tidak boleh menjadi afterthought — ia harus dirancang dari awal. Artikel ini membahas strategi rollback yang efektif menggunakan Ansible, dari automatic rollback saat health check gagal, rollback database dengan expand-contract, hingga blue-green dan canary untuk rollback instan.
Deployment Lifecycle States #
Sebelum membahas strategi rollback, penting untuk memahami state-state yang dilalui sebuah deployment selama hidupnya. Visualisasi dalam bentuk state diagram membantu kita melihat di titik mana rollback masuk sebagai transisi:
stateDiagram-v2
[*] --> Idle
Idle --> Deploying : "deploy start"
Deploying --> Healthy : "health check pass"
Deploying --> Degraded : "health check fail"
Healthy --> Degraded : "alert triggered"
Degraded --> RollingBack : "rollback trigger"
RollingBack --> Healthy : "previous version verified"
Healthy --> [*]
RollingBack --> Failed : "rollback error"
Failed --> Manual : "operator takeover"
Manual --> [*]
Idle --> Idle : "rollback within window"
Diagram ini memperlihatkan empat state kritis: Deploying (transisi), Healthy (steady state sukses), Degraded (deteksi masalah), dan RollingBack (transisi recovery). Rollback adalah transisi terkontrol dari Degraded kembali ke Healthy dengan cara me-revert ke versi sebelumnya. Tanpa state machine yang jelas, kita tidak tahu kapan harus melakukan rollback dan kapan harus melakukan forward-fix.
State Degraded biasanya dipicu oleh salah satu dari: health check gagal dalam window observasi (biasanya 5–15 menit setelah deploy), error rate naik di atas threshold (misal >1% selama 5 menit berturut), atau latency p99 naik signifikan. Threshold ini harus didefinisikan secara eksplisit, bukan perasaan subjektif.
Menyimpan State untuk Rollback #
Langkah pertama rollback yang efektif adalah memastikan informasi yang diperlukan tersedia. Sebelum deployment versi baru dimulai, kita harus mengetahui dengan pasti: apa versi yang sedang berjalan, kapan versi tersebut di-deploy, dan bagaimana cara melakukan revert.
# playbooks/deploy.yml
---
- name: Deploy dengan kemampuan rollback
hosts: appservers
vars:
deploy_version: "{{ version | mandatory }}"
pre_tasks:
- name: Catat versi yang sedang berjalan
command: cat /opt/app/VERSION
register: current_version_file
changed_when: false
ignore_errors: true
- name: Simpan versi saat ini ke variabel rollback
set_fact:
rollback_version: "{{ current_version_file.stdout | default('unknown') | trim }}"
- name: Simpan rollback info ke file
copy:
content: |
version={{ rollback_version }}
deployed_at={{ ansible_date_time.iso8601 }}
deployed_by={{ lookup('env', 'USER') | default('ci-pipeline') }}
dest: /opt/app/PREVIOUS_VERSION
mode: '0644'
- name: Log deployment dimulai
lineinfile:
path: /var/log/deployments.log
line: "{{ ansible_date_time.iso8601 }} START v{{ deploy_version }} (rollback: v{{ rollback_version }}) @ {{ inventory_hostname }}"
create: true
delegate_to: localhost
File PREVIOUS_VERSION adalah rekaman sederhana tapi kritis. Saat deployment gagal dan tim panik mencari tahu “kita harus revert ke mana?”, jawaban yang tersedia di server itu sendiri jauh lebih cepat daripada harus menelusuri history commit atau bertanya ke Slack. Lebih penting lagi, file ini ditulis di awal deployment — saat server masih dalam kondisi sehat, bukan saat kondisinya sudah kacau.
Log deployment ke file terpusat (di delegate_to: localhost) memberikan audit trail global. Saat insiden, kita bisa dengan cepat melihat: deployment apa yang dilakukan 10 menit terakhir, oleh siapa, di server mana. Ini memotong waktu investigasi dari hitungan jam menjadi menit.
Rollback Otomatis Saat Health Check Gagal #
Kegagalan health check bukan situasi yang perlu di-intervensi manual. Jika kita bisa mendeteksinya, kita juga harus bisa meresponsnya secara otomatis. block/rescue di Ansible memungkinkan ini dengan elegan: jika bagian block gagal, bagian rescue dieksekusi untuk recovery, dan always dieksekusi untuk logging — apa pun yang terjadi.
tasks:
- block:
- name: Deploy versi baru
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "v{{ deploy_version }}"
- name: Install dependencies
pip:
requirements: /opt/app/requirements.txt
virtualenv: /opt/app/venv
- name: Restart aplikasi
systemd:
name: myapp
state: restarted
- name: Tunggu aplikasi siap (health check)
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health_check
until: health_check.status == 200
retries: 12
delay: 10
- name: Verifikasi versi yang berjalan
uri:
url: "http://localhost:{{ app_port }}/api/version"
return_content: true
register: version_check
failed_when: deploy_version not in version_check.content
- name: Tulis VERSION file baru
copy:
content: "{{ deploy_version }}\n"
dest: /opt/app/VERSION
rescue:
- name: Health check gagal — memulai rollback otomatis
debug:
msg: >
Deployment v{{ deploy_version }} gagal di {{ inventory_hostname }}.
Melakukan rollback ke v{{ rollback_version }}.
- name: Rollback ke versi sebelumnya
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "v{{ rollback_version }}"
when: rollback_version != 'unknown'
- name: Restart dengan versi lama
systemd:
name: myapp
state: restarted
when: rollback_version != 'unknown'
- name: Verifikasi rollback berhasil
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
retries: 6
delay: 10
- name: Gagalkan play untuk memberitahu pipeline
fail:
msg: >
Deployment v{{ deploy_version }} gagal dan rollback ke
v{{ rollback_version }} sudah dilakukan. Periksa log untuk detail.
always:
- name: Log hasil deployment
lineinfile:
path: /var/log/deployments.log
line: >
{{ ansible_date_time.iso8601 }}
{{ 'ROLLBACK' if ansible_failed_result is defined else 'SUCCESS' }}
v{{ deploy_version }} @ {{ inventory_hostname }}
create: true
delegate_to: localhost
Sequence diagram menggambarkan alur ini dengan jelas:
sequenceDiagram
participant Pipe as "CI Pipeline"
participant Ans as "Ansible"
participant Srv as "Server"
participant Reg as "Registry"
Pipe->>Ans: "deploy v2.1.0"
Ans->>Srv: "pull source v2.1.0"
Ans->>Srv: "restart myapp"
Ans->>Srv: "GET /health"
Srv-->>Ans: "503 Service Unavailable"
Ans->>Srv: "GET /health (retry 1)"
Srv-->>Ans: "503"
Ans->>Srv: "GET /health (retry 2)"
Srv-->>Ans: "503"
Note over Ans: "retries exhausted"
Ans->>Srv: "checkout v2.0.5 (rollback)"
Ans->>Srv: "restart myapp"
Ans->>Srv: "GET /health"
Srv-->>Ans: "200 OK"
Ans-->>Pipe: "fail (rollback successful)"
Pipe->>Pipe: "notify team, page oncall"
failed_when: deploy_version not in version_check.content adalah sanity check tambahan. Kadang health check bisa mengembalikan status 200 (misalnya server lama masih berjalan atau aplikasi tidak melakukan restart dengan benar). Dengan memverifikasi bahwa endpoint /api/version mengembalikan versi yang baru di-deploy, kita memastikan deployment benar-benar terjadi, bukan silent failure.
Health check yang terlalu permisif (mengembalikan status 200 selama server merespons) bisa mengelabui sistem. Health check harus menguji fungsionalitas kritis: database connection, downstream API call, atau business logic utama. Health check yang hanya mengembalikan {"status": "ok"} tanpa tes apa pun tidak memberikan informasi apakah aplikasi benar-benar bekerja.
ANTI-PATTERN: No Rollback Plan vs Automated Rollback #
Bayangkan skenario ini: deploy ke production dilakukan pada Jumat sore, health check gagal 10 menit kemudian, dan tidak ada playbook rollback. Engineer oncall harus menelusuri Git history untuk mencari commit terakhir yang stabil, lalu men-debug cara revert karena merge conflict. Proses ini memakan waktu 45 menit. Selama 45 menit itu, production down dan revenue hilang. Situasi seperti ini sepenuhnya bisa dihindari.
# ANTI-PATTERN: deploy tanpa rencana rollback
# playbooks/deploy-naive.yml
---
- name: Deploy tanpa rollback capability
hosts: appservers
tasks:
- name: Pull latest code
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "{{ branch }}" # Bisa apa saja, bahkan commit setengah jadi
- name: Restart
systemd:
name: myapp
state: restarted
# Done. Tidak ada health check, tidak ada rollback plan,
# tidak ada audit trail. Semoga berhasil.
Masalahnya: deployment di atas “berhasil” dari sudut pandang Ansible (tasks selesai), tapi tidak ada verifikasi bahwa aplikasi benar-benar berjalan. Jika gagal, engineer harus melakukan revert secara manual dari memori atau Git log. Tidak ada otomatisasi, tidak ada cara cepat untuk kembali ke kondisi sebelumnya.
# BENAR: deploy dengan rollback capability penuh
# playbooks/deploy-safe.yml (seperti di atas, diringkas)
---
- name: Deploy dengan rollback capability penuh
hosts: appservers
vars:
deploy_version: "{{ version | mandatory }}"
pre_tasks:
- name: Catat versi sebelumnya
command: cat /opt/app/VERSION
register: current_version
changed_when: false
ignore_errors: true
- set_fact:
rollback_version: "{{ current_version.stdout | default('unknown') | trim }}"
tasks:
- block:
- name: Pull dan restart
# ... deployment tasks ...
- name: Health check
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
retries: 12
delay: 5
- name: Update VERSION
copy:
content: "{{ deploy_version }}\n"
dest: /opt/app/VERSION
rescue:
- name: Rollback otomatis ke v{{ rollback_version }}
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "v{{ rollback_version }}"
- name: Restart
systemd:
name: myapp
state: restarted
always:
- name: Log ke centralized log
lineinfile:
path: /var/log/deployments.log
line: "{{ ansible_date_time.iso8601 }} {{ 'ROLLBACK' if ansible_failed_result is defined else 'SUCCESS' }} v{{ deploy_version }}"
create: true
Perbedaan krusial: versi sebelumnya selalu dicatat sebelum deployment baru, health check wajib dijalankan, dan rollback terjadi secara otomatis dalam rescue block jika health check gagal. Saat deploy gagal, playbook tahu persis ke mana harus melakukan revert, tanpa harus bertanya atau menebak.
Rollback Manual via Pipeline #
Otomatisasi tidak menghilangkan kebutuhan rollback manual. Ada situasi di mana rollback otomatis terlalu berisiko (misalnya rollback otomatis bisa menghilangkan data yang sudah masuk, atau perlu investigasi mendalam sebelum melakukan revert). Untuk itu, kita harus menyediakan playbook rollback terpisah yang bisa dipicu dari pipeline.
# playbooks/rollback.yml
---
- name: Rollback deployment manual
hosts: appservers
vars:
# target_version harus dipass: -e target_version=2.0.5
target_version: "{{ target_version | mandatory }}"
pre_tasks:
- name: Konfirmasi versi yang akan di-rollback-ke tersedia di registry
command: "docker manifest inspect registry.company.com/myapp:{{ target_version }}"
delegate_to: localhost
changed_when: false
- name: Catat versi yang sedang berjalan (sebelum rollback)
command: cat /opt/app/VERSION
register: pre_rollback_version
changed_when: false
ignore_errors: true
tasks:
- name: Log rollback dimulai
debug:
msg: >
Rollback dari v{{ pre_rollback_version.stdout | default('unknown') }}
ke v{{ target_version }}
- name: Pull image target rollback
community.docker.docker_image:
name: "registry.company.com/myapp:{{ target_version }}"
source: pull
- name: Jalankan container versi rollback
community.docker.docker_container:
name: myapp
image: "registry.company.com/myapp:{{ target_version }}"
state: started
restart_policy: unless-stopped
recreate: true
- name: Tunggu aplikasi siap
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
retries: 12
delay: 5
- name: Update VERSION file
copy:
content: "{{ target_version }}\n"
dest: /opt/app/VERSION
- name: Log rollback selesai
debug:
msg: "Rollback berhasil ke v{{ target_version }}"
Playbook rollback terpisah memungkinkan engineer oncall melakukan revert bahkan di tengah malam tanpa harus mengingat detail deployment. Cukup jalankan perintah ansible-playbook -i inventory/production/ rollback.yml -e target_version=2.0.5 dan tidur lagi. Yang penting: playbook rollback harus independen — tidak boleh bergantung pada state atau variabel dari playbook deploy.
Rollback Database: Masalah yang Berbeda #
Rollback kode cenderung mudah: ganti image atau binary, restart service, selesai dalam hitungan menit. Rollback database schema jauh lebih kompleks karena data bersifat persistent — kolom yang di-drop akan menghapus data, tipe yang diubah bisa merusak constraint secara permanen.
Prinsip utama: kode bisa di-rollback dengan mudah, database tidak bisa. Rancang migrasi yang tidak memerlukan rollback database.
# Prinsip untuk database yang bisa di-rollback:
#
# 1. Backward-compatible migrations — kode lama harus bisa berjalan
# dengan schema baru SEBELUM rollback dilakukan
#
# 2. Pisahkan migrasi destructive — hapus kolom/tabel di migration terpisah,
# bukan bersamaan dengan tambah kolom baru
#
# 3. Expand-Contract pattern:
# Fase 1 (Expand): Tambah kolom baru, deploy kode baru yang menulis ke KEDUA kolom
# Fase 2 (Contract): Hapus kolom lama setelah verifikasi tidak ada yang butuh
#
# Jika migrasi down tersedia (Django, Alembic, dll.):
- name: Rollback database migration
command: "python manage.py migrate {{ app_name }} {{ target_migration }}"
args:
chdir: /opt/app
when:
- rollback_db | default(false) | bool
- target_migration is defined
Expand-Contract pattern adalah satu-satunya cara yang aman untuk perubahan database schema yang signifikan. Visualisasinya:
flowchart TD
A["State Awal: kolom_old"] --> B["Tambah kolom_new"]
B --> C["Deploy kode: tulis ke KEDUA kolom"]
C --> D["Backfill data dari kolom_old ke kolom_new"]
D --> E{"Verifikasi data konsisten?"}
E -- "Ya" --> F["Deploy kode: baca dari kolom_new"]
F --> G["Deploy kode: stop tulis ke kolom_old"]
G --> H["Hapus kolom_old di migration terpisah"]
E -- "Tidak" --> I["Investigasi, fix, ulang backfill"]
Dalam fase Expand, kode baru ditulis untuk menulis ke kedua kolom (lama dan baru) sehingga aplikasi versi lama masih bisa berjalan. Setelah semua kode diperbarui dan data di-backfill, baru di fase Contract kolom lama di-drop. Jika di tengah jalan deployment di-rollback, kode lama masih bisa membaca dari kolom lama, dan kolom baru masih ada (meskipun belum terisi lengkap) — tidak ada data loss.
Jangan pernah menggabungkan “tambah kolom” dan “drop kolom” dalam satu deployment. Jika kode yang di-deploy gagal di tengah jalan, kita bisa kehilangan data karena kolom sudah di-drop sedangkan kode lama yang di-rollback tidak tahu harus menyimpan data ke mana. Selalu pisahkan ke dalam minimal dua deployment: satu expand, satu contract.
Blue-Green Deployment untuk Rollback Instan #
Blue-green adalah strategi deployment yang memungkinkan rollback dengan mengganti pointer, bukan mengulang deployment. Dua environment paralel (blue dan green), hanya satu yang menerima traffic. Saat deploy, ganti traffic ke slot yang baru. Saat rollback, ganti kembali ke slot lama — selesai dalam hitungan detik.
# playbooks/blue-green-deploy.yml
---
- name: Blue-Green Deployment
hosts: loadbalancer
vars:
current_slot: "{{ lookup('file', '/etc/app/active-slot') | default('blue') }}"
new_slot: "{{ 'green' if current_slot == 'blue' else 'blue' }}"
tasks:
- name: Deploy ke slot yang tidak aktif ({{ new_slot }})
include_tasks: deploy-to-slot.yml
vars:
slot: "{{ new_slot }}"
- name: Verifikasi slot baru sehat
uri:
url: "http://{{ new_slot }}.internal:{{ app_port }}/health"
status_code: 200
retries: 12
delay: 5
- name: Switch traffic ke slot baru
template:
src: nginx-upstream.conf.j2
dest: /etc/nginx/conf.d/upstream.conf
vars:
active_slot: "{{ new_slot }}"
notify: Reload nginx
- name: Tunggu reload nginx
meta: flush_handlers
- name: Simpan slot aktif
copy:
content: "{{ new_slot }}\n"
dest: /etc/app/active-slot
- name: Instruksi rollback instan jika diperlukan
debug:
msg: >
Deployment berhasil ke slot {{ new_slot }}.
Untuk rollback instan: ansible-playbook rollback-blue-green.yml
(mengembalikan traffic ke slot {{ current_slot }} tanpa re-deploy)
State diagram untuk blue-green terlihat seperti ini:
stateDiagram-v2
[*] --> BlueActive
BlueActive --> BlueDeployingGreen : "deploy"
BlueDeployingGreen --> GreenVerifying : "deploy done"
GreenVerifying --> GreenActive : "health check pass"
GreenVerifying --> BlueActive : "health check fail"
GreenActive --> BlueActive : "rollback (instant)"
GreenActive --> GreenActive : "monitor"
Keuntungan terbesar blue-green: rollback adalah switch pointer, bukan re-deploy. Dari detik ke detik, traffic berpindah dari slot A ke slot B. Saat rollback, traffic berpindah kembali dari B ke A — selesai dalam waktu yang dibutuhkan load balancer untuk melakukan reload konfigurasi (biasanya < 5 detik dengan nginx). Tidak ada download image, tidak ada migrasi, tidak ada risiko tambahan.
Trade-off: membutuhkan 2x resource karena adanya dua environment paralel. Untuk sistem kecil atau staging yang tidak mission-critical, ini adalah pemborosan. Untuk production dengan SLA ketat, blue-green adalah standar industri.
ANTI-PATTERN: Only Forward-Fix vs Bidirectional Recovery #
Ada dua kubu ekstrem dalam rollback: kubu “selalu forward-fix” yang menolak revert karena percaya bug bisa di-patch dalam hitungan menit, dan kubu “rollback berlebihan” yang melakukan revert pada masalah terkecil tanpa investigasi. Keduanya suboptimal.
# ANTI-PATTERN: hanya forward-fix, tidak ada rollback
# playbooks/deploy-no-rollback.yml
---
- name: Deploy tanpa rollback
hosts: appservers
tasks:
- name: Deploy versi baru
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "{{ version }}"
- name: Restart
systemd:
name: myapp
state: restarted
# Selesai. Tidak ada rollback plan.
# Jika gagal, engineer harus hotfix ke main, push, dan
# berharap hotfix-nya benar. Tidak ada jalan pintas.
Pendekatan ini bermasalah karena: bug di production membutuhkan waktu untuk di-investigasi, diperbaiki, dan di-deploy. Sementara itu, service tetap down. Forward-fix adalah strategi yang baik untuk bug minor (misal typo di UI), tapi buruk untuk regresi besar (misal query yang mengunci tabel selama 30 detik).
# BENAR: bidirectional recovery — rollback dulu, fix nanti
# playbooks/deploy-with-bidirectional-recovery.yml
---
- name: Deploy dengan opsi rollback
hosts: appservers
vars:
deploy_version: "{{ version | mandatory }}"
pre_tasks:
- name: Catat versi rollback
command: cat /opt/app/VERSION
register: current
changed_when: false
ignore_errors: true
- set_fact:
rollback_version: "{{ current.stdout | default('unknown') | trim }}"
tasks:
- block:
- name: Deploy
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "v{{ deploy_version }}"
- name: Restart dan health check
systemd:
name: myapp
state: restarted
notify: wait for health
- name: Tunggu health check
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
retries: 12
delay: 5
rescue:
- name: Rollback otomatis
git:
repo: https://github.com/company/app.git
dest: /opt/app
version: "v{{ rollback_version }}"
- name: Restart dengan versi lama
systemd:
name: myapp
state: restarted
always:
- name: Kirim alert status deployment
uri:
url: "{{ vault_slack_webhook }}"
method: POST
body_format: json
body:
text: "Deploy v{{ deploy_version }}: {{ 'ROLLBACK' if ansible_failed_result is defined else 'SUCCESS' }}"
status_code: 200
Strategi benar: rollback dulu, perbaiki nanti. Service availability lebih utama daripada kecepatan perbaikan. Saat deployment gagal dan kita memiliki rollback otomatis, service kembali normal dalam hitungan menit. Investigasi root cause dan forward-fix bisa dilakukan tanpa tekanan bahwa production sedang down. Setelah perbaikan siap, lakukan deploy forward-fix sebagai deployment baru. Ini bersifat bidirectional: bisa rollback, bisa forward-fix, keputusan berdasarkan konteks.
Perbandingan Strategi Rollback #
Tidak ada satu strategi rollback yang universal. Pilih berdasarkan toleransi downtime, kompleksitas deployment, dan frekuensi rilis.
| Aspek | Git Revert | Image Redeploy | Blue-Green Switch | Canary Rollback | Feature Flag Disable |
|---|---|---|---|---|---|
| Waktu rollback | 5–15 menit | 3–10 menit | < 1 menit | 1–5 menit | < 1 menit |
| Downtime saat rollback | Mungkin (tergantung health check) | Mungkin (tergantung restart) | Zero | Zero | Zero |
| Cocok untuk database schema | Tidak (perlu migrasi down) | Tidak | Tidak | Tidak | Ya (flag per-feature) |
| Resource overhead | Rendah | Rendah | 2x lipat | 2x lipat (sementara) | Rendah |
| Kompleksitas tooling | Rendah | Rendah | Sedang (butuh LB config) | Tinggi (butuh orchestrator) | Sedang (perlu flag system) |
| Cocok untuk | Monolith, batch | Stateless service | Service dengan SLA tinggi | Risk-sensitive release | Quick kill switch per feature |
| Risiko data loss | Rendah | Rendah | Rendah | Rendah | Tidak ada |
| Butuh support rollback plan | Selalu | Selalu | Built-in | Built-in | Built-in |
Rollback strategy terbaik untuk kebanyakan tim adalah kombinasi: blue-green untuk application code (rollback dalam hitungan detik) + expand-contract untuk database (rollback tanpa data loss) + feature flag untuk quick kill switch (rollback tanpa redeploy). Mulai dari yang paling sederhana (image redeploy dengan playbook rollback) dan tambah kompleksitas hanya saat ada kebutuhan eksplisit.
Canary Rollback untuk Risk-Sensitive Deployment #
Untuk deployment ke user base besar, kita mungkin ingin membatasi exposure dari versi baru ke sebagian kecil traffic terlebih dahulu. Canary deployment memungkinkan ini: deploy versi baru ke sebagian kecil server, monitor, dan jika aman, promosikan ke semua server. Jika tidak aman, lakukan rollback hanya pada bagian canary tersebut.
# playbooks/canary-deploy.yml
---
- name: Canary deployment
hosts: localhost
vars:
canary_percentage: 10
canary_hosts_group: canary_appservers
production_hosts_group: production_appservers
tasks:
- name: Deploy ke canary subset
include_tasks: deploy-app.yml
vars:
target_hosts: "{{ canary_hosts_group }}"
version: "{{ deploy_version }}"
- name: Tunggu canary stabil
pause:
minutes: 5
prompt: "Tunggu 5 menit, lalu cek error rate"
- name: Cek error rate canary
uri:
url: "https://monitoring.internal/api/canary-error-rate?app=myapp&version={{ deploy_version }}"
return_content: true
register: canary_metrics
- name: Keputusan berdasarkan error rate
block:
- name: Promote canary ke production
include_tasks: deploy-app.yml
vars:
target_hosts: "{{ production_hosts_group }}"
version: "{{ deploy_version }}"
- name: Notifikasi sukses
debug:
msg: "Canary sukses, di-promote ke {{ canary_percentage }}% traffic"
rescue:
- name: Rollback canary
include_tasks: rollback-app.yml
vars:
target_hosts: "{{ canary_hosts_group }}"
- name: Hentikan promosi
fail:
msg: "Canary gagal, di-rollback, promosi ke production dibatalkan"
Canary rollback adalah strategi paling konservatif: hanya sebagian kecil user yang terekspos bug, dan rollback terjadi pada subset kecil tanpa harus melakukan re-deploy ke semua server. Strategi ini cocok untuk bisnis dengan reputasi yang sensitif terhadap downtime (payment, healthcare, dsb.) di mana rilis baru membutuhkan validasi ekstra.
Ringkasan #
- Selalu catat versi yang berjalan sebelum deployment dimulai — file
VERSIONdanPREVIOUS_VERSIONadalah prasyarat untuk rollback yang andal.block/rescueuntuk rollback otomatis saat health check gagal — jangan biarkan deployment yang gagal membiarkan sistem dalam kondisi setengah jalan.- Buat playbook rollback terpisah yang bisa dipicu dari pipeline atau manual — rollback tidak boleh memerlukan kemampuan teknis khusus saat terjadi insiden.
- Rollback database adalah masalah terpisah dari rollback kode — gunakan expand-contract pattern untuk migrasi yang aman di-rollback.
- Blue-green deployment memungkinkan rollback instan dengan switch pointer, bukan re-deploy — ideal untuk sistem yang tidak bisa toleransi downtime rollback.
- Canary deployment untuk risk-sensitive release — batasi exposure ke sebagian kecil traffic, rollback subset tanpa ganggu mayoritas.
- Log setiap deployment dan rollback dengan timestamp dan versi — audit trail yang lengkap sangat berharga saat investigasi insiden.
- Bidirectional recovery: rollback dulu untuk pulihkan service, fix nanti di deployment terpisah. Forward-fix bukan satu-satunya opsi.
- Health check harus menguji fungsionalitas kritis (DB connection, downstream API), bukan hanya return 200.
- Jangan gabungkan “tambah kolom” dan “drop kolom” dalam satu deployment — selalu pisahkan agar rollback tidak kehilangan data.
← Sebelumnya: Environment Management Berikutnya: Artifact Management →