Xây dựng hệ thống cảnh báo thông minh với LLM: Tự động phân loại và tóm tắt Alert Prometheus để giảm Alert Fatigue

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

Ba giờ sáng, điện thoại rung. Mở ra thấy 47 tin nhắn Telegram từ Alertmanager. Đọc lướt qua — hầu hết là InstanceDown của cùng một node đang rolling restart. Không khẩn cấp. Nhưng não vẫn phải xử lý. Sáng hôm sau mệt mỏi, check lại thấy có 2 alert thật sự quan trọng bị chìm giữa đống noise đó.

Đây là alert fatigue. Mình đã sống với nó suốt 2 năm trước khi quyết định phải làm gì đó nghiêm túc hơn.

So sánh 3 cách tiếp cận để xử lý alert thông minh

Cách 1: Rule-based filtering trong Alertmanager

Gần như ai cũng bắt đầu từ đây — viết thêm route, silence, inhibition rules trong alertmanager.yml.

# alertmanager.yml
routes:
  - match:
      severity: warning
    receiver: 'slack-warnings'
    group_wait: 30s
    group_interval: 5m
    repeat_interval: 4h
  - match:
      severity: critical
    receiver: 'pagerduty-oncall'

inhibit_rules:
  - source_match:
      alertname: NodeDown
    target_match:
      job: node
    equal: ['instance']

Ưu điểm: Đơn giản, không cần infra thêm, Alertmanager hỗ trợ native.

Nhược điểm: Rules phải viết tay, không scale khi hệ thống phức tạp. Mỗi lần xuất hiện pattern alert mới lại phải mở file YAML ra sửa. Quan trọng hơn — nó lọc chứ không giải thích. SysAdmin vẫn phải đọc từng alert để hiểu context.

Cách 2: ML-based anomaly detection

Các tool như Grafana Machine Learning, Metis, hoặc tự build model với Prophet/LSTM để phát hiện anomaly thay vì dùng threshold cứng.

Ưu điểm: Thông minh hơn, tự học pattern theo thời gian, giảm false positive.

Nhược điểm: Setup phức tạp, cần dữ liệu training, khó debug khi model ra kết quả sai. Chi phí vận hành cao. Team 2-3 người thì đây là overkill.

Cách 3: LLM-based summarization (cách mình đang dùng)

Thay vì lọc hay học pattern, dùng LLM để đọc hiểu alert và trả lời câu hỏi thực sự quan trọng: “Cái này có cần tôi thức dậy ngay không? Nếu có thì vì sao?”

Ưu điểm: Không cần training data. Và khác với rule-based, LLM hiểu ngữ cảnh — biết rằng DiskSpaceRunningLow trên node database nguy hiểm hơn nhiều so với trên node log collector. Prompt dễ customize theo domain knowledge của từng team.

Nhược điểm: Có latency (1-3 giây mỗi lần gọi API). Chi phí API — nhưng với volume alert bình thường của hệ thống vừa, tổng chi phí chỉ vài đô mỗi tháng.

Vì sao mình chọn LLM?

Mình đã thử cả 3. Rule-based chạy được 6 tháng thì file alertmanager.yml có gần 300 dòng và không ai dám đụng vào nữa. ML thì mình setup thử Grafana ML — kết quả không tệ, nhưng sau một lần deploy lớn thay đổi alert pattern, model mất gần 2 tuần mới adapt lại.

LLM thắng ở một điểm mà hai cách kia bó tay: nó trả lời câu hỏi của con người, không phải câu hỏi của máy. Thay vì chỉ biết “CPU usage > 90%”, nó nói được: “CPU database server đang 95% trong 20 phút, trùng với giờ chạy backup job hàng ngày — khả năng cao là bình thường, nhưng nên check nếu backup chưa xong sau 2 giờ nữa.”

Rule-based không làm được điều này. Không phải vì thiếu dữ liệu — mà vì nó không hiểu ngữ nghĩa.

Kiến trúc hệ thống

Flow đơn giản:

Prometheus → Alertmanager → Webhook Receiver (Python) → LLM API → Telegram/Slack

Alertmanager gửi alert sang một Python server nhỏ qua webhook. Server này gọi LLM để phân loại và tóm tắt, rồi forward kết quả đã được “dịch” sang Telegram của team.

Hướng dẫn triển khai thực tế

Bước 1: Cấu hình Alertmanager webhook receiver

Thêm webhook receiver vào alertmanager.yml:

receivers:
  - name: 'llm-summarizer'
    webhook_configs:
      - url: 'http://localhost:8080/alert'
        send_resolved: true
        http_config:
          bearer_token: 'your-secret-token'

routes:
  - receiver: 'llm-summarizer'
    group_by: ['alertname', 'cluster', 'service']
    group_wait: 10s
    group_interval: 2m
    repeat_interval: 1h

Trick quan trọng: set group_interval: 2m để Alertmanager gom alert cùng nhóm trong 2 phút trước khi gửi — LLM sẽ nhận được một batch gồm nhiều alert liên quan thay vì từng cái rời rạc.

Bước 2: Python webhook server với LLM

Cài dependencies:

pip install fastapi uvicorn anthropic httpx python-dotenv

File alert_summarizer.py:

import os
import json
import httpx
import anthropic
from fastapi import FastAPI, Request, HTTPException, Header
from typing import Optional

app = FastAPI()
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

TELEGRAM_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
WEBHOOK_TOKEN = os.environ["WEBHOOK_SECRET_TOKEN"]

SYSTEM_PROMPT = """Bạn là SRE senior phân tích alert Prometheus cho hệ thống production.
Phân loại mức độ: CRITICAL (cần xử lý ngay), WARNING (cần theo dõi), INFO (thông tin).
Tóm tắt ngắn gọn bằng tiếng Việt, giải thích impact thực tế và gợi ý bước xử lý đầu tiên.
Format trả về JSON: {"severity": "CRITICAL|WARNING|INFO", "summary": "...", "impact": "...", "action": "..."}"""

def format_alerts_for_llm(alerts: list) -> str:
    lines = []
    for a in alerts:
        labels = a.get("labels", {})
        annotations = a.get("annotations", {})
        status = a.get("status", "firing")
        lines.append(
            f"- [{status.upper()}] {labels.get('alertname', 'Unknown')}"
            f" | instance={labels.get('instance', 'N/A')}"
            f" | severity={labels.get('severity', 'N/A')}"
            f" | {annotations.get('description', annotations.get('summary', ''))}"
        )
    return "\n".join(lines)

async def send_telegram(text: str):
    url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
    async with httpx.AsyncClient() as client_http:
        await client_http.post(url, json={
            "chat_id": TELEGRAM_CHAT_ID,
            "text": text,
            "parse_mode": "Markdown"
        })

@app.post("/alert")
async def receive_alert(
    request: Request,
    authorization: Optional[str] = Header(None)
):
    if authorization != f"Bearer {WEBHOOK_TOKEN}":
        raise HTTPException(status_code=401)
    
    payload = await request.json()
    alerts = payload.get("alerts", [])
    if not alerts:
        return {"status": "no alerts"}

    alert_text = format_alerts_for_llm(alerts)
    
    response = client.messages.create(
        model="claude-haiku-4-5-20251001",  # Dùng Haiku để tiết kiệm chi phí
        max_tokens=512,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": f"Phân tích các alert sau:\n{alert_text}"}]
    )
    
    result = json.loads(response.content[0].text)
    severity = result.get("severity", "INFO")
    emoji = {"CRITICAL": "🔴", "WARNING": "🟡", "INFO": "🔵"}.get(severity, "⚪")
    
    message = (
        f"{emoji} *{severity}* — {len(alerts)} alert(s)\n\n"
        f"*Tóm tắt:* {result.get('summary')}\n"
        f"*Impact:* {result.get('impact')}\n"
        f"*Nên làm:* {result.get('action')}"
    )
    
    await send_telegram(message)
    return {"status": "ok", "severity": severity}

Chạy server:

export ANTHROPIC_API_KEY="sk-ant-..."
export TELEGRAM_BOT_TOKEN="..."
export TELEGRAM_CHAT_ID="-100..."
export WEBHOOK_SECRET_TOKEN="your-secret"

uvicorn alert_summarizer:app --host 0.0.0.0 --port 8080

Bước 3: Chạy như systemd service

[Unit]
Description=LLM Alert Summarizer
After=network.target

[Service]
Type=simple
User=prometheus
EnvironmentFile=/etc/alert-summarizer/env
ExecStart=/usr/local/bin/uvicorn alert_summarizer:app --host 0.0.0.0 --port 8080
Restart=always
RestartSec=5
WorkingDirectory=/opt/alert-summarizer

[Install]
WantedBy=multi-user.target

Mẹo tối ưu từ thực chiến

  • Dùng Haiku thay vì Sonnet cho alert summarization — response time nhanh hơn (~0.8s vs ~2s), chi phí thấp hơn 5x. Với 500 alert events/ngày, tổng chi phí chưa đến $3/tháng.
  • Cache alert đã xử lý: Dùng Redis hoặc dict in-memory lưu hash của alert batch trong 30 phút để tránh gọi LLM trùng lặp khi Alertmanager resend.
  • Inject context hệ thống vào system prompt: Liệt kê những service quan trọng nhất, giờ maintenance định kỳ, pattern alert bình thường của team. LLM sẽ phân loại chính xác hơn nhiều.
  • Set ngưỡng CRITICAL riêng: Chỉ ping oncall khi severity là CRITICAL. WARNING và INFO gom vào digest gửi mỗi sáng — không wake up người lúc 3 giờ sáng vì chuyện có thể đợi đến 9 giờ.

Kết quả sau 3 tháng dùng thực tế

Từ 40-60 tin nhắn Telegram mỗi đêm xuống còn 3-5 tin nhắn có ý nghĩa. Giảm hơn 90% noise. Team bắt đầu đọc alert trở lại — thay vì reflex mute bot đi cho yên.

Chi phí API với Claude Haiku: khoảng $2-3/tháng cho một hệ thống vừa (~500 alert events/ngày). Rẻ hơn nhiều so với những đêm mất ngủ không cần thiết.

Cái thay đổi lớn nhất không phải con số. Khi có alert thật sự nghiêm trọng, nó nổi bật rõ — không còn bị chìm trong đống noise nữa. Và mình ngủ được.

Share: