Share Notes

chundev

View the Project on GitHub latteouka/share-notes

Prometheus 告警查詢完整指南:從即時狀態到歷史回溯

日期:2026-03-22 環境:Prometheus + Alertmanager(kube-prometheus-stack)


TL;DR

Prometheus 提供三層告警查詢能力:Alertmanager API 看「正在發生的」、ALERTS metric 看「過去發生過的」、ALERTS_FOR_STATE 看「什麼時候開始的」。多數人只用第一層,結果已 resolve 的告警就查不到。掌握 query_range + ALERTS metric 就能回溯任意時間範圍內的告警歷史。


背景

在 K8s 叢集上跑 kube-prometheus-stack,某天收到告警通知(email 或 Slack),但打開監控 Dashboard 只看到一個永遠在 firing 的 Watchdog — 今天明明有告警,為什麼看不到?

原因:Alertmanager API 只回傳正在 firing 或 pending 的告警。一旦告警 resolve,就從 API 結果中消失了。要看歷史告警,需要用不同的查詢方式。


Prometheus 告警的三層查詢

層級 工具 查什麼 能看到已 resolve 的嗎?
1. Alertmanager API /api/v1/alerts 當前 firing/pending 的告警 ❌ 不能
2. ALERTS metric query_range 告警的時間序列 ✅ 可以回溯
3. ALERTS_FOR_STATE query 告警開始的 Unix timestamp ✅ 告警起始時間

第一層:Alertmanager API(即時狀態)

這是最常用的,也是多數 Dashboard 預設使用的。

查詢當前所有告警

curl -s http://<prometheus>:9090/api/v1/alerts | python3 -m json.tool

回傳結構:

{
  "status": "success",
  "data": {
    "alerts": [
      {
        "labels": { "alertname": "Watchdog", "severity": "none" },
        "annotations": { "summary": "..." },
        "state": "firing",
        "activeAt": "2026-03-01T06:11:02Z",
        "value": "1e+00"
      }
    ]
  }
}

查詢所有告警規則(含 inactive)

curl -s http://<prometheus>:9090/api/v1/rules | python3 -c "
import json, sys
data = json.load(sys.stdin)
for group in data['data']['groups']:
    for rule in group['rules']:
        if rule['type'] == 'alerting':
            state = rule['state']
            name = rule['name']
            alerts = rule.get('alerts', [])
            print(f'[{state.upper()}] {name} ({len(alerts)} active)')
"

⚠️ 限制/api/v1/alerts 只顯示 firingpending。告警 resolve 後就消失了 — 你無法知道「今天早上 10 點有什麼告警」。


第二層:ALERTS Metric(歷史回溯)

這是關鍵 — Prometheus 會自動產生一個叫 ALERTS 的合成時間序列(synthetic time series),記錄每個告警的狀態。

ALERTS Metric 的結構

ALERTS{alertname="KubeJobFailed", alertstate="firing", namespace="default", severity="warning"} 1

查詢過去 24 小時的所有告警

# 計算時間範圍
START=$(python3 -c "import time; print(int(time.time()) - 86400)")
END=$(python3 -c "import time; print(int(time.time()))")

# 查詢(排除永遠在 firing 的 Watchdog)
curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertstate="firing",alertname!="Watchdog"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=300"

格式化輸出

curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertstate="firing",alertname!="Watchdog"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=60" | python3 -c "
import json, sys
from datetime import datetime

data = json.load(sys.stdin)
for r in data['data']['result']:
    name = r['metric'].get('alertname', '?')
    severity = r['metric'].get('severity', '?')
    ns = r['metric'].get('namespace', '')
    job_name = r['metric'].get('job_name', '')
    values = r.get('values', [])
    if values:
        first = datetime.fromtimestamp(values[0][0]).strftime('%H:%M')
        last = datetime.fromtimestamp(values[-1][0]).strftime('%H:%M')
        duration_min = (values[-1][0] - values[0][0]) / 60
        print(f'[{severity}] {name}')
        print(f'  namespace: {ns}')
        if job_name:
            print(f'  job: {job_name}')
        print(f'  時間: {first} ~ {last} (持續 {duration_min:.0f} 分鐘)')
        print()
"

範例輸出:

[warning] KubeJobFailed
  namespace: kymo-task
  job: mission-notification-29569020
  時間: 09:46 ~ 10:30 (持續 44 分鐘)

查詢過去 7 天的告警

# 7 天 = 604800 秒,step 用 900(15 分鐘)避免回傳太多資料點
START=$(python3 -c "import time; print(int(time.time()) - 604800)")
END=$(python3 -c "import time; print(int(time.time()))")

curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertstate="firing",alertname!="Watchdog"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=900"

依嚴重性篩選

# 只看 critical
'query=ALERTS{alertstate="firing",severity="critical"}'

# 只看 warning
'query=ALERTS{alertstate="firing",severity="warning"}'

# 只看特定 namespace
'query=ALERTS{alertstate="firing",namespace="production"}'

# 只看特定告警名稱
'query=ALERTS{alertstate="firing",alertname="KubeJobFailed"}'

第三層:ALERTS_FOR_STATE(告警起始時間)

ALERTS_FOR_STATE 是一個比較少人知道的 metric。它的值是 Unix timestamp,代表該告警規則的表達式「開始為 true」的時間。

curl -s "http://<prometheus>:9090/api/v1/query" \
  --data-urlencode 'query=ALERTS_FOR_STATE' | python3 -c "
import json, sys
from datetime import datetime

data = json.load(sys.stdin)
for r in data['data']['result']:
    name = r['metric'].get('alertname', '?')
    ts = float(r['value'][1])
    started = datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
    print(f'{name}: started at {started}')
"

⚠️ 注意ALERTS_FOR_STATE 只顯示目前活躍的告警的起始時間。已 resolve 的告警也不會出現。


重要概念:為什麼 Prometheus 不保留告警歷史?

這是 Prometheus 的設計哲學:

  1. ALERTS 是合成 time series — Prometheus 在告警 firing 時每次 evaluation 都寫一個 sample(值 = 1),resolve 後標記為 stale
  2. Stale 不等於刪除 — stale 的 sample 仍存在 TSDB 中,所以用 query_range 可以回溯看到
  3. /api/v1/alerts 只看當下 — 這個 API 不查 TSDB,它只看 rule engine 的 in-memory 狀態
  4. Alertmanager 也不保留歷史 — Alertmanager 只在需要發送通知的短暫窗口內保留已 resolve 的告警

所以要看告警歷史,唯一可靠的方式就是查 ALERTS metric 的 time series

保留期限

告警歷史的可追溯範圍 = Prometheus 的 TSDB retention period。預設是 15 天,可以在 Prometheus 啟動參數中調整:

# kube-prometheus-stack values.yaml
prometheus:
  prometheusSpec:
    retention: 30d  # 保留 30 天

Step 參數的選擇

query_rangestep 參數決定取樣間隔。選擇取決於你需要的精度和時間範圍:

時間範圍 建議 step 說明
1 小時 15s 精確到秒,看告警的精確起止
24 小時 60s 精確到分鐘,找出告警時間段
7 天 300s (5m) 足夠看出哪天哪個時段有告警
30 天 900s (15m) 粗略瀏覽整月告警趨勢

⚠️ Step 太小 + 時間範圍太大 = 回傳海量資料,可能讓 Prometheus 變慢甚至 OOM。


常用指令速查表

# === 即時查詢 ===

# 1. 當前 firing 的告警
curl -s http://<prometheus>:9090/api/v1/alerts

# 2. 所有告警規則和狀態(含 inactive)
curl -s http://<prometheus>:9090/api/v1/rules

# === 歷史查詢 ===

# 3. 過去 24 小時所有告警(排除 Watchdog)
START=$(python3 -c "import time; print(int(time.time()) - 86400)")
END=$(python3 -c "import time; print(int(time.time()))")
curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertstate="firing",alertname!="Watchdog"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=60"

# 4. 過去 7 天的 critical 告警
START=$(python3 -c "import time; print(int(time.time()) - 604800)")
END=$(python3 -c "import time; print(int(time.time()))")
curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertstate="firing",severity="critical"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=300"

# 5. 某個特定告警的歷史
curl -s "http://<prometheus>:9090/api/v1/query_range" \
  --data-urlencode 'query=ALERTS{alertname="KubeJobFailed"}' \
  --data-urlencode "start=$START" \
  --data-urlencode "end=$END" \
  --data-urlencode "step=60"

# === K8s 環境 ===

# 6. 透過 kubectl exec 查詢(不需要 port-forward)
kubectl exec -n monitoring <prometheus-pod> -c prometheus -- \
  wget -qO- 'http://localhost:9090/api/v1/alerts'

# 7. 透過 port-forward 查詢
kubectl port-forward -n monitoring svc/<prometheus-svc> 9090:9090
# 然後用 localhost:9090 查詢

學到的事


參考資料