Incident Response #
Alert sudah berbunyi. Seseorang di on-call duty harus bertindak. Tanpa persiapan, setiap insiden dimulai dari nol: buka terminal, SSH ke server, coba ingat perintah diagnostik yang relevan, baca log yang tersebar, coba beberapa solusi. Seringkali yang terjadi adalah salah langkah, eskalasi yang tidak perlu, dan jam-jam yang habis untuk hal yang sebenarnya sudah pernah di-debug sebelumnya. Dengan runbook yang bisa dieksekusi — playbook Ansible yang mendokumentasikan langkah-langkah respons sekaligus menjalankannya — tim bisa merespons lebih cepat, lebih konsisten, dan dengan lebih sedikit kesalahan. Artikel ini membahas cara membangun infrastruktur runbook yang efektif.
Alur Respons Insiden yang Lengkap #
Respons insiden bukan hanya soal “fix the thing yang rusak”. Ada alur yang terstruktur dari deteksi sampai post-mortem, dan setiap fase punya tujuan serta output yang berbeda:
sequenceDiagram
participant Mon as Monitoring
participant OnCall as On-Call Engineer
participant Run as Runbook
participant Sys as Affected System
participant EsC as Escalation
participant PM as Post-Mortem
Mon->>OnCall: "Alert fired"
Note over OnCall: "Fase 1: DETEKSI"
OnCall->>Run: "Acknowledge alert, jalankan diagnose.yml"
Run->>Sys: "Kumpulkan data"
Sys-->>Run: "Logs, metrics, status"
Run-->>OnCall: "Laporan diagnostik"
Note over OnCall: "Fase 2: TRIAGE"
OnCall->>OnCall: "Tentukan severity"
OnCall->>Run: "Pilih runbook sesuai gejala"
Note over OnCall: "Fase 3: MITIGASI"
Run->>Sys: "Eksekusi langkah perbaikan"
alt Berhasil
Sys-->>Run: "Service pulih"
OnCall->>Mon: "Konfirmasi resolved"
else Gagal
Run-->>OnCall: "Runbook tidak resolve"
OnCall->>EsC: "Eskalasi (severity naik)"
end
Note over OnCall,PM: "Fase 4: POST-MORTEM"
OnCall->>PM: "Tulis blameless post-mortem"
PM->>PM: "Identifikasi root cause + action items"
Empat fase ini — Deteksi, Triage, Mitigasi, Post-Mortem — adalah fondasi respons insiden yang efektif. Ansible mengotomasi fase 1 dan 3 (diagnostik dan mitigasi), dan mendokumentasikan fase 2 dan 4 lewat komentar di playbook runbook.
Severity Level: Siapa yang Dipanggil Kapan #
Tidak semua insiden sama. Severity menentukan seberapa cepat respons dibutuhkan, siapa yang harus dipanggil, dan apakah perlu eskalasi. Tabel berikut adalah acuan yang umum dipakai:
| Severity | Dampak | Response Time | Eskalasi | Contoh |
|---|---|---|---|---|
| SEV-1 (Critical) | Total outage, data loss, security breach | < 15 menit | On-call → Lead → Director → VP Engineering | Production down, customer-facing API return 500 |
| SEV-2 (Major) | Degraded sebagian user, ada workaround | < 1 jam | On-call → Lead | Satu region down, payment processing lambat |
| SEV-3 (Minor) | Edge case, tidak ada user impact langsung | < 4 jam | On-call handle sendiri | Non-critical job gagal, satu metric alert palsu |
| SEV-4 (Cosmetic) | Bug minor, no user impact | Best effort | Backlog | Typo di UI, log noisy |
Memiliki severity matrix yang jelas mencegah dua masalah umum: (1) engineer yang burn-out karena semua alert diperlakukan SEV-1, dan (2) insiden yangunder-escalated karena engineer tidak yakin apakah harus panggil lead.
State Diagram Severity Escalation #
Severity bisa naik turun tergantung situasi. Insiden yang dimulai SEV-3 bisa menjadi SEV-1 jika business impact ternyata lebih besar dari yang diasumsikan awal:
stateDiagram-v2
[*] --> SEV3: "Alert diterima"
SEV3 --> SEV2: "Impact meluas"
SEV2 --> SEV1: "Total outage"
SEV2 --> SEV3: "Impact terbatas"
SEV1 --> SEV2: "Mitigasi parsial"
SEV1 --> Resolved: "Service pulih"
SEV2 --> Resolved: "Workaround aktif"
SEV3 --> Resolved: "Fixed"
Resolved --> PostMortem: "Severity apapun"
PostMortem --> [*]
SEV1 --> SEV1: "Self-escalate jika impact memburuk"
Decision Tree: Severity Awal yang Tepat #
flowchart TD
A["Alert masuk"] --> B{"User-facing impact?"}
B -- "Ya" --> C{"Semua user atau sebagian?"}
C -- "Semua user" --> D["SEV-1: Total outage"]
C -- "Sebagian" --> E{"Ada workaround?"}
E -- "Tidak" --> F["SEV-1: Customer-facing down"]
E -- "Ya" --> G["SEV-2: Degraded"]
B -- "Tidak" --> H{"Data loss atau security?"}
H -- "Ya" --> I["SEV-1: Data/Security"]
H -- "Tidak" --> J{"Bisakah ditunda jam ini?"}
J -- "Tidak" --> K["SEV-2: Internal blocker"]
J -- "Ya" --> L["SEV-3: Minor"]
Playbook Diagnostik: Kumpulkan Dulu, Putuskan Kemudian #
Langkah pertama saat insiden bukanlah melakukan perubahan — melainkan mengumpulkan informasi untuk memahami situasi. Banyak insiden yang sebenarnya hanya butuh restart service, tapi tanpa diagnostik yang benar, engineer malah mengubah konfigurasi atau melakukan deploy ulang yang tidak perlu dan justru memperparah masalah:
# playbooks/diagnose.yml
---
- name: Kumpulkan informasi diagnostik saat insiden
hosts: "{{ target_hosts | default('all') }}"
gather_facts: true
tasks:
- name: Kumpulkan status semua service kritis
systemd:
name: "{{ item }}"
register: service_status
loop: "{{ critical_services }}"
ignore_errors: true
changed_when: false
- name: Kumpulkan penggunaan resource
command: "{{ item.cmd }}"
register: "{{ item.name }}"
loop:
- { name: top_processes, cmd: "ps aux --sort=-%cpu | head -15" }
- { name: disk_usage, cmd: "df -h" }
- { name: memory_usage, cmd: "free -h" }
- { name: network_conn, cmd: "ss -tnp | head -30" }
- { name: open_files, cmd: "lsof | wc -l" }
ignore_errors: true
changed_when: false
- name: Kumpulkan log error terbaru
command: "journalctl -u {{ item }} --since '30 minutes ago' --no-pager -p err"
register: recent_errors
loop: "{{ critical_services }}"
ignore_errors: true
changed_when: false
- name: Simpan semua diagnostik ke file lokal
local_action:
module: copy
content: |
===== DIAGNOSTIK INSIDEN =====
Host: {{ inventory_hostname }}
Waktu: {{ ansible_date_time.iso8601 }}
=== STATUS SERVICE ===
{% for result in service_status.results %}
{{ result.item }}: {{ result.status.ActiveState | default('unknown') }}
{% endfor %}
=== RESOURCE USAGE ===
{{ disk_usage.stdout }}
{{ memory_usage.stdout }}
=== TOP PROCESSES ===
{{ top_processes.stdout }}
=== LOG ERRORS (30 MENIT TERAKHIR) ===
{% for result in recent_errors.results %}
--- {{ result.item }} ---
{{ result.stdout | default('(tidak ada error)') }}
{% endfor %}
dest: "/tmp/incident-diag-{{ inventory_hostname }}-{{ ansible_date_time.date }}.txt"
delegate_to: localhost
- name: Tampilkan ringkasan diagnostik
hosts: localhost
gather_facts: false
tasks:
- name: Lokasi file diagnostik
debug:
msg: "File diagnostik tersimpan di /tmp/incident-diag-*.txt"
Poin penting dari playbook ini: tidak ada task yang mengubah state sistem. Semua task hanya register data dan command dengan changed_when: false. Tujuannya murni mengumpulkan informasi. Engineer yang menjalankan playbook ini boleh tenang karena tidak ada risiko merusak sesuatu yang sebenarnya masih berfungsi.
Saat menjalankan playbook diagnostik, selalu simpan output ke file dengan timestamp, lalu commit ke Git atau upload ke internal wiki. File ini menjadi bukti untuk post-mortem dan referensi untuk insiden serupa di masa depan. Diagnostic yang tidak diarsipkan = pembelajaran yang hilang.
Self-Healing: Automasi untuk Kondisi Umum #
Beberapa kondisi insiden yang berulang dan terprediksi bisa diatasi secara otomatis tanpa intervensi manusia. Automasi self-healing mengurangi waktu pemulihan, mengurangi alert fatigue, dan membuat on-call engineer fokus pada insiden yang benar-benar butuh penalaran manusia:
# playbooks/self-heal.yml
---
- name: Self-healing untuk kondisi umum
hosts: appservers
become: true
tasks:
- name: Kumpulkan informasi kondisi saat ini
gather_facts: true
# Kondisi 1: Disk hampir penuh — bersihkan log lama dan Docker images
- name: Cek penggunaan disk
command: df / --output=pcent
register: disk_pcent
changed_when: false
- name: Bersihkan log lama jika disk > 85%
block:
- name: Hapus log lama (lebih dari 7 hari)
find:
paths: /var/log
age: 7d
patterns: "*.log.*"
recurse: true
register: old_logs
- name: Hapus file log lama
file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_logs.files }}"
- name: Bersihkan Docker images yang tidak digunakan
community.docker.docker_prune:
images: true
volumes: true
builder_cache: true
ignore_errors: true
- name: Catat tindakan self-healing
debug:
msg: "Self-heal: disk cleanup dilakukan (penggunaan disk > 85%)"
when: disk_pcent.stdout | trim | replace('%', '') | int > 85
# Kondisi 2: Service crash — restart otomatis
- name: Cek status service kritis
systemd:
name: "{{ item }}"
register: svc_check
loop: "{{ critical_services }}"
changed_when: false
ignore_errors: true
- name: Restart service yang tidak aktif
systemd:
name: "{{ item.item }}"
state: restarted
loop: "{{ svc_check.results }}"
when:
- item.status is defined
- item.status.ActiveState != "active"
loop_control:
label: "{{ item.item }}"
# Kondisi 3: Memory pressure — restart service dengan memory leak
- name: Cek penggunaan memory
command: free -m --output=used
register: mem_used
changed_when: false
- name: Restart aplikasi jika memory sangat tinggi
systemd:
name: myapp
state: restarted
when:
- ansible_memtotal_mb > 0
- (mem_used.stdout_lines[-1] | trim | int) / ansible_memtotal_mb > 0.90
Kapan Self-Healing Tepat vs Berbahaya #
Self-healing adalah pedang bermata dua. Di satu sisi ia mengurangi noise dan MTTR; di sisi lain, ia bisa menutupi masalah sistemik yang seharusnya diinvestigasi:
SELF-HEAL COCOK untuk:
✓ Disk cleanup (log rotation, image prune) — reversible, low risk
✓ Restart service yang crash — biasanya pulih dengan sendirinya
✓ Kill process zombie — clear dan reversible
✓ Rotate log file besar — idempotent
SELF-HEAL BERBAHAYA untuk:
✗ Restart database prod tanpa threshold ketat — bisa trigger failover
✗ Auto-scale up resource tanpa analisis — menutupi memory leak
✗ Clear alert state — menyembunyikan masalah
✗ Modify konfigurasi service — bisa jadi salah konfigurasi makin parah
✗ Restart Kubernetes node — bisa trigger pod eviction cascade
Setiap self-heal action harus mengirim notifikasi ke channel on-call, bahkan jika berhasil. Engineer perlu tahu bahwa self-heal terjadi — bukan untuk bertindak saat itu, tapi untuk menganalisis pola: jika self-heal untuk service X terjadi 5x dalam sehari, ada masalah fundamental di service X (memory leak, config bug) yang harus diinvestigasi dan di-fix permanen. Self-heal yang sering adalah symptom, bukan solusi.
Runbook sebagai Playbook yang Terdokumentasi #
Runbook yang paling efektif adalah yang bisa dijalankan sekaligus menjelaskan apa yang dilakukannya. Dokumentasi yang terpisah dari eksekusi cepat basi — runbook yang ada di playbook Ansible selalu ter-update karena tested setiap kali dijalankan:
# playbooks/runbooks/database-connection-exhausted.yml
---
# RUNBOOK: Database Connection Pool Exhausted
#
# Gejala: Error "remaining connection slots are reserved for non-replication superuser connections"
# atau aplikasi timeout saat konek ke database
# Penyebab umum: Connection leak di aplikasi, traffic spike, atau pool size terlalu kecil
# Eskalasi: Jika runbook ini tidak menyelesaikan masalah dalam 10 menit,
# hubungi tim database dan naikkan severity ke SEV-2
- name: Runbook — Database Connection Pool Exhausted
hosts: dbservers
become: true
tasks:
- name: "[Diagnostik] Lihat semua koneksi aktif ke database"
command: >
psql -U postgres -c
"SELECT client_addr, state, count(*) as count
FROM pg_stat_activity
GROUP BY client_addr, state
ORDER BY count DESC"
register: db_connections
changed_when: false
become_user: postgres
- name: "[Info] Tampilkan koneksi per client"
debug:
var: db_connections.stdout_lines
- name: "[Cek] Berapa koneksi idle yang lama?"
command: >
psql -U postgres -c
"SELECT count(*) FROM pg_stat_activity
WHERE state = 'idle'
AND state_change < now() - interval '10 minutes'"
register: idle_connections
changed_when: false
become_user: postgres
- name: "[Aksi] Terminasi koneksi idle lama jika > 20"
command: >
psql -U postgres -c
"SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND state_change < now() - interval '10 minutes'
AND pid <> pg_backend_pid()"
become_user: postgres
when: idle_connections.stdout | regex_search('\d+') | int > 20
register: terminate_result
- name: "[Hasil] Tampilkan jumlah koneksi yang diterminasi"
debug:
msg: "Koneksi yang diterminasi: {{ terminate_result.stdout | default('0 (tidak ada yang perlu diterminasi)') }}"
- name: "[Verifikasi] Cek jumlah koneksi sekarang"
command: >
psql -U postgres -c
"SELECT count(*) FROM pg_stat_activity"
register: current_connections
changed_when: false
become_user: postgres
- name: "[Status] Kondisi koneksi saat ini"
debug:
var: current_connections.stdout_lines
Perhatikan struktur di playbook: setiap task punya label [Diagnostik], [Cek], [Aksi], [Verifikasi], [Status] di awal nama. Pola ini memudahkan on-call engineer yang membaca log output memahami fase mana yang sedang berjalan, dan juga mempercepat post-mortem — log langsung menunjukkan urutan langkah yang sudah dilakukan.
Anti-Pattern: Runbook Samar vs Runbook Konkret #
# ANTI-PATTERN: Runbook samar tanpa detail langkah
# playbooks/fix-database.yml
---
- name: Fix database issue
hosts: dbservers
tasks:
- name: Restart postgres if needed
systemd:
name: postgresql
state: restarted
when: "something is wrong"
# Masalah:
# 1. "if needed" dan "something is wrong" tidak terdefinisi.
# 2. Engineer yang jalankan ini di jam 3 pagi harus menebak kapan harus restart.
# 3. Tidak ada verifikasi setelah restart.
# 4. Tidak ada catatan apa yang dicek.
# 5. Tidak reversible kalau restart justru memperparah masalah.
# BENAR: Runbook dengan step konkret, threshold jelas, dan verifikasi
# playbooks/runbooks/database-connection-exhausted.yml
- name: Runbook — Database Connection Pool Exhausted
hosts: dbservers
tasks:
- name: "[Diagnostik] Hitung koneksi idle > 10 menit"
command: "psql -c \"SELECT count(*) FROM pg_stat_activity WHERE state = 'idle' AND state_change < now() - interval '10 minutes'\""
register: idle_count
changed_when: false
- name: "[Aksi] Terminasi jika > 20 koneksi idle"
command: "psql -c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND state_change < now() - interval '10 minutes' AND pid <> pg_backend_pid()\""
when: idle_count.stdout | regex_search('\d+') | int > 20
- name: "[Verifikasi] Pastikan total koneksi turun di bawah threshold"
command: "psql -c \"SELECT count(*) FROM pg_stat_activity\""
register: total_count
failed_when: total_count.stdout | regex_search('\d+') | int > 100
# Keuntungan:
# - Threshold eksplisit (> 20 koneksi idle, total < 100)
# - Ada fase verifikasi (failed_when untuk rollback indicator)
# - Tidak restart service — reversible, low-risk
# - Output jelas untuk post-mortem
Workflow Eskalasi #
Saat runbook tidak menyelesaikan masalah, eskalasi harus terjadi secara otomatis — engineer on-call tidak perlu membuang waktu untuk memutuskan siapa yang harus dipanggil. Sistem harus membuat keputusan eskalasi berdasarkan severity dan durasi insiden:
# playbooks/escalate.yml
---
- name: Kirim eskalasi insiden
hosts: localhost
vars:
incident_id: "{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}"
tasks:
- name: Buat tiket insiden di sistem tiket
uri:
url: "{{ pagerduty_events_url }}"
method: POST
body_format: json
body:
routing_key: "{{ vault_pagerduty_routing_key }}"
event_action: trigger
dedup_key: "ansible-incident-{{ incident_id }}"
payload:
summary: "{{ incident_summary }}"
severity: "{{ incident_severity | default('critical') }}"
source: "ansible-runbook"
custom_details:
environment: "{{ env }}"
affected_hosts: "{{ ansible_play_hosts | join(', ') }}"
runbook: "{{ playbook_dir | basename }}"
status_code: 202
no_log: true
- name: Kirim notifikasi Slack darurat
uri:
url: "{{ vault_slack_webhook_url }}"
method: POST
body_format: json
body:
text: ":rotating_light: *INSIDEN KRITIS*"
attachments:
- color: danger
title: "{{ incident_summary }}"
fields:
- title: Environment
value: "{{ env }}"
short: true
- title: Incident ID
value: "{{ incident_id }}"
short: true
- title: Runbook
value: "{{ playbook_dir | basename }}"
short: true
- title: Affected Hosts
value: "{{ ansible_play_hosts | join(', ') }}"
short: false
no_log: true
Decision Tree: Siapa yang Dipanggil #
flowchart TD
A[Incident triggered] --> B{Severity?}
B -- SEV-1 --> C[On-Call: panggil<br/>+ lead engineer]
C --> D{Ack < 5 min?}
D -- Tidak --> E[Auto-escalate:<br/>engineering manager]
E --> F{Ack < 10 min?}
F -- Tidak --> G[VP Engineering +<br/>comms team]
B -- SEV-2 --> H[On-Call handle<br/>+ notify lead di Slack]
H --> I{Resolved < 1 jam?}
I -- Tidak --> J[Eskalasi ke SEV-1]
I -- Ya --> K[Close + post-mortem]
B -- SEV-3 --> L[On-Call handle sendiri]
L --> M[Resolved? ticket close]
B -- SEV-4 --> N[Backlog, no escalation]
Workflow ini bisa diotomasi lewat timer di PagerDuty atau rule di alerting (lihat Alerting). Yang penting: eskalasi tidak pernah menjadi keputusan manual saat insiden terjadi — keputusan itu sudah dibuat sebelumnya dan tertulis di policy.
Post-Mortem: Blameless dan Actionable #
Post-mortem bukan dokumen untuk mencari siapa yang salah — itu adalah dokumen untuk mencari kondisi sistemik yang memungkinkan insiden terjadi. Budaya “blameless” krusial: kalau orang takut dituduh saat insiden, mereka akan menyembunyikan informasi, dan pembelajaran tidak pernah terjadi:
Anti-Pattern: Skip Post-Mortem vs Blameless Post-Mortem #
ANTI-PATTERN: Skip post-mortem untuk insiden "kecil"
Setelah SEV-3 selesai, engineer kembali ke kerjaan biasa.
Tidak ada post-mortem. Tidak ada root cause analysis.
3 bulan kemudian, SEV-3 yang sama terjadi lagi, ke orang berbeda.
Lagi-lagi tidak ada post-mortem. Pattern tidak pernah terlihat.
Risk: Insiden yang sama berulang dengan biaya yang sama setiap kali.
Tim tidak pernah belajar. Budaya "fix and forget" menang.
BENAR: Blameless post-mortem untuk semua insiden SEV-1 dan SEV-2
Dalam 48 jam setelah resolved, schedule post-mortem meeting.
Hadir: on-call engineer, lead, SRE, product manager.
Format:
1. Timeline (dari alert sampai resolved, dengan timestamp presisi)
2. Root cause analysis (5-Whys atau fishbone diagram)
3. Contributing factors (apa yang membuat insiden ini bisa terjadi)
4. What went well (respon cepat, runbook efektif)
5. What went wrong (alert lambat, dokumentasi kurang, runbook obsolete)
6. Action items dengan owner dan deadline
Output: Dokumen publik di internal wiki + action items di backlog.
Risk: post-mortem makan 1-2 jam per insiden.
Benefit: insiden yang sama tidak berulang → save 5-10 jam incident response
di masa depan → ROI positif setelah insiden ke-2.
Template Post-Mortem via Ansible #
Untuk konsistensi, sediakan template post-mortem yang bisa di-generate dari playbook:
# playbooks/templates/post-mortem-template.md.j2
# Post-Mortem: {{ incident_summary }}
**Incident ID:** {{ incident_id }}
**Date:** {{ incident_date }}
**Severity:** {{ incident_severity }}
**Status:** Resolved
**Duration:** {{ incident_duration }}
## Timeline
| Time (UTC) | Event |
|---|---|
{% for event in timeline %}
| {{ event.time }} | {{ event.description }} |
{% endfor %}
## Root Cause
{{ root_cause }}
## Contributing Factors
{% for factor in contributing_factors %}
- {{ factor }}
{% endfor %}
## What Went Well
{% for item in went_well %}
- {{ item }}
{% endfor %}
## What Went Wrong
{% for item in went_wrong %}
- {{ item }}
{% endfor %}
## Action Items
| Action | Owner | Deadline | Status |
|---|---|---|---|
{% for item in action_items %}
| {{ item.action }} | {{ item.owner }} | {{ item.deadline }} | Open |
{% endfor %}
Anti-Pattern: Post-Mortem yang Menyalahkan Individu #
ANTI-PATTERN: Post-mortem yang fokus ke "siapa"
"Andi salah hapus tabel production saat maintenance."
"Toni telat acknowledge alert karena sedang meeting."
"Siti deploy di jam sibuk tanpa approval."
Efek:
- Tim jadi defensive, takut diskusi terbuka di post-mortem
- Informasi disembunyikan di post-mortem berikutnya
- Fokus ke "orang X harus hati-hati" bukan ke sistem
- Tidak ada perubahan sistemik → insiden berulang
BENAR: Post-mortem yang fokus ke kondisi sistemik
"Proses deployment tidak punya pre-check yang memvalidasi environment."
"Alert tidak punya escalation policy yang jelas untuk severity ini."
"On-call schedule tidak overlap dengan meeting tim, jadi ada gap 30 menit."
"Runbook untuk skenario X belum ditulis, dan engineer yang pertama respond
belum pernah menghadapi skenario ini sebelumnya."
Keuntungan:
- Tidak ada yang merasa disalahkan secara personal
- Fokus ke sistem, proses, dan tooling yang bisa diperbaiki
- Action items berupa perubahan sistemik (bukan "Andi harus lebih hati-hati")
- Budaya psychological safety → insiden dilaporkan lebih dini
Integrasi dengan Alerting dan SLO #
Respons insiden yang efektif dimulai dari alert yang baik dan SLO yang terdefinisi dengan jelas. Alert tanpa SLO = noise. SLO tanpa runbook = tujuan tanpa eksekusi. Lihat Alerting untuk setup alert yang actionable, dan SLO & SLA untuk mendefinisikan target layanan yang terukur:
flowchart LR
A["Monitoring & SLO"] --> B{"Breach SLO?"}
B -- "Ya" --> C["Alert fired"]
C --> D["Severity assigned"]
D --> E{"Eskalasi policy"}
E --> SEV1["SEV-1: PagerDuty"]
E --> SEV2["SEV-2: Slack + on-call"]
E --> SEV3["SEV-3: Ticket"]
SEV1 --> F["On-Call respond"]
SEV2 --> F
SEV3 --> F
F --> G{"Runbook resolves?"}
G -- "Ya" --> H["Service restored"]
H --> I["Post-Mortem"]
G -- "Tidak" --> J["Eskalasi naik"]
J --> K["Lead/Manager panggil"]
K --> F
Alert yang baik adalah alert yang punya runbook link di annotation-nya. Saat on-click alert di PagerDuty, engineer langsung dibawa ke runbook yang relevan. Ini menghemat lookup time dan mengurangi keputusan “apa yang harus dilakukan” saat insiden terjadi.
Kapan Incident Response Automation Tidak Cocok #
Tidak semua insiden cocok diotomasi. Ada kelas insiden yang butuh penalaran manusia dan keputusan situasional:
JANGAN automasi untuk:
✗ Security incident (data breach, unauthorized access) — butuh investigasi forensik
✗ Data corruption atau potential data loss — butuh assessment manual
✗ Deployment rollback keputusan — butuh judgement bisnis
✗ Communication ke customer tentang outage — butuh crafted message
✗ Root cause analysis — butuh manusia dengan konteks domain
✗ Action items post-mortem — butuh diskusi tim, bukan script
AUTOMASI COCOK untuk:
✓ Kumpulkan diagnostik (logs, metrics, status) saat alert
✓ Restart service yang crash dengan threshold jelas
✓ Cleanup resource (disk, memory) yang sudah penuh
✓ Terminasi koneksi database yang idle/over
✓ Kirim notifikasi eskalasi ke PagerDuty/Slack
✓ Generate template post-mortem dengan timeline
✓ Verifikasi kondisi setelah perbaikan
Batas yang jelas antara automasi dan judgement adalah kunci: automasi yang terlalu agresif bisa menutupi masalah; automasi yang terlalu minim tidak memberikan value.
Ringkasan #
- Kumpulkan diagnostik sebelum bertindak — playbook diagnostik yang mengumpulkan status service, resource usage, dan log error memberikan gambaran situasi tanpa membuat perubahan.
- Severity matrix yang jelas (SEV-1 sampai SEV-4) mencegah under-escalation dan over-escalation — tentukan threshold response time dan jalur eskalasi sebelum insiden terjadi.
- Self-healing untuk kondisi yang berulang dan terprediksi (disk penuh, service crash, idle connection) — kurangi intervensi manual untuk hal-hal yang bisa diotomasi dengan risiko rendah.
- Runbook sebagai playbook adalah cara terbaik mendokumentasikan prosedur insiden — dokumentasi dan eksekusi ada di tempat yang sama dan selalu sinkron.
- Tambahkan komentar naratif di playbook runbook (Gejala, Penyebab, Eskalasi) — orang yang on-call jam 3 pagi butuh konteks, bukan hanya perintah.
- Gunakan label task seperti
[Diagnostik],[Aksi],[Verifikasi]untuk memudahkan baca log output dan analisis post-mortem.- Workflow eskalasi harus otomatis — jika runbook tidak menyelesaikan masalah, sistem harus mengirim alert eskalasi tanpa membutuhkan keputusan manual.
- Blameless post-mortem fokus ke kondisi sistemik, bukan individu — ini menciptakan psychological safety dan mencegah insiden berulang.
- Semua post-mortem SEV-1 dan SEV-2 harus menghasilkan action items dengan owner dan deadline — dokumentasi tanpa action items = pembelajaran tanpa implementasi.
- Setiap self-heal action harus kirim notifikasi ke on-call, bahkan jika berhasil — pola self-heal yang sering adalah symptom dari masalah sistemik.
- Simpan semua runbook di Git dan review secara berkala — runbook yang tidak di-update menjadi dokumen yang menyesatkan saat paling dibutuhkan.
- Alert yang baik punya runbook link di annotation — satu klik dari PagerDuty langsung ke playbook yang relevan.
- Lihat juga Alerting untuk setup alert yang actionable, dan SLO & SLA untuk mendefinisikan target layanan yang terukur.