Alerting #

Alert yang baik adalah alert yang ketika muncul di jam 3 pagi, engineer yang bertugas tahu persis apa yang harus dilakukan. Alert yang buruk adalah kebisingan — terlalu banyak, terlalu sensitif, atau tidak actionable — yang membuat on-call engineer kehilangan kepercayaan pada sistem monitoring dan akhirnya mengabaikan alert. Merancang alerting yang tepat sama pentingnya dengan mengumpulkan metrik yang tepat.

Prinsip Alert yang Baik #

Alert yang baik memenuhi kriteria:

  1. Actionable
     Ada sesuatu yang bisa dilakukan manusia sebagai respons
     Alert "CPU tinggi" tanpa threshold yang bermakna tidak actionable
     Alert "Error rate >5% selama 5 menit" → investigasi, rollback, atau scale

  2. Symptom-based, bukan cause-based
     Alert berdasarkan dampak yang dirasakan pengguna, bukan penyebab internal
     Buruk: "Pod baru tidak bisa di-schedule" (internal)
     Baik:  "API error rate >1% selama 5 menit" (dampak pada pengguna)

  3. Cukup sensitif, tidak terlalu sensitif
     Terlalu sensitif: banyak false positive → alert fatigue
     Kurang sensitif: masalah nyata lolos tidak terdeteksi
     Gunakan for: clause untuk pastikan kondisi persisten sebelum alert

  4. Severity yang tepat
     Critical/Page: segera butuh tindakan, bisa bangunkan on-call
     Warning/Ticket: perlu perhatian tapi tidak malam ini

PrometheusRule: Mendefinisikan Alert #

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: api-alerts
  namespace: monitoring
  labels:
    release: prometheus         # agar Prometheus Operator pick up
spec:
  groups:
  - name: api.critical
    rules:
    # Alert: error rate tinggi
    - alert: HighErrorRate
      expr: |
        sum(rate(http_requests_total{
          namespace="production",
          status_code=~"5.."
        }[5m]))
        /
        sum(rate(http_requests_total{
          namespace="production"
        }[5m]))
        > 0.05             # lebih dari 5% error rate        
      for: 3m              # kondisi harus persisten 3 menit sebelum alert
      labels:
        severity: critical
        team: backend
      annotations:
        summary: "Error rate tinggi di production API"
        description: |
          Error rate: {{ $value | humanizePercentage }}
          Jauh di atas threshold 5%.          
        runbook_url: "https://wiki.company.com/runbooks/high-error-rate"

    # Alert: latensi p99 tinggi
    - alert: HighP99Latency
      expr: |
        histogram_quantile(0.99,
          sum by (le) (
            rate(http_request_duration_seconds_bucket{
              namespace="production"
            }[5m])
          )
        ) > 2              # p99 di atas 2 detik        
      for: 5m
      labels:
        severity: warning
        team: backend
      annotations:
        summary: "P99 latency tinggi"
        description: "P99 saat ini {{ $value }}s, threshold 2s"

  - name: kubernetes.critical
    rules:
    # Alert: Pod crash loop
    - alert: PodCrashLooping
      expr: |
        increase(kube_pod_container_status_restarts_total{
          namespace="production"
        }[15m]) > 3        
      for: 0m              # segera alert, tidak perlu for
      labels:
        severity: critical
      annotations:
        summary: "Pod {{ $labels.pod }} crash looping"
        description: "Container {{ $labels.container }} restart {{ $value }} kali dalam 15 menit"

    # Alert: Deployment tidak mencapai desired replicas
    - alert: DeploymentReplicasMismatch
      expr: |
        kube_deployment_status_replicas_available{namespace="production"}
        !=
        kube_deployment_spec_replicas{namespace="production"}        
      for: 10m
      labels:
        severity: warning
      annotations:
        summary: "Deployment {{ $labels.deployment }} tidak punya cukup replicas"
        description: |
          Tersedia: {{ $value }} replicas
          Diinginkan: {{ query "kube_deployment_spec_replicas{deployment=\"%s\"}" $labels.deployment | first | value }}          

    # Alert: Node hampir kehabisan disk
    - alert: NodeDiskPressure
      expr: |
        (
          node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"}
          / node_filesystem_size_bytes{mountpoint="/"}
        ) < 0.10           # kurang dari 10% tersisa        
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Disk hampir penuh di node {{ $labels.instance }}"
        description: "Hanya tersisa {{ $value | humanizePercentage }} ruang disk"

Konfigurasi Alertmanager #

Alertmanager menerima alert dari Prometheus dan memutuskan ke mana harus dikirim:

# alertmanager-config.yaml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/...'

route:
  receiver: 'default-slack'
  group_by: ['alertname', 'namespace']
  group_wait: 30s           # tunggu 30 detik sebelum kirim grup pertama
  group_interval: 5m        # interval antara notifikasi untuk alert yang sama
  repeat_interval: 4h       # kirim ulang jika alert belum resolved setelah 4 jam
  routes:
  # Critical alert → PagerDuty (membangunkan on-call)
  - match:
      severity: critical
    receiver: 'pagerduty-critical'
    repeat_interval: 30m

  # Warning → Slack channel berbeda
  - match:
      severity: warning
    receiver: 'slack-warning'
    group_wait: 5m

receivers:
- name: 'default-slack'
  slack_configs:
  - channel: '#alerts-production'
    title: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
    text: |
      {{ range .Alerts }}
      *Alert:* {{ .Annotations.summary }}
      *Severity:* {{ .Labels.severity }}
      *Description:* {{ .Annotations.description }}
      *Runbook:* {{ .Annotations.runbook_url }}
      {{ end }}      
    send_resolved: true

- name: 'pagerduty-critical'
  pagerduty_configs:
  - routing_key: '<pagerduty-integration-key>'
    description: '{{ .CommonAnnotations.summary }}'
    client: 'AlertManager'
    client_url: 'https://grafana.company.com'

- name: 'slack-warning'
  slack_configs:
  - channel: '#alerts-warning'
    text: '{{ .CommonAnnotations.description }}'
    send_resolved: true

Silencing dan Inhibition #

# Silence alert selama maintenance window
amtool silence add \
  alertname="HighErrorRate" \
  --duration=2h \
  --comment="Scheduled maintenance: database upgrade"

# Lihat semua silence aktif
amtool silence query

# Hapus silence
amtool silence expire <silence-id>
# Inhibition: suppress warning jika critical sudah aktif
# (jangan kirim warning jika critical yang lebih penting sudah dipagingkan)
inhibit_rules:
- source_match:
    severity: 'critical'
  target_match:
    severity: 'warning'
  equal: ['alertname', 'namespace']
  # Jika ada critical alert di namespace X, suppress warning di namespace X

SLO-Based Alerting #

Alih-alih alert berdasarkan threshold arbitrari, alert berdasarkan SLO (Service Level Objective) lebih bermakna:

SLO definition:
  "API harus menyelesaikan 99.9% request tanpa error dalam rolling 30 hari"

Error budget = 100% - 99.9% = 0.1% error yang diperbolehkan per 30 hari
  = 0.001 × 30 × 24 × 60 = 43.2 menit downtime/error diperbolehkan per bulan

Alert saat error budget terbakar terlalu cepat:

Tier 1 (Critical): burn rate > 14.4x dalam 1 jam
  → Menghabiskan 2% error budget dalam 1 jam
  → Harus segera ditangani

Tier 2 (Warning): burn rate > 6x dalam 6 jam
  → Menghabiskan 5% error budget dalam 6 jam
  → Perlu investigasi hari ini
# Multi-window, multi-burn-rate alert untuk SLO
- alert: SLOErrorBudgetBurnRateCritical
  expr: |
    (
      sum(rate(http_requests_total{status_code=~"5.."}[1h]))
      / sum(rate(http_requests_total[1h]))
    ) > (14.4 * 0.001)       # 14.4x burn rate, SLO 99.9% = 0.1% error budget
    and
    (
      sum(rate(http_requests_total{status_code=~"5.."}[5m]))
      / sum(rate(http_requests_total[5m]))
    ) > (14.4 * 0.001)       # konfirmasi dengan window pendek juga    
  for: 2m
  labels:
    severity: critical
  annotations:
    summary: "SLO error budget terbakar terlalu cepat"

Ringkasan #

  • Alert berdasarkan dampak pengguna, bukan internal metric — “error rate >5%” lebih actionable dari “CPU high”; symptom-based alerting mengurangi noise.
  • for: N untuk menghindari false positive dari spike sementara — kondisi harus persisten selama N menit sebelum alert dikirim; for: 5m adalah nilai aman untuk sebagian besar alert.
  • Critical → PagerDuty, Warning → Slack — kriteria severity yang jelas mencegah alert fatigue; hanya bangunkan on-call jika benar-benar diperlukan.
  • Runbook URL di setiap alert — sertakan link ke dokumentasi langkah penanganan; engineer baru sekalipun bisa ikuti prosedur saat on-call.
  • Silencing selama maintenance — alert yang expected selama maintenance tidak perlu mengganggu on-call; buat silence sebelum window dimulai.
  • SLO-based alerting untuk produksi matang — alert berbasis error budget burn rate lebih bermakna dari threshold arbitrari dan langsung terhubung ke komitmen reliability ke pengguna.

← Sebelumnya: Metrics dan Prometheus   Berikutnya: Distributed Tracing →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact