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. 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.
Playbook Diagnostik: Kumpulkan Dulu, Putuskan Kemudian #
Langkah pertama saat insiden bukanlah melakukan perubahan — melainkan mengumpulkan informasi untuk memahami situasi:
# 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"
Self-Healing: Automasi untuk Kondisi Umum #
Beberapa kondisi insiden yang berulang bisa diatasi secara otomatis tanpa intervensi 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
Runbook sebagai Playbook yang Terdokumentasi #
Runbook yang paling efektif adalah yang bisa dijalankan sekaligus menjelaskan apa yang dilakukannya:
# playbooks/runbooks/database-connection-exhausted.yml
---
# RUNBOOK: Database Connection Pool Exhausted
#
# Gejala: Error "remaining connection slots are reserved for non-replication superuser connections"
# Penyebab umum: Connection leak di aplikasi, traffic spike, atau pool size terlalu kecil
# Eskalasi: Jika runbook ini tidak menyelesaikan masalah, hubungi tim database
- 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
Workflow Eskalasi #
# 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
no_log: true
Ringkasan #
- Kumpulkan diagnostik sebelum bertindak — playbook diagnostik yang mengumpulkan status service, resource usage, dan log error memberikan gambaran situasi tanpa membuat perubahan.
- Self-healing untuk kondisi yang berulang dan terprediksi (disk penuh, service crash, idle connection) — kurangi intervensi manual untuk hal-hal yang bisa diotomasi.
- 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.
- Workflow eskalasi harus otomatis — jika runbook tidak menyelesaikan masalah, sistem harus mengirim alert eskalasi tanpa membutuhkan keputusan manual.
- Simpan semua runbook di Git dan review secara berkala — runbook yang tidak di-update menjadi dokumen yang menyesatkan saat paling dibutuhkan.