← DevOps← DevOps
DevOpsDevOps18 Th7, 2026Jul 18, 202623 phút đọc19 min read

Monitoring & Observability (Giám sát & Khả năng quan sát)Monitoring & Observability

Thuộc knowledge base theo DevOps Roadmap.

Tổng quan

Monitoring cho bạn biết hệ thống khỏe hay không; observability cho phép bạn hỏi tại sao nó không khỏe. Monitoring là thu thập các tín hiệu định nghĩa trước (CPU, error rate, độ sâu queue) và cảnh báo khi vượt ngưỡng — nó trả lời những câu hỏi bạn đã nghĩ ra từ trước. Observability là một thuộc tính của hệ thống: mức độ mà các dữ liệu đầu ra (telemetry) cho phép giải thích bất kỳ trạng thái nội bộ nào, kể cả những kiểu lỗi không ai lường trước. Trong kiến trúc microservice, nơi một request của người dùng có thể đi qua hàng chục service, “unknown unknowns” chiếm đa số — vì vậy observability trở thành năng lực cốt lõi của DevOps chứ không còn là việc phụ của ops.

Cách đóng khung phổ biến trong ngành là ba trụ cột — metrics, logs và traces — thường được mở rộng thêm events (deploy, thay đổi config, scaling) cung cấp bối cảnh “cái gì vừa thay đổi?”. Mỗi trụ cột trả lời một câu hỏi khác nhau: metric nói có gì đó sai (rẻ, đã tổng hợp, phù hợp để alert), trace nói sai ở đâu trên đường đi của request, còn log nói vì sao với đầy đủ chi tiết. Các team trưởng thành sẽ liên kết cả ba: alert nổ trên một metric, bạn nhảy sang một trace mẫu (exemplar), rồi xoay sang log của span đang lỗi. Ba trụ cột là mô hình khởi đầu, không phải giáo điều — mục tiêu sâu xa hơn là telemetry cardinality cao, được liên kết, khám phá được, cho phép bạn cắt lát theo bất kỳ chiều nào (user, region, version, endpoint) khi đang xử lý sự cố.

Vượt ra ngoài tooling, observability là nền móng của reliability engineering: SLI định lượng hành vi mà người dùng nhìn thấy, SLO biến chúng thành mục tiêu, và error budget biến độ tin cậy thành một “đồng tiền” mà team có thể chi tiêu cho tốc độ phát triển tính năng. Không có đo lường thì “hệ thống ổn định” chỉ là ý kiến cá nhân. Trạng thái đích cần nhắm tới: khi có gì đó hỏng lúc 3 giờ sáng, một engineer on-call chưa từng thấy lỗi này bao giờ vẫn có thể đi từ “một alert vừa nổ” tới “đây là nguyên nhân gốc” chỉ trong vài phút, chỉ bằng telemetry — mà không phải deploy lại để thêm một câu print.

Một lưu ý: telemetry là dữ liệu, không phải dashboard

Người mới hay đánh đồng observability với dashboard. Dashboard là để trả lời các câu hỏi đã biết. Observability thực sự lộ ra khi câu hỏi là mới: “vì sao request checkout từ người dùng Android ở eu-west chỉ chậm khi app ở version 4.2?”. Bạn chỉ trả lời được nếu dữ liệu mang theo các chiều đó và bạn query xuyên qua chúng được một cách tùy ý. Hãy thiết kế telemetry sao cho cắt lát được theo những chiều mà bạn chưa nghĩ tới việc vẽ biểu đồ từ trước.

Kiến thức nền tảng

Ba trụ cột (cộng thêm events)

Tín hiệuLà gìĐiểm mạnhĐiểm yếuTrả lời
MetricsChuỗi thời gian dạng số (counter, gauge, histogram)Lưu trữ rẻ, query nhanh, lý tưởng cho alert và xu hướngĐã tổng hợp trước: không có chi tiết theo request; giới hạn cardinality gì đó sai không?
LogsBản ghi sự kiện có timestamp, tốt nhất là structured (JSON)Chi tiết tùy ý, phù hợp điều tra sự cốĐắt khi khối lượng lớn; nhiễu; khó tổng hợpVì sao sai?
TracesĐường đi của một request qua các service, dưới dạng cây các span có đo thời gianChỉ đúng chỗ gây latency/lỗi xuyên qua ranh giới serviceCần instrument; thường phải samplingSai ở đâu?
EventsThay đổi rời rạc: deploy, feature flag, config, scalingGiải thích cái gì thay đổi ngay trước sự cốChỉ hữu ích nếu thực sự được ghi lại và liên kếtCái gì thay đổi?

Monitoring vs observability trong một câu: monitoring = known-unknowns (dashboard và alert cho các lỗi đã lường trước); observability = unknown-unknowns (telemetry cardinality cao, khám phá được, hỗ trợ đặt câu hỏi tùy ý khi đang xử lý sự cố). Chúng bổ trợ nhau, không phải đối thủ — bạn monitor những gì lường trước được và dựa vào observability cho mọi thứ còn lại.

Một cách đóng khung hữu ích từ cộng đồng observability là wide, structured events như một primitive hợp nhất: thay vì nghĩ theo ba silo tách biệt, hãy phát ra một event được chú thích phong phú cho mỗi đơn vị công việc (một request), mang theo hàng chục trường (user, endpoint, version, region, thời gian DB, cache hit, error), rồi dẫn xuất metrics, traces và logs từ các event đó. Dù bạn theo mô hình này hay ba trụ cột kinh điển, nguyên tắc vẫn như nhau: giữ lại bối cảnh, giữ cardinality query được, liên kết xuyên các tín hiệu.

Prometheus

Hệ thống metrics open-source mặc định, một dự án CNCF đã graduated:

PromQL — từ cơ bản đến các pattern bạn sẽ thực sự dùng:

# Tốc độ HTTP request mỗi giây trong 5 phút, theo service (RED: Rate)
sum by (service) (rate(http_requests_total[5m]))

# Tỷ lệ lỗi (5xx / tổng) — RED: Errors
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

# Latency p95 từ histogram — RED: Duration.
# Tổng hợp *rate của bucket* theo 'le' TRƯỚC, RỒI mới lấy quantile.
histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

# Phần trăm memory đã dùng trên mỗi node (USE: Utilization)
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

# Disk sẽ đầy trong 4 giờ tới? (dự đoán tuyến tính trên 6h lịch sử)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4*3600) < 0

# Top 5 pod theo CPU (dùng topk cho dashboard dễ đọc, đừng vẽ mọi thứ)
topk(5, sum by (pod) (rate(container_cpu_usage_seconds_total[5m])))

# Availability trong một cửa sổ, từ recording rule (tính SLO hiệu quả)
sum(rate(http_requests_total{status!~"5.."}[30d]))
  / sum(rate(http_requests_total[30d]))

Các “bẫy” PromQL cần khắc cốt:

Grafana

Lớp visualization và alerting tiêu chuẩn: dashboard trên Prometheus, Loki, Tempo, Elasticsearch, CloudWatch và hàng chục data source khác trong một giao diện. Các khả năng chính:

Quản lý log

# LogQL: log lỗi của một app, parse và lọc theo một trường JSON
{app="checkout", env="prod"} |= "error" | json | status >= 500

# Metric sinh từ log: tỷ lệ lỗi theo pod trong 5m
sum by (pod) (rate({app="checkout"} |= "error" [5m]))

# Trích latency từ log và tính p95 (metric query trên log)
quantile_over_time(0.95,
  {app="checkout"} | json | unwrap latency_ms [5m]) by (route)

Mô hình tư duy LogQL: label selector {...} chọn stream nào để đọc (có index, nhanh); mọi thứ sau một dấu | là một pipeline áp lên các dòng đã khớp (line filter, parser json/logfmt, label filter). Thu hẹp bằng label trước, lọc text sau — làm ngược lại thì chậm và tốn kém.

Distributed tracing

Khái niệm chính

SLI, SLO, SLA và error budget

Thuật ngữĐịnh nghĩaVí dụ
SLIChỉ số đo được về sức khỏe dịch vụ theo góc nhìn người dùngTỷ lệ request < 300 ms và không phải 5xx
SLOMục tiêu nội bộ cho một SLI trong một khoảng thời gian99.9% thành công trong 30 ngày
SLACam kết hợp đồng có chế tài (hoàn tiền/phạt)Uptime 99.5%/tháng, không đạt thì hoàn tiền
Error budget1 − SLO: mức “được phép lỗi”99.9% ⇒ 43.2 phút lỗi/tháng

Error budget là một công cụ chính sách, không chỉ là một con số: còn budget → cứ ship tính năng và chấp nhận rủi ro; hết budget → dừng các launch rủi ro và đầu tư vào reliability. Cách này giải quyết mâu thuẫn kinh điển dev-vs-ops bằng dữ liệu thay vì tranh cãi — reliability trở thành một “đồng tiền” chung, được định lượng, thay vì một cuộc cãi vã.

Chọn SLI tốt. Một SLI tốt là (1) lấy người dùng làm trung tâm (đo cái người dùng thực sự trải nghiệm, không phải proxy nội bộ), (2) là tỷ lệ giữa event tốt trên tổng event (nên tự nhiên là phần trăm), và (3) đo gần người dùng (ở load balancer hoặc client, không phải sâu trong một backend chưa từng thấy các request lỗi). Các loại SLI thường gặp: availability (tỷ lệ request thành công), latency (tỷ lệ phục vụ dưới một ngưỡng), quality/correctness, và freshness/durability cho hệ thống dữ liệu.

Toán học của các mục tiêu thường gặp (các “số 9”):

SLODowntime / 30 ngàyDowntime / năm
99% (“two nines”)~7.2 giờ~3.65 ngày
99.9% (“three nines”)~43.2 phút~8.77 giờ
99.95%~21.6 phút~4.38 giờ
99.99% (“four nines”)~4.3 phút~52.6 phút

Một kỷ luật quan trọng: đừng đặt 99.99% chỉ vì nghe cho oai. Mỗi con số 9 thêm vào tốn kém theo cấp số nhân, và một mục tiêu chặt hơn mức người dùng có thể cảm nhận (hoặc hơn mức các dependency chịu được) là lãng phí budget. Chọn SLO giữ người dùng vui và không chặt hơn thế.

Best practice về alerting

# Prometheus alert rule: SLO burn đa cửa sổ, đa burn-rate.
# Với SLO 99.9%, error budget là 0.001. Cháy ở mức 14.4x nghĩa là
# budget 30 ngày sẽ hết trong ~2 ngày nếu kéo dài.
# Phép AND của cửa sổ 1h và 5m tránh page cho một spike đã hồi phục.
groups:
  - name: slo-burn
    rules:
      - alert: HighErrorBudgetBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
              / sum(rate(http_requests_total[1h]))
          ) > (14.4 * 0.001)
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[5m]))
              / sum(rate(http_requests_total[5m]))
          ) > (14.4 * 0.001)
        for: 2m
        labels:
          severity: page
        annotations:
          summary: "Error budget đang cháy nhanh gấp 14.4 lần (SLO 99.9%)"
          description: "Tỷ lệ 5xx trong cả 1h và 5m đều vượt ngưỡng fast-burn."
          runbook_url: "https://runbooks.example.com/slo-burn"

      # Slow burn: page ít khẩn cấp hơn nhiều, bắt lỗi thấp nhưng đều đặn.
      - alert: SlowErrorBudgetBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
              / sum(rate(http_requests_total[6h]))
          ) > (1 * 0.001)
        for: 15m
        labels:
          severity: ticket
        annotations:
          summary: "Error budget đang cháy ~1x (chậm) — điều tra trong hôm nay"
          runbook_url: "https://runbooks.example.com/slo-burn"

Infrastructure monitoring vs application monitoring

TầngTheo dõi gìCông cụ/phương pháp điển hình
InfrastructureCPU, memory, disk, network, sức khỏe node/podnode_exporter, kube-state-metrics, cAdvisor; USE method (Utilization, Saturation, Errors) cho từng resource
ApplicationRequest rate, errors, duration, saturation; KPI nghiệp vụInstrument app (OTel/Prometheus client), RED method (Rate, Errors, Duration) cho từng service; Four Golden Signals của Google = latency, traffic, errors, saturation
End-user / syntheticTrải nghiệm người dùng thật; probe từ bên ngoàiBlackbox exporter, synthetic check, RUM (Real User Monitoring), Core Web Vitals

Hai phương pháp đáng thuộc lòng:

Metric hạ tầng hiếm khi đáng để page một mình — chúng cung cấp lý do sau khi alert theo triệu chứng đã nổ (“lỗi tăng vọt node hết memory”). Synthetic/blackbox check là lưới an toàn cho tình huống “sập toàn bộ mà telemetry nội bộ không kịp báo”, vì một hệ thống sập hẳn không thể tự phát telemetry.

Chi phí và cardinality

Chi phí telemetry tỷ lệ với cardinality của label (metrics), khối lượng (logs) và số lượng span (traces). Cardinality là số time series duy nhất = tích số các giá trị phân biệt của mọi label. Gắn label metric với một giá trị không giới hạn là kiểu tự gây sự cố kinh điển:

# BOM CARDINALITY — TUYỆT ĐỐI KHÔNG làm thế này.
# Mỗi user một time series; một triệu user = một triệu series,
# và Prometheus giữ tất cả trong memory. Đây là cách team làm OOM chính hệ giám sát.
http_requests_total{user_id="...", request_id="...", session="..."}

# Ổn: label bị giới hạn (status có ~5 giá trị, route là tập hữu hạn).
http_requests_total{status="500", route="/checkout", method="POST"}

Quy tắc: không bao giờ gắn label metric với user ID, request ID, email, URL đầy đủ chứa ID, hay timestamp. Đặt các chiều chi tiết, không giới hạn vào traces và logs (vốn sinh ra để làm việc đó), không phải metrics. Sample trace (tail-based để giữ cái đáng quan tâm). Phân bậc retention log (hot vài ngày, cold storage rẻ cho compliance). Hóa đơn observability phình to trong im lặng — một label tồi có thể làm chi phí metrics tăng 100× chỉ sau một đêm, nên hãy review cardinality như review một database schema.

Best Practices

  1. Alert theo triệu chứng và SLO, không theo nguyên nhân. Người dùng không quan tâm CPU; chỉ page khi hành vi mà người dùng thấy bị suy giảm, còn tín hiệu nguyên nhân để trên dashboard phục vụ chẩn đoán. Đây là quy tắc đòn bẩy cao nhất để giảm nhiễu.
  2. Định nghĩa SLI/SLO trước khi dựng dashboard. Instrument mà không có mục tiêu chỉ tạo ra biểu đồ đẹp, không tạo ra quyết định. Bắt đầu từ “một người dùng vui trải nghiệm gì?” và đo cái đó.
  3. Dùng burn-rate alert đa cửa sổ, đa burn-rate. Bắt được sự cố lớn trong vài phút lẫn rò rỉ chậm qua nhiều ngày, đồng thời bỏ qua các blip đã hồi phục — tốt hơn hẳn một ngưỡng tĩnh.
  4. Mọi page đều phải actionable và có runbook. Alert không kèm phản ứng được ghi chép và cần con người là nhiễu; review và cắt tỉa danh mục alert mỗi quý; xóa cái không bao giờ nổ hoặc không ai xử lý.
  5. Chuẩn hóa instrumentation trên OpenTelemetry. SDK trung lập vendor và Collector giúp đổi backend mà không phải instrument lại — instrument một lần, export đi đâu cũng được.
  6. Log JSON có cấu trúc và luôn kèm trace_id. Tốc độ debug đến từ khả năng liên kết xuyên trụ cột (metric → trace → log qua ID dùng chung); log không cấu trúc thì không join được vào đâu.
  7. Kiểm soát cardinality và volume một cách chủ động. Không dùng label metric không giới hạn; đẩy các chiều chi tiết vào traces/logs; sample trace; phân bậc retention log. Review chi phí telemetry như review một hóa đơn — nó phình lên trong im lặng.
  8. Giám sát từ bên ngoài nữa. Blackbox/synthetic probe bắt được các sự cố sập toàn phần mà telemetry nội bộ không thể tự báo, vì một hệ thống chết không phát ra gì.
  9. Ghi lại event deploy và thay đổi config. Đa số sự cố xảy ra ngay sau một thay đổi; overlay event deploy/flag/config lên dashboard trả lời “cái gì vừa đổi?” ngay lập tức và rút ngắn thời gian chẩn đoán.
  10. Coi dashboard và alert là code. Version hóa, review và provision (Terraform/Jsonnet/JSON) để chúng sống sót khi rebuild cluster, luôn audit được, và được code-review như mọi thay đổi khác.
  11. Phủ RED/USE cơ bản cho mọi service và resource. Một baseline nhất quán (RED cho mỗi service, USE cho mỗi resource, Four Golden Signals ở biên) tốt hơn một đống dashboard “mỗi nơi một kiểu”.
  12. Chọn mục tiêu SLO có chủ đích — đừng chạy đua số 9. Mỗi con số 9 thêm vào tốn kém cấp số nhân; một mục tiêu vượt mức người dùng cảm nhận hoặc dependency chịu được là lãng phí budget. Khớp SLO với kỳ vọng thật của người dùng.
  13. Chi tiêu error budget như một tính năng. Budget còn là “giấy phép” để ship và chấp nhận rủi ro; budget hết là tín hiệu chậm lại và gia cố. Làm chính sách rõ ràng và được cả dev lẫn ops tôn trọng.
  14. Instrument toàn bộ request path và propagate context. Một hop traceparent bị đứt sẽ âm thầm cắt vụn trace; hãy xác minh propagation qua mọi service, queue và ranh giới bất đồng bộ.
  15. Kiểm thử chính hệ thống observability trước khi cần đến. Chạy chaos/game day: chủ động phá và xác minh alert có nổ, dashboard có giải thích được, runbook có dùng được — để sự cố thật lúc 3 giờ sáng không phải là lần test đầu tiên.
  16. Giữ retention và access của telemetry hợp lý. Retention độ phân giải cao trong thời gian ngắn cộng lịch sử downsample/rollup dài hơn; và nhớ rằng log và trace có thể chứa PII — áp cùng mức access control và redaction như với mọi dữ liệu nhạy cảm.

Tài liệu tham khảo

Part of the DevOps Roadmap knowledge base.

Overview

Monitoring tells you whether your system is healthy; observability lets you ask why it isn’t. Monitoring is collecting predefined signals (CPU, error rate, queue depth) and alerting when they cross thresholds — it answers questions you thought to ask in advance. Observability is a property of the system: how well its external outputs (telemetry) let you explain any internal state, including failure modes nobody predicted. In microservice architectures, where a single user request may touch dozens of services, the “unknown unknowns” dominate, which is why observability has become a core DevOps competency rather than an ops afterthought.

The industry framing is the three pillars — metrics, logs, and traces — often extended with events (deploys, config changes, scaling actions) that provide the “what changed?” context. Each pillar answers a different question: metrics say something is wrong (cheap, aggregated, alertable), traces say where in the request path it’s wrong, and logs say why with full detail. Mature teams correlate all three: an alert fires on a metric, you jump to an exemplar trace, and pivot to the logs of the failing span. The pillars are a starting mental model, not a religion — the deeper goal is high-cardinality, correlated, explorable telemetry that lets you slice by any dimension (user, region, version, endpoint) during an incident.

Beyond tooling, observability underpins reliability engineering itself: SLIs quantify user-visible behavior, SLOs turn them into targets, and error budgets turn reliability into a currency teams can spend on feature velocity. Without measurement, “reliable” is just an opinion. The end state you’re aiming for: when something breaks at 3 a.m., an on-call engineer who has never seen this failure before can go from “an alert fired” to “here is the root cause” in minutes, using telemetry alone — without redeploying to add a print statement.

A note on telemetry as data, not dashboards

Beginners equate observability with dashboards. Dashboards are for known questions. Real observability shows up when the question is novel: “why are checkout requests from Android users in eu-west slow only on app version 4.2?” You can only answer that if the data carries those dimensions and you can query across them ad hoc. Design your telemetry so it can be sliced by dimensions you did not think to graph in advance.

Fundamentals

The three pillars (plus events)

SignalWhat it isStrengthsWeaknessesAnswers
MetricsNumeric time series (counters, gauges, histograms)Cheap to store, fast to query, ideal for alerting and trendsPre-aggregated: no per-request detail; cardinality limitsIs something wrong?
LogsTimestamped, ideally structured (JSON) event recordsArbitrary detail, great for forensicsExpensive at volume; noisy; hard to aggregateWhy is it wrong?
TracesA request’s path across services as a tree of timed spansPinpoints latency and failures across service boundariesRequires instrumentation; usually sampledWhere is it wrong?
EventsDiscrete changes: deploys, feature flags, config, scalingExplain what changed right before an incidentOnly useful if actually recorded and correlatedWhat changed?

Monitoring vs observability in one line: monitoring = known-unknowns (dashboards and alerts for anticipated failures); observability = unknown-unknowns (high-cardinality, explorable telemetry that supports ad-hoc questions during an incident). They are complementary, not rivals — you monitor the things you can predict and rely on observability for everything else.

A useful reframing from the observability community is wide, structured events as a unifying primitive: instead of thinking in three separate silos, emit one richly-annotated event per unit of work (a request), carrying dozens of fields (user, endpoint, version, region, DB time, cache hit, error), then derive metrics, traces, and logs from those events. Whether you adopt that model or the classic three pillars, the principle is the same: capture context, keep cardinality queryable, correlate across signals.

Prometheus

The default open-source metrics system, a CNCF graduated project:

PromQL — from basics to the patterns you’ll actually use:

# Per-second HTTP request rate over 5m, by service (RED: Rate)
sum by (service) (rate(http_requests_total[5m]))

# Error ratio (5xx / all) — RED: Errors
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

# p95 latency from a histogram — RED: Duration.
# Aggregate the *bucket rates* by 'le' first, THEN take the quantile.
histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

# Memory usage percentage per node (USE: Utilization)
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

# Disk full within 4 hours? (linear prediction over 6h of history)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4*3600) < 0

# Top 5 pods by CPU (use topk for readable dashboards, not raw everything)
topk(5, sum by (pod) (rate(container_cpu_usage_seconds_total[5m])))

# Availability over a window, from a recording rule (efficient SLO math)
sum(rate(http_requests_total{status!~"5.."}[30d]))
  / sum(rate(http_requests_total[30d]))

PromQL gotchas to internalize:

Grafana

The standard visualization and alerting layer: dashboards over Prometheus, Loki, Tempo, Elasticsearch, CloudWatch, and dozens of other data sources in one pane. Key capabilities:

Log management

# LogQL: error logs for one app, parsed and filtered by a JSON field
{app="checkout", env="prod"} |= "error" | json | status >= 500

# Log-derived metric: error rate per pod over 5m
sum by (pod) (rate({app="checkout"} |= "error" [5m]))

# Extract latency from logs and compute p95 (metric query over logs)
quantile_over_time(0.95,
  {app="checkout"} | json | unwrap latency_ms [5m]) by (route)

LogQL mental model: the label selector {...} chooses which streams to read (indexed, fast); everything after a | is a pipeline applied to matched lines (line filters, json/logfmt parsers, label filters). Narrow with labels first, filter text second — the reverse is slow and expensive.

Distributed tracing

Key Concepts

SLI, SLO, SLA and error budgets

TermDefinitionExample
SLIA measured indicator of service health from the user’s perspectiveFraction of requests < 300 ms and non-5xx
SLOInternal target for an SLI over a window99.9% successful over 30 days
SLAContractual promise with consequences (credits/penalties)99.5% monthly uptime or refund
Error budget1 − SLO: the allowed unreliability99.9% ⇒ 43.2 min/month of failure

The error budget is a policy instrument, not just a number: budget remaining → ship features and take risks; budget exhausted → freeze risky launches and invest in reliability. This resolves the classic dev-vs-ops tension with data instead of arguments — reliability becomes a shared, quantified currency rather than a shouting match.

Choosing good SLIs. A good SLI is (1) user-centric (measures what a user actually experiences, not internal proxies), (2) a ratio of good events to total events (so it’s naturally a percentage), and (3) measured close to the user (at the load balancer or client, not deep in a backend that never saw the failed requests). Common SLI types: availability (fraction of successful requests), latency (fraction served under a threshold), quality/correctness, and freshness/durability for data systems.

Common target math (the “nines”):

SLODowntime / 30 daysDowntime / year
99% (“two nines”)~7.2 hours~3.65 days
99.9% (“three nines”)~43.2 minutes~8.77 hours
99.95%~21.6 minutes~4.38 hours
99.99% (“four nines”)~4.3 minutes~52.6 minutes

A key discipline: don’t set 99.99% because it sounds good. Each extra nine costs exponentially more engineering, and a target tighter than what users can even perceive (or than your dependencies support) is wasted budget. Pick the SLO that keeps users happy and no tighter.

Alerting best practices

# Prometheus alert rule: multi-window, multi-burn-rate SLO alert.
# For a 99.9% SLO, the error budget is 0.001. Firing at 14.4x burn
# means the 30-day budget would be exhausted in ~2 days if sustained.
# The AND of a 1h and 5m window avoids paging on an already-recovered spike.
groups:
  - name: slo-burn
    rules:
      - alert: HighErrorBudgetBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
              / sum(rate(http_requests_total[1h]))
          ) > (14.4 * 0.001)
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[5m]))
              / sum(rate(http_requests_total[5m]))
          ) > (14.4 * 0.001)
        for: 2m
        labels:
          severity: page
        annotations:
          summary: "Error budget burning 14.4x too fast (99.9% SLO)"
          description: "5xx ratio over 1h and 5m both exceed the fast-burn threshold."
          runbook_url: "https://runbooks.example.com/slo-burn"

      # Slow burn: pages far less urgently, catches steady low-grade errors.
      - alert: SlowErrorBudgetBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
              / sum(rate(http_requests_total[6h]))
          ) > (1 * 0.001)
        for: 15m
        labels:
          severity: ticket
        annotations:
          summary: "Error budget burning ~1x (slow) — investigate today"
          runbook_url: "https://runbooks.example.com/slo-burn"

Infrastructure vs application monitoring

LayerWhat you watchTypical tools/methods
InfrastructureCPU, memory, disk, network, node/pod healthnode_exporter, kube-state-metrics, cAdvisor; USE method (Utilization, Saturation, Errors) per resource
ApplicationRequest rate, errors, duration, saturation; business KPIsApp instrumentation (OTel/Prometheus client), RED method (Rate, Errors, Duration) per service; Google’s Four Golden Signals = latency, traffic, errors, saturation
End-user / syntheticReal user experience; probes from outsideBlackbox exporter, synthetic checks, RUM (Real User Monitoring), Core Web Vitals

The two methods worth memorizing:

Infrastructure metrics rarely deserve pages on their own — they provide the why once a symptom-based alert fires (“errors spiked because the node ran out of memory”). Synthetic/blackbox checks are your safety net for “the whole thing is down and nothing internal noticed”, because a system that’s fully down can’t emit its own telemetry.

Cost and cardinality

Telemetry costs scale with label cardinality (metrics), volume (logs), and span count (traces). Cardinality is the number of unique time series = the product of every label’s distinct values. Labeling a metric with an unbounded value is the classic self-inflicted outage:

# CARDINALITY BOMB — do NOT do this.
# One time series per user; a million users = a million series,
# and Prometheus keeps them all in memory. This is how teams OOM their monitoring.
http_requests_total{user_id="...", request_id="...", session="..."}

# Fine: labels are bounded (status has ~5 values, route is a bounded set).
http_requests_total{status="500", route="/checkout", method="POST"}

Rules of thumb: never label metrics with user ID, request ID, email, full URL with IDs, or timestamps. Put unbounded, high-detail dimensions in traces and logs (which are built for it), not metrics. Sample traces (tail-based to keep the interesting ones). Tier log retention (hot for days, cheap cold storage for compliance). Observability bills grow silently — a single bad label can 100× your metrics cost overnight, so review cardinality the way you review a database schema.

Best Practices

  1. Alert on symptoms and SLOs, not causes. Users don’t care about CPU; page only when user-visible behavior degrades, and keep cause signals on dashboards for diagnosis. This is the highest-leverage rule for reducing noise.
  2. Define SLIs/SLOs before building dashboards. Instrumentation without targets produces pretty graphs and no decisions. Start from “what does a happy user experience?” and measure that.
  3. Use multi-window, multi-burn-rate alerting. It catches fast outages in minutes and slow leaks over days, while ignoring recovered blips — dramatically better than a static threshold.
  4. Make every page actionable, with a runbook. An alert without a documented, human-necessary response is noise. Review and prune the alert catalog quarterly; delete what never fires or never gets acted on.
  5. Standardize on OpenTelemetry for instrumentation. Vendor-neutral SDKs and the Collector keep you free to switch backends without re-instrumenting — instrument once, export anywhere.
  6. Log structured JSON and always include trace_id. Cross-pillar correlation (metric → trace → log by shared ID) is where debugging speed comes from; unstructured logs can’t be joined to anything.
  7. Control cardinality and volume deliberately. No unbounded metric labels; push high-detail dimensions into traces/logs; sample traces; tier log retention. Review telemetry cost like you review a bill — it grows silently.
  8. Monitor from the outside too. Blackbox/synthetic probes catch total failures your internal telemetry can’t report, because a dead system emits nothing.
  9. Record deploy and config events. Most incidents follow a change; overlaying deploy/flag/config events on dashboards answers “what changed?” instantly and shrinks time-to-diagnosis.
  10. Treat dashboards and alerts as code. Version, review, and provision them (Terraform/Jsonnet/JSON) so they survive cluster rebuilds, stay auditable, and can be code-reviewed like any change.
  11. Cover the RED/USE basics for every service and resource. A consistent baseline (RED per service, USE per resource, Four Golden Signals at the edge) beats a pile of bespoke, inconsistent dashboards.
  12. Pick SLO targets deliberately — don’t chase nines. Each extra nine costs exponentially more; a target beyond what users perceive or dependencies support wastes budget. Match the SLO to real user expectations.
  13. Spend the error budget as a feature. A remaining budget is permission to ship and take risks; an exhausted one is a signal to slow down and harden. Make the policy explicit and honored by both dev and ops.
  14. Instrument the request path end-to-end and propagate context. A broken traceparent hop silently fragments traces; verify propagation across every service, queue, and async boundary.
  15. Test your observability before you need it. Run chaos/game days: break things on purpose and verify alerts fire, dashboards explain, and runbooks work — so the first real 3 a.m. incident isn’t the first test.
  16. Keep telemetry retention and access sane. Short high-resolution retention plus longer downsampled/rolled-up history; and remember logs and traces can contain PII — apply the same access control and redaction you would to any sensitive data.

References