DevOpsIndex

AlertManager

1. Architecture

Prometheus evaluates rules and pushes firing alerts to AlertManager, which deduplicates, groups, routes, and dispatches notifications.

flowchart LR
    P[Prometheus] -->|POST /alerts| AM[AlertManager]
    AM --> RT{Routing Tree}
    RT -->|severity=critical| PD[PagerDuty]
    RT -->|severity=warning| SL[Slack]
    RT -->|default| EM[Email]

2. Alert Lifecycle

An alert transitions through states based on the for: duration in the rule and whether it resolves.

stateDiagram-v2
    [*] --> Inactive: rule not firing
    Inactive --> Pending: condition true
    Pending --> Firing: for: duration met
    Pending --> Inactive: condition false
    Firing --> Resolved: condition false
    Resolved --> [*]
State Meaning
Inactive Rule condition is false
Pending Condition true, waiting for: duration
Firing Duration exceeded — alert sent
Resolved Condition cleared, resolve notification sent

3. Routing Tree

Routes are evaluated top-down; first match wins. Each route can override receiver, group_by, and timing.

flowchart TD
    G[global defaults] --> R[root route]
    R --> M1{team=infra?}
    M1 -->|yes| R1[pagerduty-infra]
    M1 -->|no| M2{severity=warning?}
    M2 -->|yes| R2[slack-warning]
    M2 -->|no| R3[default receiver]

Config example:

route:
  receiver: default-receiver
  group_by: [alertname, cluster]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers:
        - severity = critical
      receiver: pagerduty-critical
      continue: false
    - matchers:
        - severity = warning
      receiver: slack-warning

4. Grouping

Parameter Purpose Typical Value
group_by Labels that form a notification group [alertname, cluster, namespace]
group_wait Wait before sending first notification 30s
group_interval Wait before sending added/resolved alerts 5m
repeat_interval Re-notify if still firing 4h

Grouping prevents alert storms: 100 pods failing → 1 grouped notification.

5. Inhibition

Suppress derived/symptom alerts when the root cause is already firing. Inhibition rules match a source alert and silence matching target alerts.

inhibit_rules:
  - source_matchers:
      - alertname = "NodeDown"
    target_matchers:
      - alertname =~ "Pod.*"
    equal: [cluster, node]

If NodeDown fires for node=worker-1, all Pod* alerts on the same node are silenced.

6. Silences

Silences mute alerts matching a set of matchers for a time window. Created via UI or amtool.

Types:

Type Example use
Time-based Maintenance window (Sat 02:00–04:00)
Matcher-based Mute specific service during deploy
# Create silence for 2 hours on a specific service
amtool silence add alertname="HighErrorRate" service="payments" \
  --duration=2h --comment="Deploying payments v2.3"

# List active silences
amtool silence query

# Expire a silence
amtool silence expire <silence-id>

7. Complete alertmanager.yml

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alertmanager@example.com'
  smtp_auth_username: 'alertmanager'
  smtp_auth_password: 'secret'
  pagerduty_url: 'https://events.pagerduty.com/v2/enqueue'

templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  receiver: default-email
  group_by: [alertname, cluster, namespace]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers:
        - severity = critical
      receiver: pagerduty-critical
      group_wait: 10s
      repeat_interval: 1h
      continue: false

    - matchers:
        - severity = warning
      receiver: slack-warning
      group_wait: 1m
      repeat_interval: 8h
      continue: false

    - matchers:
        - alertname = Watchdog
      receiver: null-receiver

receivers:
  - name: null-receiver

  - name: pagerduty-critical
    pagerduty_configs:
      - routing_key: '<PAGERDUTY_INTEGRATION_KEY>'
        description: '{{ template "pagerduty.default.description" . }}'
        severity: critical
        details:
          firing: '{{ .Alerts.Firing | len }}'
          cluster: '{{ .CommonLabels.cluster }}'

  - name: slack-warning
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/XXX/YYY/ZZZ'
        channel: '#alerts-warning'
        title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *Alert:* {{ .Annotations.summary }}
          *Severity:* {{ .Labels.severity }}
          *Details:* {{ range .Labels.SortedPairs }} {{ .Name }}={{ .Value }} {{ end }}
          {{ end }}
        send_resolved: true

  - name: default-email
    email_configs:
      - to: 'oncall@example.com'
        send_resolved: true

inhibit_rules:
  - source_matchers:
      - severity = critical
    target_matchers:
      - severity = warning
    equal: [alertname, cluster, namespace]

  - source_matchers:
      - alertname = NodeDown
    target_matchers:
      - alertname =~ "Pod.*"
    equal: [node]

8. Debugging

Config validation:

amtool check-config /etc/alertmanager/alertmanager.yml

Query active alerts:

# All firing alerts
amtool alert query

# Filter by label
amtool alert query severity=critical

# Against a specific AlertManager
amtool alert query --alertmanager.url=http://alertmanager:9093

API endpoints:

# List all alerts (v2 API)
curl http://alertmanager:9093/api/v2/alerts | jq .

# List active silences
curl http://alertmanager:9093/api/v2/silences | jq .

# AlertManager status
curl http://alertmanager:9093/api/v2/status | jq .

# Reload config (SIGHUP or POST)
curl -X POST http://alertmanager:9093/-/reload

Common issues:

Problem Check
Alerts not routing amtool config routes test severity=critical
Silence not working Verify matcher syntax with amtool silence query
No notifications sent Check amtool alert query — alert must be in AM first
Config errors on reload amtool check-config before applying