CloudWatch: Alarm, Dashboard và SLO Cho API

K
Kai··5 min read

Bài trước cho hệ thống log, trace và metric. Nhưng dữ liệu quan sát chỉ có giá trị nếu có người nhìn, và không ai ngồi nhìn dashboard 24/7. Cần biến các chỉ số đó thành cảnh báo tự động: khi có gì vượt ngưỡng, hệ thống tự báo, thay vì chờ người dùng phàn nàn. Bài này định nghĩa ngưỡng đó qua SLO, dựng alarm, và kích một cái sang trạng thái cảnh báo thật.

Mục tiêu

Định nghĩa vài SLO cho API, dựng alarm trên CloudWatch gắn với SNS để cảnh báo khi lỗi hay throttle vượt ngưỡng, gom các metric vào một dashboard, và chạy một truy vấn Logs Insights để tính chỉ số từ log. Rồi kích một alarm thật sang ALARM. Chi phí alarm và dashboard ở mức này không đáng kể.

SLO và SLI: đặt ngưỡng cho "tốt"

Trước khi cảnh báo, phải định nghĩa thế nào là hệ thống khỏe. SLI (Service Level Indicator) là một chỉ số đo chất lượng dịch vụ, ví dụ tỉ lệ request thành công, hay độ trễ p99. SLO (Service Level Objective) là mục tiêu đặt ra cho SLI đó, ví dụ "99,9% request mở link thành công" hay "p99 độ trễ resolve dưới 200 ms". SLO cho ta một đường ranh giới khách quan: dưới ngưỡng là cần hành động, và ngưỡng đó là chỗ đặt alarm.

Với URL shortener, hai SLO hợp lý cho đường nóng là: độ tin cậy (resolve không lỗi, không bị throttle) và độ trễ (p99 trong tầm chấp nhận). Ta dựng alarm phản ánh chúng.

Alarm gắn với SNS

Một alarm theo dõi một metric, so với ngưỡng trong một khoảng thời gian, và khi vượt thì chuyển sang trạng thái ALARM và bắn hành động, ở đây là gửi tới một SNS topic (nơi sau này nối email, Slack, hay PagerDuty):

AlarmTopic:
  Type: AWS::SNS::Topic

ResolveThrottleAlarm:
  Type: AWS::CloudWatch::Alarm
  Properties:
    Namespace: AWS/Lambda
    MetricName: Throttles
    Dimensions:
      - { Name: FunctionName, Value: !Ref ResolveLinkFunction }
    Statistic: Sum
    Period: 60
    EvaluationPeriods: 1
    Threshold: 1
    ComparisonOperator: GreaterThanOrEqualToThreshold
    TreatMissingData: notBreaching
    AlarmActions: [!Ref AlarmTopic]

TreatMissingData: notBreaching quan trọng: khi không có request nào, metric throttle vắng dữ liệu, và ta muốn alarm coi đó là bình thường chứ không báo động. Một alarm thứ hai theo dõi Errors của resolve theo cùng kiểu. Mỗi alarm là một SLO được mã hóa thành ngưỡng cụ thể.

Dashboard

Một dashboard gom các metric vào một chỗ để nhìn nhanh. Khai bằng JSON trong template, gồm các widget cho lượt gọi/lỗi/throttle, độ trễ p50/p99, và metric tùy biến LinkResolved từ bài trước:

Dashboard:
  Type: AWS::CloudWatch::Dashboard
  Properties:
    DashboardName: url-shortener
    DashboardBody: !Sub |
      { "widgets": [
        { "type":"metric", ..., "metrics":[
          ["AWS/Lambda","Invocations","FunctionName","${ResolveLinkFunction}",{"stat":"Sum"}],
          ["AWS/Lambda","Errors",...], ["AWS/Lambda","Throttles",...] ]},
        { ..., "metrics":[["AWS/Lambda","Duration",...,{"stat":"p99"}]] },
        { ..., "metrics":[["UrlShortener","LinkResolved",{"stat":"Sum"}]] }
      ]}

Đặt dashboard trong template nghĩa là nó được phiên bản hóa cùng code, không phải dựng tay trên console rồi quên mất ai sửa gì.

Kích alarm thật

Để thấy alarm hoạt động chứ không chỉ tồn tại, ta tạo điều kiện cho nó. Tài khoản này có giới hạn concurrency Lambda là 10 (gặp ở bài 06), nên bắn 60 request mở link gần như cùng lúc sẽ tạo throttle. Sau khi bắn, theo dõi trạng thái alarm:

chờ alarm throttle sang ALARM...
  t=15s: INSUFFICIENT_DATA
  t=30s: ALARM

$ aws cloudwatch describe-alarms --alarm-names url-shortener-resolve-throttles \
    --query 'MetricAlarms[0].{State:StateValue,Reason:StateReason}'
Threshold Crossed: 1 datapoint [23.0 (25/05/26 17:31:00)] was greater than or
equal to the threshold (1.0).    ALARM

Alarm chuyển sang ALARM trong khoảng nửa phút, với lý do ghi rõ: một điểm dữ liệu 23 throttle vượt ngưỡng 1. Cùng lúc nó gửi thông báo tới SNS topic. Khi tải giảm và throttle hết, alarm tự quay về OK. Đây là vòng khép kín: một sự cố thật (vượt concurrency) sinh ra metric, metric vượt ngưỡng SLO, alarm chuyển trạng thái và gửi thông báo tới SNS.

Logs Insights: tính chỉ số từ log

Không phải SLI nào cũng có sẵn thành metric. Với log có cấu trúc từ bài trước, Logs Insights cho phép truy vấn và tính toán trực tiếp trên log. Ví dụ đếm log resolve theo mức:

$ aws logs start-query --log-group-name "/aws/lambda/...ResolveLinkFunction..." \
    --query-string 'fields @message | filter ispresent(level) | stats count(*) as n by level'
...
level   INFO
n       8

Tám dòng INFO trong cửa sổ truy vấn — chỉ những request thực sự chạy tới handler mới sinh log; phần bị throttle không vào tới đó nên không xuất hiện (số này nhỏ hơn nhiều so với tổng request bắn ra, đúng như kỳ vọng khi phần lớn bị gạt). Vì log đã có cấu trúc, ta truy vấn được theo level, theo code, hay nối với xray_trace_id, và tính ra các chỉ số mà metric dựng sẵn không có. Logs Insights là cách trả lời những câu hỏi đặc thù về hành vi hệ thống mà không phải thêm metric mới.

🧹 Dọn dẹp

aws dynamodb scan --table-name url-shortener --query 'Items[].{PK:PK.S,SK:SK.S}' --output text | \
  while read pk sk; do aws dynamodb delete-item --table-name url-shortener \
    --key "{\"PK\":{\"S\":\"$pk\"},\"SK\":{\"S\":\"$sk\"}}"; done
aws cognito-idp admin-delete-user --user-pool-id "$POOL" --username cw@example.com

Alarm tự về OK khi throttle hết. Giữ stack cho bài sau.

Tổng kết

Hệ thống giờ tự báo khi có vấn đề. SLO định nghĩa thế nào là khỏe, alarm mã hóa ngưỡng đó và bắn tới SNS khi vượt, dashboard gom metric để nhìn nhanh, và Logs Insights tính được những chỉ số đặc thù từ log có cấu trúc. Phép thử cho thấy một alarm thật chuyển sang ALARM khi resolve bị throttle, đúng vòng từ sự cố tới cảnh báo.

Alarm throttle vừa rồi nhắc lại một điểm đau: giới hạn concurrency và cold start. Bài sau quay lại đúng chủ đề đó để tối ưu thật. Ta sẽ đo cold start của resolve, rồi so các cách giảm: SnapStart (giờ đã hỗ trợ Python, Java và .NET), provisioned concurrency, và việc cắt kích thước gói, để thấy mỗi cách đổi gì lấy gì.