Prometheus Recording Rules và Alerting Rules nâng cao: Tối ưu truy vấn và cảnh báo chính xác cho production

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

Khi dashboard Grafana chậm như rùa và alert bắn liên tục lúc 3 giờ sáng

Hồi mới dựng Prometheus cho hệ thống, mình cứ nghĩ cài xong là xong. Dashboard lên, Grafana kết nối được, metrics có — ổn rồi. Nhưng chỉ sau vài tuần, team bắt đầu phàn nàn: dashboard load mất 10–15 giây, alert cứ bắn vài phút rồi tự tắt (flapping), và quan trọng nhất — lúc 3 giờ sáng mình nhận alert “CPU high” nhưng SSH vào server thì… CPU bình thường.

Vấn đề không phải ở Prometheus. Mà ở cách dùng — cụ thể là hai tính năng mình đã bỏ qua hoàn toàn: Recording RulesAlerting Rules viết đúng cách.

Tại sao dashboard chậm và alert không tin cậy?

Prometheus lưu raw metrics dưới dạng time series. Mỗi lần Grafana render một panel, nó gửi một PromQL query xuống Prometheus — query này có thể scan hàng triệu data point trong khoảng thời gian bạn chọn.

Ví dụ panel “CPU Usage trung bình 5 phút” dùng query:

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

Nhìn đơn giản vậy thôi, nhưng khi có 50 server và dashboard có 20 panel, mỗi lần refresh Grafana phải chạy 20 query đồng thời — mỗi query scan toàn bộ time series trong time range đang xem. Dashboard lag là tất yếu.

Alert flapping thì khác: khi điều kiện cảnh báo đánh giá thẳng trên raw metrics mà không có buffer thời gian, CPU spike 2 giây là đủ để bắn alert. CPU về bình thường → alert tắt → spike lại → alert bắn tiếp. Bạn bị đánh thức lúc nửa đêm vì một spike ngắn hoàn toàn vô hại.

Recording Rules: Tính toán trước, truy vấn sau

Ý tưởng của Recording Rules rất đơn giản: thay vì để Grafana tính lại từ đầu mỗi lần refresh, bạn bảo Prometheus tính sẵn kết quả theo định kỳ (mặc định 1 phút) và lưu thành một metric mới. Grafana chỉ việc đọc metric đó ra — nhanh như query một con số đơn giản.

Cấu trúc file Recording Rules

Tạo file /etc/prometheus/rules/recording_rules.yml:

groups:
  - name: node_cpu_recording
    interval: 1m   # Tính lại mỗi 1 phút
    rules:
      # CPU usage trung bình 5 phút, theo từng instance
      - record: job:node_cpu_usage:avg5m
        expr: |
          100 - (
            avg by (instance, job) (
              rate(node_cpu_seconds_total{mode="idle"}[5m])
            ) * 100
          )

      # Memory usage % theo instance
      - record: job:node_memory_usage:ratio
        expr: |
          1 - (
            node_memory_MemAvailable_bytes
            / node_memory_MemTotal_bytes
          )

      # Disk read/write throughput tổng hợp
      - record: job:node_disk_throughput:rate5m
        expr: |
          sum by (instance, job) (
            rate(node_disk_read_bytes_total[5m])
            + rate(node_disk_written_bytes_total[5m])
          )

Về tên metric: convention chính thức của Prometheus là level:metric:operations. Ví dụ job:node_cpu_usage:avg5m đọc là “aggregated ở level job, metric cpu usage, operation avg 5 phút”. Không bắt buộc, nhưng nên theo — đặc biệt khi team lớn dần và cần tra cứu nhanh.

Đăng ký file rules vào prometheus.yml

# /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 1m   # Tần suất đánh giá rules

rule_files:
  - "/etc/prometheus/rules/*.yml"

Kiểm tra rules hợp lệ trước khi reload

# Dùng promtool để validate — không cần restart Prometheus
promtool check rules /etc/prometheus/rules/recording_rules.yml

# Reload Prometheus nhận rules mới (không downtime)
curl -X POST http://localhost:9090/-/reload

Sau khi áp dụng Recording Rules, dashboard của mình load từ 15 giây xuống dưới 2 giây. Không cần nâng server, không cần tune Prometheus — chỉ cần dùng đúng tính năng có sẵn.

Alerting Rules: Cảnh báo đúng lúc, đúng người

Alerting Rules không lưu kết quả như Recording Rules — nó đánh giá điều kiện và đẩy sang Alertmanager khi điều kiện thỏa. Chìa khóa để loại bỏ flapping nằm ở tham số for. Nghe đơn giản, nhưng đây là thứ mình thấy bị bỏ qua nhiều nhất.

Cấu trúc Alerting Rules chuẩn

# /etc/prometheus/rules/alerting_rules.yml
groups:
  - name: node_alerts
    rules:
      # CPU cao liên tục 10 phút mới alert — tránh flapping
      - alert: HighCPUUsage
        expr: job:node_cpu_usage:avg5m > 85
        for: 10m
        labels:
          severity: warning
          team: infrastructure
        annotations:
          summary: "CPU cao trên {{ $labels.instance }}"
          description: |
            Instance {{ $labels.instance }} có CPU usage {{ $value | printf "%.1f" }}%
            liên tục trên 85% trong 10 phút. Kiểm tra process nào đang chạy nặng.
          runbook_url: "https://wiki.internal/runbooks/high-cpu"

      # RAM gần đầy — critical vì ảnh hưởng trực tiếp
      - alert: HighMemoryUsage
        expr: job:node_memory_usage:ratio > 0.90
        for: 5m
        labels:
          severity: critical
          team: infrastructure
        annotations:
          summary: "RAM sắp đầy trên {{ $labels.instance }}"
          description: |
            Memory usage đang ở {{ $value | printf "%.0f%%" }} trên {{ $labels.instance }}.
            Hệ thống có thể bắt đầu swap hoặc OOM kill process.

      # Disk sắp đầy — dùng predict để cảnh báo sớm
      - alert: DiskWillFillSoon
        expr: |
          predict_linear(
            node_filesystem_avail_bytes{mountpoint="/"}[6h], 24 * 3600
          ) < 0
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "Disk / sẽ đầy trong 24h trên {{ $labels.instance }}"
          description: |
            Dựa trên tốc độ tăng 6 giờ qua, disk {{ $labels.mountpoint }}
            trên {{ $labels.instance }} sẽ đầy trong vòng 24 giờ tới.

Giải thích các tham số quan trọng

  • for: 10m — Điều kiện phải thỏa mãn liên tục 10 phút mới bắn alert. CPU spike 2 giây không đủ điều kiện → không alert. Đây là cách loại bỏ flapping đơn giản và hiệu quả nhất.
  • labels.severity — Alertmanager dùng label này để route: warning gửi Slack trong giờ hành chính, critical gọi PagerDuty lúc 3 giờ sáng. Phân loại đúng từ đầu giúp on-call không bị burnout.
  • annotations.description — Template với {{ $labels.instance }}{{ $value }} đưa thẳng con số vào tin nhắn. On-call engineer đọc alert là biết ngay vấn đề, không cần SSH để xem thêm.
  • predict_linear() — Thay vì cảnh báo khi disk đã đầy (quá muộn để phản ứng), hàm này dự đoán dựa trên tốc độ tăng 6 giờ gần nhất. Cảnh báo trước 24 giờ — đủ thời gian dọn log hoặc mở rộng volume.

Kết hợp Recording Rules với Alerting Rules

Một pattern mình hay dùng: Recording Rules tính toán metric phức tạp, Alerting Rules dùng metric đó để alert. Vừa nhanh, vừa nhất quán — dashboard và alert dùng cùng một con số, không bao giờ xảy ra chuyện dashboard hiển thị CPU 40% trong khi alert đang kêu CPU 90%.

groups:
  - name: http_slo_recording
    rules:
      # Tỷ lệ lỗi HTTP 5xx trong 5 phút
      - record: job:http_error_rate:ratio5m
        expr: |
          sum by (job, instance) (
            rate(http_requests_total{status=~"5.."}[5m])
          )
          /
          sum by (job, instance) (
            rate(http_requests_total[5m])
          )

  - name: http_slo_alerts
    rules:
      # Alert khi error rate vượt SLO 1%
      - alert: HTTPErrorRateTooHigh
        expr: job:http_error_rate:ratio5m > 0.01
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Tỷ lệ lỗi HTTP cao: {{ $labels.job }}"
          description: |
            Service {{ $labels.job }} đang có {{ $value | printf "%.2f%%" }} lỗi 5xx.
            SLO cho phép tối đa 1%. Kiểm tra application logs ngay.

Những gì mình học được sau nhiều lần deploy sai

Không phải lần đầu mình làm đúng ngay. Đây là quy trình đang chạy ổn định sau vài tháng production:

  1. Tách file riêng cho từng nhóm: recording_node.yml, alerting_node.yml, alerting_http.yml… — khi một file lỗi chỉ rollback file đó, không ảnh hưởng phần còn lại.
  2. Chạy promtool check rules trong CI/CD trước khi merge pull request — bắt lỗi syntax sớm, không để đến lúc deploy mới phát hiện.
  3. Đặt for ít nhất 5 phút cho warning, 2–3 phút cho critical — đủ để lọc spike ngắn nhưng không trễ quá khi sự cố thật xảy ra.
  4. Dùng Recording Rules cho mọi query xuất hiện hơn 2 lần — trong dashboard hay alert rules đều tính. Nguyên tắc DRY áp dụng hoàn toàn cho PromQL.
  5. Luôn có runbook_url trong annotations — link thẳng đến tài liệu xử lý. On-call engineer mới join team vẫn biết cần làm gì, không cần ping người khác lúc 2 giờ sáng.

Trước đây, mỗi khi có sự cố là mình phải SSH vào từng server để kiểm tra. Bây giờ alert gửi thẳng vào Telegram với đủ thông tin — nhiều khi không cần mở laptop. Nhưng thứ có giá trị hơn tốc độ phản ứng là alert đáng tin. Team không còn tự hỏi “liệu cái này có phải flapping không” — mỗi alert bắn là có việc thật cần xử lý.

Share: