Опирается на правила: R-OBS-SLO-1R-OBS-SLO-4 и R-OBS-SLO-X1R-OBS-SLO-X3 из Observability Style Guide → раздел 7. SLO и алерты.

Важно знать

  • Каждый critical-endpoint имеет SLO (например, 99.9% non-5xx + p95 < 500ms на rolling 30-day window).
  • В Python метрика запросов экспонируется через prometheus-fastapi-instrumentator — имя отличается от Micrometer: http_requests_total и http_request_duration_seconds.
  • Multi-window multi-burn-rate alerts по Google SRE Workbook: fast burn (1h, rate > 14.4) и slow burn (6h, rate > 6).
  • Error budget: 99.9% target = 43 минуты downtime/month. 100% target — нечем оперировать.
  • Алерт на исчерпание бюджета (< 10%) → сигнал команде переключиться с features на reliability.
  • Алерты отдельные от SLO: infra (event-loop lag, worker saturation), domain (бизнес-фейлы), resilience (CB open), Kafka lag.
  • Alert на каждый ERROR → fatigue → команда игнорирует. Агрегируй по типу исключения.
  • Каждый алерт имеет runbook. Без инструкции дежурный не знает, кому звонить и что трогать.

SLO (Service Level Objective) — обещание уровня обслуживания: не «всё работает», а «99.9% запросов успешны в течение 30-day window». Разница принципиальная: появляется error budget, появляется инструмент приоритизации reliability vs features.

Экспозиция HTTP-метрик в FastAPI

R-OBS-SLO-1 требует SLI-метрику для каждого critical-endpoint. В Python/FastAPI источником служит prometheus-fastapi-instrumentator, который авто-регистрирует гистограмму http_request_duration_seconds и счётчик http_requests_total с labels handler, method, status_code.

from fastapi import FastAPI
from prometheus_fastapi_instrumentator import Instrumentator

app = FastAPI()

Instrumentator(
    should_group_status_codes=False,
    should_ignore_untemplated=True,
    excluded_handlers=["/health/live", "/health/ready", "/metrics"],
).instrument(app).expose(app, endpoint="/metrics", include_in_schema=False)

Дополнительные бизнес-счётчики — через prometheus_client напрямую:

from prometheus_client import Counter, Histogram

ORDER_CREATED = Counter(
    "order_created_total",
    "Successful order creations",
    ["payment_method"],
)

ORDER_FAILED = Counter(
    "order_failed_total",
    "Failed order creations",
    ["reason"],
)

CHECKOUT_DURATION = Histogram(
    "checkout_duration_seconds",
    "End-to-end checkout latency",
    buckets=[0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
)

Имена — snake_case с единицей (_seconds, _total): правило R-OBS-MTR-6.

SLO для critical endpoints

R-OBS-SLO-1: для каждого critical-endpoint — availability и latency SLO.

EndpointAvailability SLOLatency SLO
POST /orders99.9% non-5xxp95 < 500ms
POST /payments99.95% non-5xxp95 < 1s
GET /orders/{id}99.95% non-5xxp95 < 200ms
GET /products/search99.5% non-5xxp95 < 800ms

Расчёт SLI в Prometheus для prometheus-fastapi-instrumentator:

# Availability SLI — доля non-5xx запросов
sum(rate(http_requests_total{handler="/orders",method="POST",status_code!~"5.."}[30d]))
  /
sum(rate(http_requests_total{handler="/orders",method="POST"}[30d]))

# Latency SLI — p95
histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket{handler="/orders",method="POST"}[30d]))
)

Обрати внимание: label называется handler (значение — шаблон пути, например /orders/{order_id}), не uri как в Micrometer. Перед написанием PromQL-запросов стоит проверить реальные имена labels через /metrics.

Multi-window multi-burn-rate

R-OBS-SLO-2: классический подход SRE Workbook chapter 5.

Идея: SLO 99.9% даёт error budget 0.1% за 30 дней. Если за 1 час расходуется 5% бюджета — это быстрое сжигание, при таком темпе бюджет кончится за 20 часов. Это алерт.

Формула burn rate:

burn_rate = (error_rate_in_window) / (1 - SLO_target)

Для SLO 99.9% (target = 0.999, budget = 0.001):

  • burn rate > 14.4 (window 1h) → 5% бюджета сгорает за 1 час → немедленный инцидент
  • burn rate > 6 (window 6h) → медленная деградация → создай ticket
  • burn rate ≤ 1 → нормальная скорость
groups:
  - name: orders.slo
    rules:
      - alert: OrdersSloFastBurn
        expr: |
          (
            sum(rate(http_requests_total{handler="/orders",method="POST",status_code=~"5.."}[1h]))
            /
            sum(rate(http_requests_total{handler="/orders",method="POST"}[1h]))
          ) > (14.4 * (1 - 0.999))
        for: 2m
        labels:
          severity: critical
          team: order-service
        annotations:
          summary: "Orders SLO fast burn — потенциальный outage"
          runbook: https://runbooks.internal/orders-slo-fast-burn

      - alert: OrdersSloSlowBurn
        expr: |
          (
            sum(rate(http_requests_total{handler="/orders",method="POST",status_code=~"5.."}[6h]))
            /
            sum(rate(http_requests_total{handler="/orders",method="POST"}[6h]))
          ) > (6 * (1 - 0.999))
        for: 15m
        labels:
          severity: warning
          team: order-service
        annotations:
          summary: "Orders SLO slow burn — деградация без outage"
          runbook: https://runbooks.internal/orders-slo-slow-burn

Fast burn будит ops немедленно. Slow burn создаёт ticket — деградация, не критичная, но требует разбора на следующий рабочий день.

Error budget exhaustion

R-OBS-SLO-3: отдельный алерт на остаток бюджета за rolling 30-day window.

# Сколько бюджета осталось (0 = весь израсходован, 1 = весь свободен)
1 - (
  (1 - sum(rate(http_requests_total{handler="/orders",method="POST",status_code!~"5.."}[30d]))
        /
       sum(rate(http_requests_total{handler="/orders",method="POST"}[30d])))
  /
  (1 - 0.999)
)

Алерт если < 10%:

- alert: OrdersErrorBudgetExhausted
  expr: <budget_remaining_expression> < 0.1
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "Только 10% error budget остался — /orders"
    description: |
      Команда переключается с features на reliability.
      Релизы рискованных изменений приостановлены до восстановления бюджета.
    runbook: https://runbooks.internal/orders-error-budget

Это не «срочно чинить ночью», а сигнал бизнесу: следующий месяц команда фокусируется на устойчивости.

Алерты отдельные от SLO

R-OBS-SLO-4: SLO — про user-facing успешность. Python/FastAPI-сервис порождает отдельные категории алертов.

КатегорияМетрикаДействие
Infrastructureprocess_resident_memory_bytes > 1.5G, event-loop lag > 100msSRE: scale, profile
Worker saturationuvicorn_workers_busy / uvicorn_workers_total > 0.9scale workers
Domainorder_failed_total rate > 100/minProduct: что-то изменилось в данных
Resiliencecircuitbreaker_state{state="open"} (через stamina или custom)внешний сервис недоступен
Cachecache_hits / (hits + misses) < 0.7tune TTL / size
Kafka consumer lagkafka_consumer_lag_max > 10000scale consumers

Пример domain-алерта для сервиса заказов:

from prometheus_client import Counter

PRODUCT_CHECKOUT_BLOCKED = Counter(
    "product_checkout_blocked_total",
    "Checkout blocked due to inventory hold failure",
    ["reason"],
)
- alert: ProductCheckoutBlockedHigh
  expr: sum(rate(product_checkout_blocked_total[5m])) > 5
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Высокий rate заблокированных checkout — проверь инвентарь"
    runbook: https://runbooks.internal/product-checkout-blocked

Resilience CB open → запросы уходят на fallback → SLO ещё в норме, но проблема назревает. Поэтому инфра-алерты нужны параллельно с SLO-алертами.

Что запрещено

АнтипаттернПравилоЧто взамен
Alert на каждый ERROR в логахR-OBS-SLO-X1rate(http_requests_total{status_code=~"5.."}[5m]) с for: 5m
SLO 100% targetR-OBS-SLO-X299.9% (43m/mo) или 99.95% / 99.99%
Алерт без annotations.runbookR-OBS-SLO-X3URL runbook обязателен в каждом алерте
Один алерт «что-то сломалось»R-OBS-SLO-4категории: infra / domain / resilience / SLO burn
Burn rate только по 30d без short windowR-OBS-SLO-2multi-window: 1h fast + 6h slow
Latency SLO по avgR-OBS-SLO-1histogram_quantile(0.95, ...) — только percentile
for: отсутствует или for: 0mR-OBS-SLO-2for: 2m fast / for: 15m slow — фильтр flapping
High-cardinality label в SLI (customer_id=<value>)R-OBS-MTR-X1только низкая cardinality: handler/method/status_class

Куда дальше

  • Metrics — prometheus-client, prometheus-fastapi-instrumentator, business-метрики, cardinality.
  • Tracing — manual span через context-manager, sampling, разбор burn через traces с ошибками.
  • Health checks — почему liveness/readiness не для бизнес-SLO.
  • Logging — structlog, contextvars, связка trace_id → лог → алерт.
  • Context propagation — bind_contextvars, async-propagation, copy_context для thread-offload.
  • Конфигурация — отдельный management-порт, explicit endpoints, /metrics без auth в проде.
  • Google SRE Workbook ch.5 — Alerting on SLOs — первоисточник multi-window burn rate.