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. 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.
Menyimpan State untuk Rollback #
Langkah pertama rollback yang efektif adalah memastikan informasi yang diperlukan tersedia:
# 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
Rollback Otomatis Saat Health Check Gagal #
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
Rollback Manual via Pipeline #
Buat Job Template atau workflow khusus untuk rollback manual:
# 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 }}"
Rollback Database: Masalah yang Berbeda #
Rollback kode mudah. Rollback database schema jauh lebih kompleks:
# 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
Blue-Green Deployment untuk Rollback Instan #
Blue-green adalah strategi deployment yang memungkinkan rollback dengan mengganti pointer, bukan mengulang deployment:
# 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)
Ringkasan #
- Selalu catat versi yang berjalan sebelum deployment dimulai — ini adalah 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.
- Log setiap deployment dan rollback dengan timestamp dan versi — audit trail yang lengkap sangat berharga saat investigasi insiden.
← Sebelumnya: Environment Management Berikutnya: Artifact Management →