SLO 101: SLI, error budget và cảnh báo burn rate chỉ kêu khi thật sự cần

Một SLO đầu tiên làm thực tế với Prometheus: chọn SLI mà người dùng thật sự cảm nhận, biến 99.9% thành error budget, viết recording rule, thêm cảnh báo burn rate nhiều cửa sổ kèm unit test, và thống nhất error budget policy.

Read in English →

Nếu bạn không đo được mức độ “ổn” của hệ thống, mọi cuộc tranh luận về reliability chỉ là cảm tính.

Phần lớn các team không thiếu cảnh báo. Họ có quá nhiều: CPU trên 80%, một pod restart, latency tăng vọt 30 giây lúc 3 giờ sáng. Đa số những lần bị gọi dậy đó chẳng ảnh hưởng gì tới người dùng, còn những lần thật sự quan trọng thì lại chìm trong đống nhiễu. SLO giải quyết chuyện này bằng một câu hỏi duy nhất: người dùng có đang nhận đúng trải nghiệm ta đã hứa không, và ta đang tiêu hết khoảng dư cho phép lỗi nhanh đến mức nào?

Bài này dựng trọn vẹn một SLO cho một HTTP API với Prometheus: SLI, error budget, recording rule, cảnh báo, unit test chứng minh cảnh báo kêu đúng lúc, và policy để con số đó thực sự có ích.

Ba khái niệm cốt lõi

Khái niệm Ý nghĩa Ví dụ
SLI (chỉ số) Tỉ lệ sự kiện tốt trên tổng sự kiện, đo ở nơi người dùng cảm nhận Tỉ lệ request không trả về 5xx
SLO (mục tiêu) Mục tiêu cho SLI trong một khoảng thời gian 99.9% trong 30 ngày trượt
Error budget Phần SLO cho phép lỗi: 1 − SLO 0.1% request ≈ 43 phút sập hoàn toàn / 30 ngày

Budget mới là phần hữu ích. 99.9% không có nghĩa là “cố gắng hết sức”. Nó có nghĩa là bạn được phép lỗi 1 request trong 1.000, và mọi thứ khác xuất phát từ việc bạn tiêu phần đó nhanh hay chậm.

SLO Error budget Thời gian sập hoàn toàn được phép mỗi 30 ngày
99% 1% 7 giờ 12 phút
99.5% 0.5% 3 giờ 36 phút
99.9% 0.1% 43 phút 12 giây
99.95% 0.05% 21 phút 36 giây
99.99% 0.01% 4 phút 19 giây

Đừng nhắm tới 100%. Qua Wi-Fi và điện thoại của chính mình, người dùng không phân biệt được 99.99% với 100%, còn mục tiêu 100% nghĩa là bạn không bao giờ được deploy, migrate hay thử nghiệm gì. Hãy chọn con số thấp nhất mà người dùng vẫn hài lòng, rồi siết lại nếu họ phàn nàn.

Bước 1: chọn SLI mà người dùng thật sự cảm nhận

Bắt đầu với một hoặc hai SLI cho mỗi service phục vụ người dùng:

  • Availability: tốt = không phải 5xx (tự quyết xem 429 có tính là xấu không: nếu người dùng coi đó là lỗi thì có).
  • Latency: tốt = được phục vụ nhanh hơn một ngưỡng có ý nghĩa với người dùng, ví dụ 300 ms cho một API. Dùng tỉ lệ request dưới ngưỡng, không dùng trung bình hay một gauge p99.

Đo càng gần người dùng càng tốt: load balancer hay ingress thấy được cả những lỗi chưa bao giờ tới được pod. Loại bỏ những gì không phải traffic thật của người dùng (health check, batch job nội bộ), nếu không SLO sẽ trông đẹp hơn thực tế.

Bước 2: recording rule

Một cảnh báo phải tính rate(...[3d]) trên counter thô mỗi phút vừa tốn tài nguyên vừa khó đọc. Hãy tính sẵn tỉ lệ lỗi cho từng cửa sổ thời gian bằng recording rule, đặt tên theo kiểu level:metric:operation của Google SRE Workbook:

groups:
  - name: slo:api:recordings
    rules:
      - record: job:slo_errors_per_request:ratio_rate5m
        expr: |
          sum by (job) (rate(http_requests_total{job="api", code=~"5.."}[5m]))
          /
          sum by (job) (rate(http_requests_total{job="api"}[5m]))
      # cùng một rule cho 30m, 1h, 2h, 6h, 1d và 3d: chỉ đổi khoảng thời gian
      - record: job:slo_errors_per_request:ratio_rate1h
        expr: |
          sum by (job) (rate(http_requests_total{job="api", code=~"5.."}[1h]))
          /
          sum by (job) (rate(http_requests_total{job="api"}[1h]))
      # SLI latency: tỉ lệ request nhanh hơn 300 ms
      - record: job:slo_latency_good:ratio_rate5m
        expr: |
          sum by (job) (rate(http_request_duration_seconds_bucket{job="api", le="0.3"}[5m]))
          /
          sum by (job) (rate(http_request_duration_seconds_count{job="api"}[5m]))

Bạn cần bảy cửa sổ cho các cảnh báo bên dưới: 5m, 30m, 1h, 2h, 6h, 1d và 3d. SLI latency chỉ đúng khi 0.3 thực sự là một mốc bucket trong histogram của bạn, nên hãy kiểm tra lại phần instrumentation.

Bước 3: cảnh báo theo burn rate, không theo tỉ lệ lỗi

Burn rate là tốc độ bạn tiêu budget so với nhịp “vừa đúng mục tiêu”. Burn rate bằng 1 tiêu hết budget đúng sau 30 ngày. Burn rate 14.4 tiêu hết trong khoảng 2 ngày.

Error budget còn lại trong cửa sổ 30 ngày của SLO 99.9%: burn rate 1 tiêu hết 43 phút budget đúng ngày thứ 30, burn rate 6 tiêu hết trong 5 ngày, burn rate 14.4 tiêu hết trong khoảng 2 ngày
Số ngày tới khi hết budget = 30 ÷ burn rate.

Công thức đáng nhớ là budget đã tiêu = burn rate × (cửa sổ ÷ 30 ngày). Từ đó ra các ngưỡng mà SRE Workbook khuyến nghị làm điểm khởi đầu cho SLO 99.9%:

Mức độ Cửa sổ dài Cửa sổ ngắn Burn rate Budget đã tiêu
Page 1 giờ 5 phút 14.4 2%
Page 6 giờ 30 phút 6 5%
Ticket 1 ngày 2 giờ 3 10%
Ticket 3 ngày 6 giờ 1 10%

Mỗi cảnh báo cần cả hai cửa sổ cùng vượt ngưỡng:

Cảnh báo nhiều cửa sổ: cửa sổ dài chứng minh mức tiêu budget là đáng kể, cửa sổ ngắn chứng minh sự cố vẫn đang diễn ra; cảnh báo chỉ kêu khi cả hai cùng vượt ngưỡng, nên bật nhanh và tắt trong vài phút sau khi sửa xong
Cửa sổ dài nói "chuyện này quan trọng". Cửa sổ ngắn nói "nó vẫn đang xảy ra", nên page tắt chỉ vài phút sau khi sửa xong.
  - name: slo:api:alerts
    rules:
      - alert: APIErrorBudgetBurn
        expr: |
          (
            job:slo_errors_per_request:ratio_rate1h{job="api"} > (14.4 * 0.001)
            and
            job:slo_errors_per_request:ratio_rate5m{job="api"} > (14.4 * 0.001)
          )
          or
          (
            job:slo_errors_per_request:ratio_rate6h{job="api"} > (6 * 0.001)
            and
            job:slo_errors_per_request:ratio_rate30m{job="api"} > (6 * 0.001)
          )
        labels:
          severity: page
        annotations:
          summary: "api is burning its 30-day error budget fast (2% in 1h or 5% in 6h)"
      - alert: APIErrorBudgetBurnSlow
        expr: |
          (
            job:slo_errors_per_request:ratio_rate1d{job="api"} > (3 * 0.001)
            and
            job:slo_errors_per_request:ratio_rate2h{job="api"} > (3 * 0.001)
          )
          or
          (
            job:slo_errors_per_request:ratio_rate3d{job="api"} > 0.001
            and
            job:slo_errors_per_request:ratio_rate6h{job="api"} > 0.001
          )
        labels:
          severity: ticket
        annotations:
          summary: "api will run out of error budget before the 30-day window ends"

0.001 là error budget (1 − 0.999). Nếu đổi SLO, hãy đổi ở mọi chỗ, hoặc để một công cụ sinh rule làm việc đó (Bước 5).

Bước 4: chứng minh cảnh báo hoạt động bằng promtool

Rule cảnh báo cũng là code, nên hãy test nó. promtool test rules chạy chuỗi dữ liệu giả qua rule của bạn và kiểm tra cảnh báo nào được bật:

# slo-test.yml: chạy bằng `promtool test rules slo-test.yml`
rule_files: [slo-rules.yml]
evaluation_interval: 1m
tests:
  # 5% request lỗi: gấp 50 lần nhịp budget → page
  - interval: 1m
    input_series:
      - series: 'http_requests_total{job="api",code="200"}'
        values: '0+950x180'
      - series: 'http_requests_total{job="api",code="500"}'
        values: '0+50x180'
    alert_rule_test:
      - eval_time: 20m
        alertname: APIErrorBudgetBurn
        exp_alerts:
          - exp_labels: {job: api, severity: page}
            exp_annotations:
              summary: "api is burning its 30-day error budget fast (2% in 1h or 5% in 6h)"
  # 0.5% lỗi: burn 5x → ticket, nhưng không page
  - interval: 1m
    input_series:
      - series: 'http_requests_total{job="api",code="200"}'
        values: '0+995x300'
      - series: 'http_requests_total{job="api",code="500"}'
        values: '0+5x300'
    alert_rule_test:
      - eval_time: 240m
        alertname: APIErrorBudgetBurn
        exp_alerts: []
      - eval_time: 240m
        alertname: APIErrorBudgetBurnSlow
        exp_alerts:
          - exp_labels: {job: api, severity: ticket}
            exp_annotations:
              summary: "api will run out of error budget before the 30-day window ends"

Rule và test trong bài này đều qua promtool check rules và promtool test rules với Prometheus 3.15, kể cả một trường hợp thứ ba không in ở đây: lỗi 0.05%, nằm trong SLO, không bật cảnh báo nào. Hãy chạy các test này trong CI để không ai làm hỏng cảnh báo SLO chỉ vì gõ nhầm.

Bước 5: để công cụ sinh rule thay bạn

Bảy recording rule và hai cảnh báo cho mỗi SLO thì ổn với một service, nhưng rất mệt với năm mươi service. Sloth và Pyrra sinh trọn bộ rule từ một spec ngắn. Với Sloth:

version: "prometheus/v1"
service: "api"
labels:
  owner: "platform-team"
slos:
  - name: "requests-availability"
    objective: 99.9
    description: "99.9% of API requests succeed over 30 days."
    sli:
      events:
        error_query: sum(rate(http_requests_total{job="api", code=~"5.."}[{{.window}}]))
        total_query: sum(rate(http_requests_total{job="api"}[{{.window}}]))
    alerting:
      name: APIHighErrorRate
      labels:
        category: "availability"
      annotations:
        summary: "api is burning its error budget"
      page_alert:
        labels:
          severity: page
      ticket_alert:
        labels:
          severity: ticket

sloth generate -i api-slo.yml -o api-rules.yml biến spec này thành 17 rule (đã chạy thử với Sloth 0.16): các cửa sổ SLI, đúng bộ cảnh báo page và ticket nhiều cửa sổ như trên, và các chuỗi dùng ngay cho dashboard như slo:period_error_budget_remaining:ratio. Trên Kubernetes có Prometheus Operator, cả hai công cụ đều chạy được dạng controller, biến một custom resource SLO thành PrometheusRule.

Nếu làm dashboard mà không dùng công cụ sinh rule, “budget còn lại trong 30 ngày qua” là:

1 - (
  sum(increase(http_requests_total{job="api", code=~"5.."}[30d]))
  /
  sum(increase(http_requests_total{job="api"}[30d]))
) / 0.001

Bước 6: error budget policy

Một con số không ai hành động theo thì chỉ để trang trí. Hãy viết ra, trước sự cố đầu tiên, mỗi mức budget nghĩa là gì:

Error budget policy: còn nhiều budget thì cứ ship tính năng và chấp nhận rủi ro; budget thấp thì chậm lại và ưu tiên việc reliability; hết budget thì đóng băng các release rủi ro cho tới khi budget hồi lại
Policy là thứ biến SLO thành một cuộc trao đổi giữa product và engineering, thay vì một cuộc cãi vã.

Con số ngưỡng cụ thể không quan trọng bằng việc mọi người đã thống nhất từ trước. Hãy thống nhất luôn những gì không tính vào budget, ví dụ một sự cố trên toàn nhà cung cấp mà bạn không thể giảm thiểu, và ai được quyền cho ngoại lệ.

Những cái bẫy

  • Traffic thấp: với 10 request mỗi giờ, một lỗi là tỉ lệ lỗi 10% và burn 100 lần. Hãy dùng cửa sổ dài hơn, gộp các service nhỏ cùng chung failure domain, hoặc thêm synthetic probe. Đừng page vì một request.
  • Cửa sổ trượt và cửa sổ theo lịch: cửa sổ trượt 30 ngày hồi dần dần, còn tháng theo lịch thì reset vào ngày 1. Cửa sổ trượt tốt hơn cho cảnh báo, theo lịch thì dễ báo cáo hơn. Chọn một và ghi rõ trên dashboard.
  • Counter reset và mất series: nếu API ngừng báo metric hoàn toàn, tỉ lệ không có dữ liệu và không cảnh báo nào kêu. Hãy đặt kèm mỗi cảnh báo SLO một cảnh báo absent() hoặc “target down”.
  • Quá nhiều SLO: ba SLO mỗi service mà mọi người thực sự nhìn còn hơn hai mươi SLO không ai hiểu. Bắt đầu từ những user journey quan trọng nhất.
  • Phụ thuộc: SLO của bạn không thể cao hơn tích SLO của những thứ bạn gọi đồng bộ, trừ khi có retry, cache hay fallback. Hai dependency 99.9% nối tiếp nhau cho bạn khoảng 99.8%.

Checklist

  • Một hoặc hai SLI cho mỗi service phục vụ người dùng, đo ở biên
  • Mục tiêu người dùng chấp nhận được, không phải 100%
  • Recording rule cho 5m, 30m, 1h, 2h, 6h, 1d, 3d
  • Cảnh báo burn rate nhiều cửa sổ: page khi burn nhanh, ticket khi burn chậm
  • promtool test rules trong CI
  • Một cảnh báo mất dữ liệu đặt cạnh mỗi cảnh báo SLO
  • Error budget policy được cả product và engineering ký duyệt
  • Dashboard hiển thị budget còn lại, được xem lại trong buổi họp vận hành hằng tuần

Nguồn

Bình luận