Log mà không có alert thì giống camera giám sát không có người ngồi xem: ghi lại đầy đủ mọi thứ nhưng bạn chỉ biết bị trộm vào sáng hôm sau. Kibana từ 7.13 trở đi có hệ thống alert đầy đủ, và tới 8.x thì gần ngang bằng các tool thương mại như PagerDuty Insights hay Datadog Monitor.

Bài này tập trung vào ba loại rule cơ bản nhất, cũng là ba loại dùng nhiều nhất trong production:

  • ES query rule: viết DSL hoặc KQL, trigger khi có document khớp
  • Threshold rule: aggregate metric, trigger khi vượt ngưỡng
  • Burn rate rule: track SLO error budget consumption

Mục tiêu:

  • Hiểu mental model về Rule, Alert, Action trong Kibana
  • Setup đúng schedule, time window, threshold cho từng loại rule
  • Biết khi nào dùng rule type nào và tránh các pitfall hay gặp

Phần 1: Mental model Rule và Alert

Trong Kibana, ba khái niệm dễ nhầm:

ConceptĐịnh nghĩa
RuleMột định nghĩa: “Mỗi N phút, chạy query Y, nếu Y trả về kết quả thoả X thì sinh ra alert”
AlertMột instance trigger từ rule. Cùng một rule có thể sinh nhiều alert (mỗi alert cho một entity)
ActionHành động khi alert trigger: gửi Slack, gọi webhook, tạo issue

Ví dụ: rule “Service down” check mỗi service trong 5 phút. Nếu 3 service payment, auth, inventory đều down, rule sẽ sinh 3 alert song song, mỗi alert có context riêng. Action gửi Slack sẽ gửi 3 message rời (trừ khi setup throttling).

Phân biệt này quan trọng khi debug: rule đang chạy nhưng không gửi notification, có thể vì alert đang ở state Active mà action chỉ gửi khi state đổi (new alert vs ongoing).

Phần 2: ES query rule

Đây là rule type linh hoạt nhất. Bạn viết một query Elasticsearch DSL hoặc KQL, rule trigger khi query trả về document.

Setup qua GUI

Stack Management → Rules → Create rule → Elasticsearch query.

Form chính:

  1. Index: nhập app-logs-* hoặc data view ID.
  2. Time field: @timestamp.
  3. Query type: KQL/Lucene hoặc Query DSL.
  4. Query: ví dụ KQL Level : "Fatal".
  5. Threshold: IS ABOVE 0 (trigger nếu có bất kỳ document nào khớp).
  6. Time window: 5 minutes.
  7. Run rule every: 1 minute.

Setup qua API

curl -s -u "$KB_USER:$KB_PASS" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -X POST "$KIBANA_URL/api/alerting/rule" \
  -d '{
    "name": "Fatal error detected",
    "rule_type_id": ".es-query",
    "consumer": "alerts",
    "schedule": {"interval": "1m"},
    "params": {
      "searchType": "esQuery",
      "index": ["app-logs-*"],
      "timeField": "@timestamp",
      "esQuery": "{\"query\":{\"match\":{\"Level\":\"Fatal\"}}}",
      "size": 100,
      "threshold": [0],
      "thresholdComparator": ">",
      "timeWindowSize": 5,
      "timeWindowUnit": "m"
    },
    "actions": []
  }'

Time window và schedule

Hai tham số dễ nhầm:

  • timeWindowSize (5m): rule sẽ scan document trong khoảng now - 5m đến now.
  • schedule.interval (1m): rule chạy mỗi phút một lần.

Nếu schedule = 1m, window = 5m, mỗi document sẽ được scan 5 lần (trong 5 lần chạy liên tiếp). Điều này gây trùng alert. Cách tránh: dùng deduplication hoặc throttle action (bài 11 sẽ đi sâu).

Pitfall: window quá nhỏ

Một rule check error 5xx với timeWindowSize: 1m, schedule mỗi 1 phút. Nhưng nếu ES ingest delay 30 giây (Vector buffer, hay Logstash batch), thì document vào ES sau khi rule chạy 30 giây. Rule kế tiếp 1 phút sau scan window mới, document đã rơi ngoài window cũ. Kết quả: alert miss.

Rule of thumb: timeWindowSize >= 2 * ingest_lag + schedule_interval. Nếu ingest lag 30s, schedule 1m thì window ít nhất 2m.

Pitfall: query không match field name

KQL: level : "fatal"

Trả về 0 kết quả nếu Serilog emit Level PascalCase với value "Fatal". Rule sẽ không bao giờ trigger. Trước khi dán query vào rule, test trong Discover với cùng time range và data view. Nếu Discover ra kết quả mà rule không, kiểm tra data view ID có match không (rule lưu data view title; nếu rename data view, link gãy).

Phần 3: Threshold rule

Threshold rule là wrapper trên ES query với UI thân thiện hơn. Thay vì viết DSL, bạn chọn aggregation từ dropdown.

Use case điển hình

“Trigger khi avg(response_time) > 1000ms trong 5 phút qua, group by service.name”.

Setup:

  1. Rule type: Index threshold.
  2. Index: metrics-*.
  3. When: average of response_time.
  4. Over: all documents hoặc top 5 by service.name.
  5. For the last: 5 minutes.
  6. Threshold: IS ABOVE 1000.

Nếu chọn “top 5 by service.name”, rule sẽ tạo 5 alert riêng (mỗi service một alert) nếu thoả threshold. Action có thể template {{context.group}} để biết service nào trigger.

Aggregation choices

AggregationKhi dùng
countSố request, số log, số event
averageResponse time, CPU, memory
min / maxBottom outlier, peak
sumDoanh thu, tổng byte transferred
cardinalityUnique user, unique session

Lưu ý: cardinality threshold dễ flaky vì approximate (xem bài 7 về visualization pitfalls).

Pitfall: top N skew

Threshold rule với top 5 chỉ alert cho 5 entity highest. Nếu có 50 service và service nhỏ bị spike, rule có thể miss vì không lọt top 5.

Fix: thay vì top 5, dùng all values với threshold thấp, hoặc tách thành nhiều rule riêng (mỗi service một rule với filter service.name: "X"). Tốn config hơn nhưng không miss.

Phần 4: Burn rate rule

Burn rate là khái niệm từ Google SRE Workbook. Thay vì alert “có error”, alert “đang tiêu error budget quá nhanh”.

Mental model

Giả sử SLO của bạn là 99.9% availability trong 30 ngày. Error budget = 0.1% = 43.2 phút downtime trong 30 ngày.

  • Nếu trong 1 giờ qua, downtime là 0.1% (3.6 giây) → tiêu budget với tốc độ 1x (vừa đủ trong 30 ngày).
  • Nếu downtime trong 1 giờ là 1% (36 giây) → tiêu 10x → hết budget trong 3 ngày.

Burn rate alert trigger khi rate vượt ngưỡng, không phải khi tổng error vượt.

Multi-window burn rate

Google khuyến nghị dùng 2 hoặc 3 window đồng thời để giảm false positive:

Window ngắnWindow dàiBurn rate thresholdSeverity
5 phút1 giờ14.4xPage (high urgency)
30 phút6 giờ6xPage (medium urgency)
1 giờ24 giờ1xTicket (long-term)

Logic: trigger CHỈ khi cả window ngắn VÀ window dài đều vượt rate. Window ngắn detect spike nhanh; window dài tránh nhiễu một phút bất thường.

Setup trong Kibana

Kibana 8.x có dedicated SLO + Burn rate rule (sẽ đi sâu bài 10). Nhưng nếu chưa muốn dùng SLO feature, có thể tự build với ES query rule:

curl -s -u "$KB_USER:$KB_PASS" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -X POST "$KIBANA_URL/api/alerting/rule" \
  -d '{
    "name": "Burn rate fast (5m + 1h)",
    "rule_type_id": ".es-query",
    "consumer": "alerts",
    "schedule": {"interval": "1m"},
    "params": {
      "searchType": "esQuery",
      "index": ["request-logs-*"],
      "timeField": "@timestamp",
      "esQuery": "{\"query\":{\"bool\":{\"must\":[{\"range\":{\"@timestamp\":{\"gte\":\"now-5m\"}}},{\"range\":{\"status\":{\"gte\":500}}}]}}}",
      "size": 0,
      "threshold": [0.0144],
      "thresholdComparator": ">",
      "timeWindowSize": 5,
      "timeWindowUnit": "m"
    }
  }'

Threshold 0.0144 là số tuyệt đối; bạn cần normalize bằng cách chia tổng request. Cách tốt hơn: dùng pipeline aggregation hoặc transform tạo index error_rate-* precompute, rồi rule chỉ check error_rate > 0.0144.

Transform approach

PUT _transform/error-rate-1m
{
  "source": { "index": "request-logs-*" },
  "pivot": {
    "group_by": {
      "ts": { "date_histogram": { "field": "@timestamp", "fixed_interval": "1m" } },
      "service": { "terms": { "field": "service.name" } }
    },
    "aggregations": {
      "total": { "value_count": { "field": "@timestamp" } },
      "errors": { "filter": { "range": { "status": { "gte": 500 } } } },
      "error_rate": {
        "bucket_script": {
          "buckets_path": { "e": "errors._count", "t": "total" },
          "script": "params.e / params.t"
        }
      }
    }
  },
  "dest": { "index": "error-rate-1m" },
  "frequency": "30s",
  "sync": { "time": { "field": "@timestamp", "delay": "60s" } }
}

Sau đó rule chỉ cần check avg(error_rate) > 0.0144 trên error-rate-1m. Đơn giản hơn nhiều.

Phần 5: Pitfall storytelling

Ca 1: Alert flapping mỗi đêm

Rule “CPU > 80%” trigger và clear xen kẽ mỗi vài phút lúc 3 giờ sáng. Slack channel ngập noise. Lý do: backup job chạy 3 giờ sáng đẩy CPU lên 85% trong 30 giây rồi xuống. Rule trigger ngay khi vượt.

Fix: dùng for N consecutive checks (Kibana 8.6+) hoặc tăng timeWindowSize lên 5 phút và threshold lên 85%. Rule chỉ trigger khi CPU cao SUSTAINED, không phải spike.

Ca 2: Alert miss vì index pattern thay đổi

Rule check app-logs-2026.*. Sang năm 2027, log đi vào app-logs-2027.*. Rule không trigger nữa vì index pattern không match.

Fix: luôn dùng wildcard rộng app-logs-* thay vì hardcode năm. Hoặc alias app-logs-current rotate manually.

Ca 3: Rule “không chạy”

Rule tạo xong, status Active nhưng không thấy alert. Debug:

  1. Vào Stack Management → Rules → click rule → Execution log. Xem có error không.
  2. Check consumer field. Nếu consumer: "alerts" mà bạn không có quyền alerts cluster privilege, rule chạy nhưng không emit alert.
  3. Check API key creator. Rule chạy với quyền của user/key tạo nó. Nếu key expired hoặc revoke, rule chết âm thầm.

Phần 6: Best practices

PracticeLý do
timeWindowSize >= 2 * ingest_lagTránh miss alert do ingest delay
Dùng wildcard index patternTránh gãy khi rotation
Test query trong Discover trướcĐảm bảo trigger condition đúng
Tag rule với owner và severityDễ filter và route
Set notification_delay 1-2 phútTránh alert spike một lần xảy ra
Backup rule definition qua API exportKhôi phục nhanh khi cluster die
Document runbook trong rule descriptionOn-call có context khi nhận page

Cheatsheet

Rule typeUse caseSchedule điển hình
.es-queryCustom DSL, flexible1-5m
.index-thresholdAggregate metric đơn giản1-5m
.geo-containmentGeofence (xem bài 6)1m
Burn rate (custom)SLO tracking1m
.metrics-* (APM)Service-specific30s-1m

Lời kết

Alert là chỗ Kibana từ một log viewer trở thành observability platform. Ba rule type cơ bản này phủ 90% nhu cầu production: ES query cho event-based, Threshold cho metric-based, Burn rate cho SLO. Phần khó nhất không phải config, mà là chọn đúng time window và threshold để không noise mà cũng không miss.

Bài tiếp theo trong series Kibana từ A đến Z sẽ đi vào Connectors: cách setup Slack, Email, Webhook, PagerDuty an toàn và đúng pattern. Có rule mà không có connector tốt thì alert vẫn vô dụng. Nếu bạn đang stuck với một rule cụ thể (flapping, miss, không trigger), comment cho mình biết.