Alerting #
Monitoring tanpa alerting seperti dashboard yang tidak ada yang melihat — masalah bisa terjadi tanpa ada yang tahu. Alerting adalah lapisan di atas monitoring yang secara proaktif memberitahu tim saat kondisi abnormal terdeteksi. Tapi alerting yang buruk bisa sama berbahayanya dengan tidak ada alerting sama sekali: terlalu banyak notifikasi yang tidak actionable membuat tim mati rasa dan mengabaikan alert yang benar-benar penting. Artikel ini membahas cara mengotomasi setup Alertmanager dengan Ansible, merancang rute notifikasi yang terstruktur, dan membangun pola alert yang bisa ditindaklanjuti tanpa membanjiri tim on-call.
Arsitektur Alerting #
Alur alerting di stack Prometheus terbagi menjadi dua tahap yang jelas. Prometheus bertugas mendeteksi — mengevaluasi alert rules terhadap metrik yang sudah di-scrape dan mengubahnya menjadi status firing atau resolved. Alertmanager bertugas menyalurkan — menerima alert dari Prometheus, lalu memutuskan siapa yang harus tahu, di channel mana, seberapa sering, dan apa yang harus di-suppress. Pemisahan ini penting: jika kita menggabungkan keduanya, kita akan kehilangan semua fitur grouping, deduplication, inhibition, dan silencing yang membuat alerting praktis di skala produksi.
flowchart TD
P[Prometheus] -->|evaluasi alert rules| P
P -->|alert firing| AM[Alertmanager]
AM --> G1{"Grouping<br/>by alertname+env+job"}
G1 --> G2{"Dedup<br/>cluster-wide"}
G2 --> R{"Routing tree<br/>matchers"}
R -->|"severity=critical"| PD["PagerDuty<br/>on-call paging"]
R -->|"severity=warning"| SL["Slack<br/>#alerts-warning"]
R -->|"environment=production"| SP["Slack<br/>#alerts-production"]
R -->|"job=db"| SD["Slack<br/>#team-database"]
R -->|digest harian| EM["Email<br/>nightly digest"]
AM -. "inhibit" .-> AM
AM -. "silence" .-> AM
Tiga fitur inti Alertmanager yang membedakannya dari “kirim notifikasi mentah” adalah grouping (menggabungkan alert yang berkaitan menjadi satu notifikasi), inhibition (menekan alert turunan ketika alert induk sudah firing), and silencing (menyembunyikan alert untuk waktu tertentu, biasanya saat maintenance). Ketiganya akan dibahas mendalam di bagian selanjutnya.
Instalasi dan Konfigurasi Alertmanager #
Role Ansible untuk Alertmanager harus meng-handle tiga hal secara terpisah: download binary, deploy konfigurasi, dan setup systemd service. Pemisahan ini membuat setiap komponen bisa di-redeploy tanpa mengulang yang lain:
# roles/alertmanager/tasks/main.yml
---
- name: Buat user alertmanager
user:
name: alertmanager
system: true
shell: /usr/sbin/nologin
home: /var/lib/alertmanager
create_home: true
- name: Download Alertmanager
get_url:
url: >
https://github.com/prometheus/alertmanager/releases/download/
v{{ alertmanager_version }}/alertmanager-{{ alertmanager_version }}.linux-amd64.tar.gz
dest: /tmp/alertmanager.tar.gz
checksum: "sha256:{{ alertmanager_checksum }}"
- name: Extract dan install binary
unarchive:
src: /tmp/alertmanager.tar.gz
dest: /tmp/
remote_src: true
- name: Salin binary ke /usr/local/bin
copy:
src: "/tmp/alertmanager-{{ alertmanager_version }}.linux-amd64/alertmanager"
dest: /usr/local/bin/alertmanager
owner: root
group: root
mode: '0755'
remote_src: true
- name: Buat direktori konfigurasi dan data
file:
path: "{{ item }}"
state: directory
owner: alertmanager
group: alertmanager
mode: '0750'
loop:
- /etc/alertmanager
- /etc/alertmanager/templates
- /var/lib/alertmanager
- name: Deploy konfigurasi Alertmanager
template:
src: alertmanager.yml.j2
dest: /etc/alertmanager/alertmanager.yml
owner: alertmanager
group: alertmanager
mode: '0640'
validate: "alertmanager --config.file=%s --storage.path=/tmp/amtest check-config"
notify: Restart Alertmanager
- name: Deploy systemd unit file
template:
src: alertmanager.service.j2
dest: /etc/systemd/system/alertmanager.service
notify:
- Reload systemd
- Restart Alertmanager
- name: Jalankan Alertmanager
systemd:
name: alertmanager
state: started
enabled: true
Perhatikan penggunaan validate di task konfigurasi — Ansible akan menjalankan alertmanager --config.file=%s --storage.path=/tmp/amtest check-config terhadap file yang baru di-render sebelum memindahkannya ke tempat final. Jika sintaks YAML salah atau field tidak valid, Ansible akan gagal sebelum konfigurasi rusak menimpa yang lama. Pola ini wajib untuk semua konfigurasi service yang kritis.
Konfigurasi Routing dan Notifikasi #
Routing tree adalah otak dari Alertmanager. Ia menentukan kepada siapa dan di mana alert tertentu harus sampai. Konfigurasi di bawah ini menunjukkan pola bertingkat yang umum di perusahaan dengan multiple environment dan multiple team:
{# roles/alertmanager/templates/alertmanager.yml.j2 #}
global:
resolve_timeout: 5m
slack_api_url: "{{ vault_slack_webhook_url }}"
pagerduty_url: "https://events.pagerduty.com/v2/enqueue"
smtp_smarthost: "{{ smtp_host }}:{{ smtp_port }}"
smtp_from: "alertmanager@{{ mail_domain }}"
smtp_require_tls: true
templates:
- /etc/alertmanager/templates/*.tmpl
route:
# Default receiver jika tidak ada route yang cocok
receiver: slack-warnings
group_by: ['alertname', 'environment', 'job']
group_wait: 30s # Tunggu 30 detik sebelum kirim notifikasi pertama
group_interval: 5m # Kirim update setiap 5 menit jika ada alert baru dalam grup
repeat_interval: 4h # Ulangi notifikasi setiap 4 jam jika belum resolved
routes:
# Alert critical → PagerDuty (untuk on-call duty)
- matchers:
- severity = critical
receiver: pagerduty-critical
repeat_interval: 1h # Ulangi lebih sering untuk critical
continue: true # Tetap evaluasi route di bawahnya juga
# Alert untuk environment production → channel terpisah
- matchers:
- environment = production
- severity =~ "warning|critical"
receiver: slack-production
continue: true # Lanjutkan ke route berikutnya juga
# Alert database → team database
- matchers:
- job =~ "postgresql|mysql|redis"
receiver: slack-database-team
continue: true
# Nightly digest untuk alert informational
- matchers:
- severity = info
receiver: email-digest
repeat_interval: 24h
active_time_intervals:
- business-hours
receivers:
- name: slack-warnings
slack_configs:
- channel: "#alerts-warning"
send_resolved: true
title: >
{{ '{{' }} template "slack.title" . {{ '}}' }}
text: >
{{ '{{' }} template "slack.text" . {{ '}}' }}
actions:
- type: button
text: "Acknowledge"
url: "{{ '{{' }} .CommonAnnotations.ack_url {{ '}}' }}"
- type: button
text: "Runbook"
url: "{{ '{{' }} .CommonAnnotations.runbook_url {{ '}}' }}"
- name: slack-production
slack_configs:
- channel: "#alerts-production"
send_resolved: true
icon_emoji: ":fire:"
mention_users:
- "{{ vault_oncall_user_id }}"
- name: pagerduty-critical
pagerduty_configs:
- routing_key: "{{ vault_pagerduty_routing_key }}"
description: >
{{ '{{' }} template "pagerduty.description" . {{ '}}' }}
severity: critical
details:
environment: "{{ '{{' }} .CommonLabels.environment {{ '}}' }}"
service: "{{ '{{' }} .CommonLabels.job {{ '}}' }}"
runbook: "{{ '{{' }} .CommonAnnotations.runbook_url {{ '}}' }}"
- name: slack-database-team
slack_configs:
- channel: "#team-database"
send_resolved: true
mention_users:
- "{{ vault_db_oncall_user_id }}"
- name: email-digest
email_configs:
- to: "{{ alerts_digest_recipients | join(',') }}"
send_resolved: true
headers:
Subject: "[Daily Alert Digest] {{ '{{' }} .CommonLabels.environment {{ '}}' }}"
inhibit_rules:
# Jika ada alert critical dari host yang sama, suppress alert warning-nya
- source_matchers:
- severity = critical
target_matchers:
- severity = warning
equal: ['instance', 'job']
# Jika ada alert "HostDown", suppress semua alert metrik dari host tersebut
- source_matchers:
- alertname = HostDown
target_matchers:
- severity =~ "warning|critical"
equal: ['instance']
# Jika ada alert "ClusterUnreachable", suppress semua alert per-node
- source_matchers:
- alertname = ClusterUnreachable
target_matchers:
- alertname =~ "Node.*|Host.*"
equal: ['cluster']
time_intervals:
- name: business-hours
time_intervals:
- weekdays: ['monday:friday']
times:
- start_time: '08:00'
end_time: '18:00'
Parameter penting yang perlu kita pahami:
group_by— label-label yang digunakan untuk menggabungkan alert. Alert denganalertname,environment, danjobyang sama akan dikirim sebagai satu notifikasi, bukan tiga notifikasi terpisah.group_wait— jeda setelah alert pertama firing sebelum notifikasi pertama dikirim. Berguna untuk menunggu apakah lebih banyak alert akan muncul yang bisa digabung.group_interval— seberapa sering notifikasi tambahan dikirim jika ada alert baru dalam grup yang sama.repeat_interval— seberapa sering notifikasi diulang untuk alert yang masih firing. Set lebih pendek untukcritical(1 jam) dan lebih panjang untukwarning(4-24 jam).continue: true— evaluasi route di bawahnya juga. Tanpacontinue, route pertama yang cocok akan menghentikan evaluasi.inhibit_rules— saat ada alert sumber yang firing, alert target akan di-suppress jika labelequalcocok.
Sequence Diagram: Alur Alert dari Deteksi ke Notifikasi #
Untuk memahami bagaimana semua komponen bekerja sama, perhatikan sequence di bawah ini. Dimulai dari Prometheus yang mengevaluasi rule, hingga alert muncul di channel yang tepat:
sequenceDiagram
participant App as Aplikasi
participant Prom as Prometheus
participant AM as Alertmanager
participant Slack as Slack
participant PD as PagerDuty
App->>Prom: "Expose /metrics (scrape setiap 15s)"
Prom->>Prom: "Evaluasi alert rules<br/>(setiap 15s)"
Note over Prom: "Alert rule:<br/>expr: error_rate > 5%<br/>for: 5m"
Prom->>Prom: "Status: PENDING (kurang dari 5m)"
Prom->>Prom: "Status: FIRING (setelah 5m)"
Prom->>AM: "POST /api/v1/alerts<br/>(firing alert)"
AM->>AM: "Group by alertname+env+job"
AM->>AM: Apply inhibition rules
AM->>AM: Check active silences
AM->>AM: Apply routing tree
AM->>AM: "group_wait 30s (tunggu alert lain)"
AM->>Slack: "POST webhook ke #alerts-warning"
AM->>PD: POST event ke PagerDuty
Slack-->>AM: 200 OK
PD-->>AM: 202 Accepted
Note over Prom,AM: "Repeat interval 4h<br/>selama alert masih firing"
Prom->>AM: "Status update (masih firing)"
AM->>Slack: "Kirim reminder (resolved=false)"
Perhatikan dua hal kritis: (1) for: 5m di alert rule membuat alert hanya firing setelah kondisi bertahan 5 menit — ini mencegah false alarm dari spike sesaat. (2) group_wait di Alertmanager menunggu 30 detik sebelum mengirim, memberi kesempatan alert lain yang berkaitan untuk digabung.
Template Notifikasi yang Informatif #
Alert yang hanya bilang “something went wrong” tidak berguna. Template yang baik menyertakan semua informasi yang dibutuhkan untuk memulai investigasi — host, deskripsi, waktu mulai, dan link ke runbook. Berikut template Slack yang memisahkan informasi ke field-field terstruktur:
{# /etc/alertmanager/templates/slack.tmpl #}
{{ '{{' }} define "slack.title" {{ '}}' }}
[{{ '{{' }} .Status | toUpper {{ '}}' }}{{ '{{' }} if eq .Status "firing" {{ '}}' }}:{{ '{{' }} .Alerts.Firing | len {{ '}}' }}{{ '{{' }} end {{ '}}' }}]
{{ '{{' }} .CommonLabels.alertname {{ '}}' }} — {{ '{{' }} .CommonLabels.environment {{ '}}' }}
{{ '{{' }} end {{ '}}' }}
{{ '{{' }} define "slack.text" {{ '}}' }}
{{ '{{' }} range .Alerts {{ '}}' }}
*Host:* `{{ '{{' }} .Labels.instance {{ '}}' }}`
*Service:* {{ '{{' }} .Labels.job {{ '}}' }}
*Severity:* {{ '{{' }} .Labels.severity | toUpper {{ '}}' }}
*Keterangan:* {{ '{{' }} .Annotations.description {{ '}}' }}
*Dimulai:* {{ '{{' }} .StartsAt.Format "2006-01-02 15:04:05 WIB" {{ '}}' }}
*Runbook:* <{{ '{{' }} .Annotations.runbook_url {{ '}}' }}|Buka Runbook>
{{ '{{' }} if .Annotations.dashboard_url {{ '}}' }}
*Dashboard:* <{{ '{{' }} .Annotations.dashboard_url {{ '}}' }}|Lihat Dashboard>
{{ '{{' }} end {{ '}}' }}
---
{{ '{{' }} end {{ '}}' }}
{{ '{{' }} end {{ '}}' }}
Template PagerDuty harus lebih ringkas karena tampilannya berbeda — fokus pada deskripsi singkat (satu kalimat), severity (paging policy tergantung dari sini), dan payload details yang muncul di incident page:
{# /etc/alertmanager/templates/pagerduty.tmpl #}
{{ '{{' }} define "pagerduty.description" {{ '}}' }}
[{{ '{{' }} .Status | toUpper {{ '}}' }}] {{ '{{' }} .CommonLabels.alertname {{ '}}' }} on {{ '{{' }} .CommonLabels.environment {{ '}}' }}: {{ '{{' }} .CommonAnnotations.summary {{ '}}' }}
{{ '{{' }} end {{ '}}' }}
{{ '{{' }} define "pagerduty.details" {{ '}}' }}
{
"firing": {{ '{{' }} .Alerts.Firing | len {{ '}}' }},
"resolved": {{ '{{' }} .Alerts.Resolved | len {{ '}}' }},
"environment": "{{ '{{' }} .CommonLabels.environment {{ '}}' }}",
"service": "{{ '{{' }} .CommonLabels.job {{ '}}' }}",
"instance": "{{ '{{' }} (index .Alerts 0).Labels.instance {{ '}}' }}",
"runbook_url": "{{ '{{' }} .CommonAnnotations.runbook_url {{ '}}' }}"
}
{{ '{{' }} end {{ '}}' }}
Pisahkan template per channel: Slack butuh Markdown dan interaktif (mention, button), PagerDuty butuh payload JSON yang bisa di-parse automation, email butuh plain text. Hindari satu template besar yang mencoba melayani semuanya — formatting akan saling bentrok.
Mengelola Silences dengan Ansible #
Saat ada maintenance terjadwal, kita tidak ingin alert dari node yang sedang di-restart membanjiri channel. Silence adalah cara resmi untuk memberitahu Alertmanager: “jangan kirim alert yang cocok dengan matcher ini selama periode tertentu”. Ansible bisa mengotomasi lifecycle silence — dibuat sebelum maintenance, di-expire setelahnya:
# playbooks/create-silence.yml
---
- name: Buat silence di Alertmanager selama maintenance
hosts: localhost
vars:
alertmanager_url: "http://alertmanager.internal:9093"
silence_duration_hours: 4
silence_comment: "Maintenance terjadwal — {{ ansible_date_time.date }}"
tasks:
- name: Hitung waktu berakhir silence
set_fact:
silence_end: >-
{{ (ansible_date_time.epoch | int + silence_duration_hours * 3600) | strftime('%Y-%m-%dT%H:%M:%S.000Z') }}
- name: Buat silence di Alertmanager
uri:
url: "{{ alertmanager_url }}/api/v2/silences"
method: POST
body_format: json
body:
matchers:
- name: environment
value: "{{ env }}"
isRegex: false
- name: job
value: "{{ silence_job | default('.*') }}"
isRegex: "{{ silence_job is regex('\\*') or silence_job is regex('\\.') }}"
startsAt: "{{ ansible_date_time.iso8601 }}"
endsAt: "{{ silence_end }}"
comment: "{{ silence_comment }}"
createdBy: "ansible-automation"
status_code: 200
register: silence_result
no_log: true
- name: Tampilkan ID silence yang dibuat
debug:
msg: "Silence dibuat dengan ID: {{ silence_result.json.silenceID }}"
- name: Simpan ID silence untuk di-expire nanti
copy:
content: "{{ silence_result.json.silenceID }}"
dest: "/tmp/silence-{{ env }}-{{ silence_job | default('all') }}.id"
mode: '0600'
delegate_to: localhost
Playbook untuk menghapus silence setelah maintenance selesai:
# playbooks/expire-silence.yml
---
- name: Expire silence setelah maintenance selesai
hosts: localhost
vars:
alertmanager_url: "http://alertmanager.internal:9093"
silence_id_file: "/tmp/silence-{{ env }}-{{ silence_job | default('all') }}.id"
tasks:
- name: Baca ID silence
slurp:
src: "{{ silence_id_file }}"
register: silence_id_b64
ignore_errors: true
- name: Decode ID silence
set_fact:
silence_id: "{{ silence_id_b64.content | b64decode | trim }}"
when: silence_id_b64 is succeeded
- name: Expire silence via API
uri:
url: "{{ alertmanager_url }}/api/v2/silences/{{ silence_id }}"
method: DELETE
status_code: 200
when: silence_id_b64 is succeeded
ignore_errors: true
- name: Hapus file ID silence
file:
path: "{{ silence_id_file }}"
state: absent
Untuk integrasi dengan workflow maintenance, panggil playbook create-silence.yml sebelum maintenance dimulai, dan expire-silence.yml setelahnya selesai — baik secara manual atau sebagai bagian dari job Jenkins/GitHub Actions.
Decision Tree: Pilih Severity yang Tepat #
Severity yang salah adalah sumber utama alert fatigue. Aturan praktisnya: critical = butuh respons dalam 15 menit (paging), warning = butuh respons dalam jam kerja (ticket), info = tidak butuh respons langsung (hanya untuk konteks). Decision tree berikut membantu tim memutuskan severity saat membuat alert baru:
flowchart TD
A["Buat alert baru"] --> B{"Apakah layanan<br/>tidak bisa digunakan<br/>oleh user?"}
B --|"Ya"| C{"Apakah ini<br/>membutuhkan<br/>respon 24/7?"}
C --|"Ya"| D["severity: critical<br/>route ke PagerDuty"]
C --|"Tidak"| E["severity: warning<br/>route ke Slack #alerts"]
B --|"Tidak"| F{"Apakah ini bisa<br/>menjadi masalah besar<br/>jika dibiarkan?"}
F --|"Ya"| G{"Ada dampak<br/>performance atau<br/>risiko kehilangan data?"}
G --|"Ya"| E
G --|"Tidak"| H["severity: warning<br/>route ke Slack channel tim"]
F --|"Tidak"| I{"Apakah ini informasi<br/>berguna untuk<br/>investigasi?"}
I --|"Ya"| J["severity: info<br/>route ke email digest"]
I --|"Tidak"| K["Hapus alert<br/>tidak perlu"]
Aturan yang sering dilanggar: alert “info” tetap akan dikirim ke channel jika ada route yang cocok. Jika kita membuat alertseverity=infotetapi tidak memiliki route yang menerimaseverity=info, alert tersebut akan jatuh ke default receiver (slack-warnings) dan menjadi noise. Selalu buat receiver khusus untukinfojika kita memang ingin mengirimkan alert info.
Perbandingan Channel Notifikasi #
Tidak semua alert cocok dikirim ke channel yang sama. Tabel ini membandingkan tiga channel utama dan kapan masing-masing paling tepat:
| Kanal | Latensi | Cocok untuk | Kelebihan | Kekurangan |
|---|---|---|---|---|
| PagerDuty | Detik (paging 24/7) | Insiden production yang butuh respons immediate | Paging ke on-call, eskalasi otomatis, ack tracking, timeline insiden | Mahal per user, harus disiplin pakai — kalau untuk warning akan jadi alarm fatigue parah |
| Slack | Detik (mention) | Warning, info, alert tim spesifik | Real-time, mudah di-acknowledge via thread, integrasi dengan workflow tim | Bisa tenggelam di channel ramai, tidak ada eskalasi jika tidak ada yang lihat |
| Menit sampai jam | Digest harian, audit trail, alert non-kritis | Tidak mengganggu on-call, bisa di-batch, archival natural | Latensi tinggi, tidak real-time, butuh filter inbox yang disiplin |
Rekomendasi praktis: PagerDuty hanya untuk severity=critical yang benar-benar paging, Slack untuk warning dan info real-time, email untuk nightly digest alert info yang perlu dilihat saat working hours. Mencampur ketiganya tanpa aturan hanya akan menghasilkan alert yang diabaikan di semua channel.
Pola Mengurangi Alert Fatigue #
Alert fatigue terjadi saat tim menerima terlalu banyak notifikasi yang tidak actionable. Beberapa pola yang terbukti mengurangi noise secara signifikan:
# Alert rules yang baik memiliki:
# 1. Threshold yang bermakna (bukan arbitrer)
# 2. Durasi 'for' yang cukup (menghindari flapping)
# 3. Severity yang tepat
# 4. Annotation yang actionable
# 5. Link ke runbook
# ANTI-PATTERN: alert yang terlalu sensitif
- alert: HighCPU
expr: cpu_usage > 80
# Tidak ada 'for' — alert setiap kali ada spike sesaat
# Threshold 80% terlalu rendah — CPU sering melewati 80% saat burst normal
# Tidak ada runbook — on-call tidak tahu harus apa
# BENAR: alert dengan durasi, threshold bermakna, dan runbook
- alert: SustainedHighCPU
expr: >
avg by(instance) (
rate(node_cpu_seconds_total{mode!="idle"}[5m])
) * 100 > 90
for: 15m # Harus bertahan 15 menit sebelum alert
labels:
severity: warning
annotations:
summary: "CPU usage tinggi berkelanjutan di {{ '{{' }} $labels.instance {{ '}}' }}"
description: "CPU {{ '{{' }} $value | printf "%.1f" {{ '}}' }}% selama 15 menit terakhir"
runbook_url: "https://wiki.company.com/runbooks/high-cpu"
# ANTI-PATTERN: alert yang hanya menyebut masalah tanpa konteks
- alert: DiskFull
expr: node_filesystem_avail_bytes{mountpoint="/"} < 1000000000
annotations:
summary: "Disk hampir penuh"
# BENAR: alert yang menyebut impact dan langkah selanjutnya
- alert: DiskSpaceCritical
expr: >
(node_filesystem_avail_bytes{mountpoint="/"}
/ node_filesystem_size_bytes{mountpoint="/"}) < 0.05
for: 10m
labels:
severity: critical
annotations:
summary: "Disk kritis di {{ '{{' }} $labels.instance {{ '}}' }} (sisa {{ '{{' }} $value | humanizePercentage {{ '}}' }})"
description: >
Disk hanya tersisa {{ '{{' }} $value | humanizePercentage {{ '}}' }}.
Service akan mulai gagal dalam 30 menit jika tidak ada tindakan.
runbook_url: "https://wiki.company.com/runbooks/disk-full"
dashboard_url: "https://grafana.company.com/d/disk-usage?var-host={{ '{{' }} $labels.instance {{ '}}' }}"
# ANTI-PATTERN: alert yang duplikat (satu masalah memicu banyak alert)
# Disk usage + inode usage + write latency + service crash — semua firing bersamaan
# saat disk benar-benar penuh
# BENAR: gunakan inhibition untuk suppress alert turunan
# Di alertmanager.yml
inhibit_rules:
- source_matchers:
- alertname = DiskSpaceCritical
target_matchers:
- alertname =~ "ServiceCrashed|HighLatency|WriteTimeout"
equal: ['instance']
# Saat DiskSpaceCritical firing, alert turunan dari instance yang sama di-suppress
# On-call hanya melihat SATU alert utama, bukan 5
Tiga prinsip utama dari contoh di atas: (1) threshold harus berdasarkan dampak, bukan angka bulat yang terlihat bagus. (2) setiap alert punya for: duration yang realistis — minimum 5 menit untuk warning, 2-3 menit untuk critical. (3) inhibit_rules membersihkan ledakan alert saat ada masalah induk.
High Availability Alertmanager #
Alertmanager mendukung clustering untuk high availability. Tanpa clustering, jika satu instance Alertmanager mati, alert yang firing tidak akan terkirim sampai instance kembali — ini bisa berakibat fatal. Konfigurasi cluster mode menggunakan gossip protocol:
{# Tambahkan di alertmanager.yml.j2 untuk mode cluster #}
{% if alertmanager_cluster_enabled | default(false) %}
cluster:
listen-address: ""
# Cluster peers di-generate dari inventory group alertmanager
{% for host in groups['alertmanager'] %}
- {{ hostvars[host]['ansible_default_ipv4']['address'] }}:9094
{% endfor %}
{% endif %}
Untuk mengotomasi setup cluster Alertmanager dengan Ansible:
# roles/alertmanager/tasks/cluster.yml
---
- name: Buka port cluster Alertmanager
ufw:
rule: allow
port: '9094'
proto: tcp
when: ansible_facts['os_family'] == 'Debian'
- name: Deploy konfigurasi dengan cluster enabled
template:
src: alertmanager.yml.j2
dest: /etc/alertmanager/alertmanager.yml
owner: alertmanager
group: alertmanager
mode: '0640'
vars:
alertmanager_cluster_enabled: true
notify: Restart Alertmanager
Alertmanager cluster menggunakan gossip protocol untuk sinkronisasi state — saat satu node menerima silence, semua node akan mengetahuinya. Saat ada alert baru, salah satu node akan mengirim notifikasi (berdasarkan konsensus), sehingga tidak ada duplikat.
Testing Alert Rules dengan Prometheus #
Sebelum deploy ke production, alert rules harus ditest untuk memastikan ekspresi PromQL benar dan threshold masuk akal. promtool menyediakan dua command penting:
# Validasi sintaks dan struktur alert rules
promtool check rules /etc/prometheus/rules/*.yml
# Unit test alert rules dengan skenario PromQL
promtool test rules /etc/prometheus/tests/alerts.test.yml
File test alert rules dalam format YAML mendefinisikan skenario input dan output yang diharapkan:
# tests/alerts.test.yml
rule_files:
- /etc/prometheus/rules/slo-recording.yml
evaluation_interval: 1m
tests:
# Test 1: Alert tidak firing saat error rate di bawah threshold
- interval: 1m
input_series:
- series: 'http_requests_total{job="api",status="200"}'
values: '1000x10'
- series: 'http_requests_total{job="api",status="500"}'
values: '5x10' # 0.5% error rate
alert_rule_test:
- eval_time: 10m
alertname: HighErrorBudgetBurnRate
exp_alerts: [] # Tidak boleh firing di error rate 0.5%
# Test 2: Alert firing saat burn rate tinggi
- interval: 1m
input_series:
- series: 'http_requests_total{job="api",status="200"}'
values: '100x10'
- series: 'http_requests_total{job="api",status="500"}'
values: '900x10' # 90% error rate — disaster
alert_rule_test:
- eval_time: 10m
alertname: HighErrorBudgetBurnRate
exp_alerts:
- exp_labels:
severity: critical
job: api
exp_annotations:
summary: "Error budget terbakar cepat"
runbook_url: "https://wiki.company.com/runbooks/error-budget-burn"
Jalankan test ini di CI pipeline setiap kali alert rules diubah:
# .github/workflows/alert-tests.yml (konsep)
- name: Test alert rules
run: |
docker run --rm -v $(pwd):/etc/prometheus \
prom/prometheus:latest \
promtool test rules /etc/prometheus/tests/alerts.test.yml
Artikel SLO & SLA membahas lebih detail tentang alert berbasis error budget yang sudah kita lihat pada test cases di atas. Untuk konteks yang lebih luas tentang alasan alert tertentu dibuat, lihat juga Monitoring yang membahas alert rules sebagai kode, dan Incident Response yang membahas runbook yang harus ditautkan pada setiap alert.
Ringkasan #
- Alertmanager menangani routing, grouping, deduplication, inhibition, dan silencing — pisahkan tanggung jawab ini dari Prometheus yang hanya bertugas deteksi.
group_wait,group_interval, danrepeat_intervaladalah tiga knob utama yang menentukan kapan dan seberapa sering notifikasi dikirim — set sesuai severity dan urgensi.inhibit_rulesmenekan alert turunan saat alert induk sudah firing — misalnya, suppress warning saat critical dari host yang sama sudah aktif, atau suppress semua alert service saatHostDownaktif.- Severity yang tepat adalah critical untuk paging 24/7, warning untuk ticket jam kerja, info untuk digest harian — pencampuran tanpa aturan hanya akan menghasilkan alert yang diabaikan di semua channel.
- Template notifikasi yang baik menyertakan: host, deskripsi masalah, waktu mulai, link ke runbook, dan link ke dashboard — semua yang dibutuhkan untuk memulai investigasi tanpa harus buka tools lain.
- Gunakan
for:di alert rules — alert tanpa durasi minimum akan fire setiap spike sesaat dan menjadi noise. Minimum realistis: 2-3 menit untuk critical, 5-15 menit untuk warning.- Silences via Ansible memungkinkan maintenance terjadwal tanpa banjir notifikasi false alarm — buat silence sebelum mulai maintenance, expire setelah selesai.
- High availability Alertmanager wajib di production — cluster mode dengan gossip protocol memastikan alert terkirim meski satu node mati.