Перейти до вмісту

Спостережуваність і діагностика

Як зрозуміти, що відбувається із сервісом у продакшені: які сигнали збирати, як повідомити балансувальнику про здоров’я екземпляра, як розслідувати високий CPU або витік на живому процесі й як налаштувати алерти, що будять лише по ділу. На цій темі перевіряють досвід експлуатації: що ви робите, коли «все гальмує», а в логах тиша.

Перед цією темою: Конфігурація й логування.

Junior+

Що таке спостережуваність і чим логи, метрики й трасування відрізняються одне від одного?

Section titled “Що таке спостережуваність і чим логи, метрики й трасування відрізняються одне від одного?”
Відповідь

Що це

Спостережуваність — властивість системи, яка дає змогу за її зовнішніми даними зрозуміти внутрішній стан. Основних видів даних, або сигналів, три.

Три сигнали

Сигнал На яке питання відповідає
Метрика: число в часі скільки і як швидко: RPS, частка помилок, затримка
Трасування: шлях одного запиту де витрачається час і де впало
Лог: окрема подія що саме сталося і з якими даними

Як користуватися

  1. Алерт за метрикою повідомляє: зросла частка помилок.
  2. Трейс запиту, що впав, показує, у якому сервісі чи запиті до БД проблема.
  3. Логи з тим самим TraceId дають текст помилки й параметри.

Типова помилка

Рахувати метрики за логами: «скільки помилок за хвилину» через пошук в Elastic. Це повільно й дорого, для лічильників є метрики.

Як влаштовано

  • Метрика — значення з мітками, агреговане за часом: лічильник, гістограма, поточне значення. Зберігається як часовий ряд, і його розмір не залежить від кількості запитів.
  • Трейс — дерево спанів. Спан — одна операція (вхідний запит, SQL-запит, HTTP-виклик) з початком, тривалістю й атрибутами. Між сервісами контекст передає заголовок traceparent.
  • Лог — подія з рівнем, шаблоном і полями. Обсяг росте разом із трафіком.

Де зберігають

Сигнал Типовий стек
Логи ELK (Elasticsearch, Logstash, Kibana), OpenSearch, Seq, Loki
Метрики Prometheus і Grafana, VictoriaMetrics
Трейси Jaeger, Grafana Tempo, Zipkin
Усе разом APM: Elastic APM, Application Insights, Datadog

Пастки

  • Немає TraceId у логах. Сигнали не пов’язані, і розслідування перетворюється на пошук за часом.
  • Pull проти push. Prometheus сам забирає метрики з ендпоінту сервісу, а OTLP надсилає їх із сервісу. Від моделі залежить, хто кому відкриває мережу.

Правило

Алерти — за метриками, пошук вузького місця — за трейсами, деталі — за логами.

Моніторинг і спостережуваність

  • Моніторинг — заздалегідь задані дашборди й алерти. Він відповідає на відомі питання: чи живий сервіс, який p99.
  • Спостережуваність — можливість відповісти на питання, про яке не думали заздалегідь. Наприклад, чому повільно лише в одному регіоні на одній версії клієнта. Для цього потрібні події з багатим контекстом і зрізи за будь-яким полем.

Ціна

  • Метрика коштує однаково при 10 і 10 000 RPS, але кожна унікальна комбінація міток — окремий ряд.
  • Трейси за повного збору дають десятки спанів на запит. Їх семплюють: head-based у застосунку або tail-based у колекторі, щоб зберегти помилки й повільні запити.
  • Логи платять за індексацію й зберігання кожної події, тому Debug у продакшені вимкнено.

Компроміси

  • Агент APM-вендора дає швидкий старт, але прив’язує до нього. OpenTelemetry — стандартний API і протокол OTLP: бекенд змінюється конфігурацією, ціною власного конвеєра (OpenTelemetry Collector).
  • Exemplars пов’язують точку на графіку метрики з конкретним TraceId: з викиду p99 можна одразу перейти до повільного трейсу.

Як перевірити в продакшені

Розберіть останній інцидент: скільки минуло від алерту до знайденої причини і чи довелося додавати логи й передеплоювати, щоб її знайти. Якщо довелося, спостережуваності бракує.

Підсумок: моніторинг відповідає на відомі питання, спостережуваність — на нові, і платять за неї обсягом телеметрії.

Приклад
# Метрика: агрегат за інтервал, без деталей окремого запиту
http_server_request_duration_seconds_count{http_route="/orders",http_response_status_code="500"} 17
# Трейс: дерево спанів одного запиту
POST /orders 480 ms trace 4bf92f3577b34da6
├─ SELECT orders 12 ms
└─ POST payments-api 455 ms ← тут час
# Лог: подія з полями й тим самим TraceId
{"Level":"Error","MessageTemplate":"Payment {PaymentId} failed","PaymentId":"p-42","TraceId":"4bf92f3577b34da6"}

Один збій видно трьома способами: метрика повідомляє про 17 помилок, трейс указує на payments-api, лог дає номер платежу.

Middle+

Як зробити health checks в ASP.NET Core і чим liveness відрізняється від readiness?

Section titled “Як зробити health checks в ASP.NET Core і чим liveness відрізняється від readiness?”
Відповідь

Що це

Health check — HTTP-ендпоінт, який повідомляє стан застосунку: Healthy, Degraded або Unhealthy. Його опитують балансувальник, оркестратор (наприклад, Kubernetes) і моніторинг.

Як користуватися

  1. builder.Services.AddHealthChecks() — реєструє сервіс перевірок.
  2. .AddCheck<T>("name") — додає перевірку: клас, що реалізує IHealthCheck.
  3. app.MapHealthChecks("/health") — публікує ендпоінт.

Коди за замовчуванням: Healthy і Degraded — 200, Unhealthy — 503.

Liveness і readiness

Проба Що за провалу
Liveness: процес живий і не завис? процес перезапускають
Readiness: готовий приймати трафік? екземпляр прибирають із балансування

Типова помилка

Один /health з перевіркою БД для обох проб. Упала БД — оркестратор по колу перезапускає всі екземпляри.

Як влаштовано

  1. Кожна перевірка реєструється з ім’ям, тегами, failureStatus і тайм-аутом.
  2. MapHealthChecks з Predicate вибирає перевірки за тегами. Так з одного набору виходять /health/live і /health/ready.
  3. Вибрані перевірки виконуються на кожен запит до ендпоінту, підсумок — найгірший статус.
  4. ResponseWriter змінює формат відповіді. За замовчуванням це одне слово: Healthy.

Що перевіряти

Ендпоінт Перевірки
/health/live нічого зовнішнього: відповів — отже, живий (Predicate = _ => false)
/health/ready БД, кеш, брокер — без чого екземпляр марний

Готові перевірки: AddDbContextCheck з пакета Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore і бібліотека AspNetCore.Diagnostics.HealthChecks (SQL Server, PostgreSQL, Redis, RabbitMQ). Другу Microsoft не підтримує.

Пастки

  • Немає тайм-ауту. Зависла БД тримає пробу довше, ніж чекає оркестратор.
  • Частота. Оркестратор, балансувальник і моніторинг разом дають десятки запитів до БД за хвилину на кожен екземпляр.
  • AllowCachingResponses керує HTTP-заголовками кешування, а не кешем результату перевірки.
  • Детальна відповідь. Список залежностей і тексти помилок корисні зловмиснику. Ендпоінт закривають через RequireHost на службовий порт або RequireAuthorization.

Правило

Liveness — без зовнішніх залежностей, readiness — лише критичні залежності й завжди з тайм-аутом.

Під капотом

  • DefaultHealthCheckService запускає перевірки паралельно через Task.Run і чекає на всі. Кожна отримує свій DI-scope і токен, який скасовується за її тайм-аутом.
  • Виняток або тайм-аут дають failureStatus реєстрації, за замовчуванням Unhealthy.
  • IHealthCheckPublisher отримує HealthReport від фонового сервісу: перший запуск через 5 секунд, далі кожні 30. З .NET 8 для окремої реєстрації можна задати свої Delay і Period.

Ціна

  • Важка перевірка, помножена на часті проби, — відчутне навантаження на залежність. Його знімають кешем результату на кілька секунд усередині перевірки.
  • Інший варіант — publisher зберігає останній звіт, а власний ендпоінт віддає його без запиту до БД. Вбудований MapHealthChecks так не вміє: він виконує перевірки за кожного звернення.

Компроміси

  • Каскадна відмова. Readiness з перевіркою спільної залежності вимикає весь пул разом. Якщо без неї сервіс бодай частково працює (кеш, черга), краще віддати Degraded, і нехай відмовляють окремі запити.
  • Глибокі перевірки. Сервіс A перевіряє B, B перевіряє C, і падіння C робить «хворими» всіх. Сусідні сервіси в readiness зазвичай не перевіряють: від їхніх збоїв захищають тайм-аути й circuit breaker.

Як перевірити в продакшені

Publisher пише статус і тривалість кожної перевірки в лог або метрику. Так видно флапи й повільні перевірки. Алерт ставлять не на одну невдалу пробу, а на частку неготових екземплярів.

Підсумок: health check керує трафіком, тому в ньому перевіряють лише те, що справді змінює рішення про трафік.

Приклад
Microsoft.Data.SqlClient
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks()
.AddCheck<SqlHealthCheck>("sql", tags: ["ready"], timeout: TimeSpan.FromSeconds(3));
var app = builder.Build();
// Liveness: жодної перевірки — відповів, отже процес живий
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });
// Readiness: лише перевірки з тегом "ready"; Unhealthy → 503
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.Run();
sealed class SqlHealthCheck(IConfiguration config) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default)
{
// Виняток або тайм-аут (ct скасується через 3 с) стануть Unhealthy
await using var conn = new SqlConnection(config.GetConnectionString("Default"));
await conn.OpenAsync(ct);
await using var cmd = new SqlCommand("SELECT 1", conn);
await cmd.ExecuteScalarAsync(ct);
return HealthCheckResult.Healthy();
}
}
Publisher: статус перевірок у лог раз на 15 секунд · Senior
using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy());
builder.Services.Configure<HealthCheckPublisherOptions>(o => o.Period = TimeSpan.FromSeconds(15));
builder.Services.AddSingleton<IHealthCheckPublisher, LogPublisher>();
var app = builder.Build();
app.Run();
sealed class LogPublisher(ILogger<LogPublisher> logger) : IHealthCheckPublisher
{
public Task PublishAsync(HealthReport report, CancellationToken ct)
{
foreach (var (name, entry) in report.Entries)
{
// Історію статусів і тривалостей видно в сховищі логів, а не лише в останній відповіді
logger.LogInformation("Health check {Check}: {Status} in {Duration} ms",
name, entry.Status, entry.Duration.TotalMilliseconds);
}
return Task.CompletedTask;
}
}
Middle+

Які метрики збирати для вебсервісу і як?

Section titled “Які метрики збирати для вебсервісу і як?”
Відповідь

Що це

Метрика — числовий показник, який сервіс записує постійно, а система моніторингу зберігає як графік у часі.

Що збирати: RED

  • Rate — запитів за секунду.
  • Errors — частка запитів із помилкою, наприклад відповідей 5xx.
  • Duration — час відповіді.

Як користуватися

  1. Вбудована метрика ASP.NET Core http.server.request.duration (.NET 8+) уже дає всі три значення з розбиттям за маршрутом і кодом відповіді.
  2. Бізнес-події («створено замовлення») рахують власним лічильником Counter<T>.
  3. Локально метрики дивляться через dotnet-counters, у продакшені — у Prometheus і Grafana або в APM.

Типова помилка

Дивитися на середній час відповіді. 99 запитів по 10 мс і один на 10 секунд дають середнє близько 110 мс, а користувач чекав 10 секунд.

Як влаштовано

  • Meter — іменоване джерело метрик. В ASP.NET Core його створюють через IMeterFactory з DI (.NET 8+).
  • Мітки (tags) — пари «ключ — значення» в кожного вимірювання: маршрут, код відповіді, спосіб оплати.
Інструмент Для чого
Counter<T> лічильник подій, що росте: замовлення, помилки
UpDownCounter<T> значення росте й спадає: активні з’єднання
Histogram<T> розподіл: тривалість, розмір відповіді
ObservableGauge<T> поточне значення, читається під час збору: довжина черги

З .NET 9 є і синхронний Gauge<T>: значення записують у момент зміни.

Коли що обирати

Підхід Що вимірює
RED кожен сервіс чи ендпоінт: rate, errors, duration
USE кожен ресурс (CPU, пам’ять, пул з’єднань): утилізація, насичення, помилки
Золоті сигнали latency, traffic, errors, saturation — RED плюс насичення

Вбудовані метрики

  • Microsoft.AspNetCore.Hosting: http.server.request.duration, http.server.active_requests.
  • Microsoft.AspNetCore.Server.Kestrel: з’єднання, черги, TLS-рукостискання.
  • System.Runtime (.NET 9+): GC, пул потоків, CPU, dotnet.exceptions.
  • У .NET 10 додалися метрики автентифікації, авторизації, Identity і Blazor.

Пастки

  • user.id або шлях /orders/42 у мітці — мільйони рядів. Мітка — шаблон маршруту http.route.
  • Тривалість у мілісекундах в одному сервісі й у секундах в іншому. Одиниці вказують явно (s, By, {order}), стандарт — секунди.

Правило

RED на кожен ендпоінт, USE на кожен обмежений ресурс, лічильники — на ключові бізнес-операції.

Під капотом

  • Add або Record без слухача — перевірка прапорця й вихід. Слухач (OpenTelemetry SDK, MeterListener, dotnet-counters через EventPipe) агрегує в процесі: суму для лічильника, бакети для гістограми.
  • У сховище потрапляє не кожне вимірювання, а ряд на кожну комбінацію міток. Prometheus забирає агрегати по HTTP, OTLP надсилає їх сам.

Ціна

  • Рядів = добуток кількостей значень міток × бакети гістограми × екземпляри. 50 маршрутів × 10 кодів × 4 методи × 15 бакетів × 20 подів — до 600 тисяч рядів в однієї метрики.
  • Сховище метрик платить пам’яттю за кожен активний ряд, тому нова мітка з тисячею значень — рішення, а не дрібниця.

Компроміси

  • Перцентиль за гістограмою приблизний: точність залежить від меж бакетів. Для http.server.request.duration з OpenTelemetry за замовчуванням межі від 5 мс до 10 с. Свої межі задають InstrumentAdvice (.NET 9+) або View в OpenTelemetry.
  • Перцентилі не можна усереднювати за екземплярами: p99 рахують із суми бакетів, у Prometheus — histogram_quantile за sum by (le).
  • Середнє й максимум дешевші, але не відповідають на питання «скільки користувачів чекали довше за секунду». Частка запитів, швидших за поріг, рахується з тих самих бакетів.

Як перевірити в продакшені

  • dotnet-counters monitor -n MyApp --counters Shop.Orders — власні метрики живого процесу без експорту.
  • У Prometheus count(http_server_request_duration_seconds_bucket) показує, скільки рядів дає одна метрика.

Підсумок: перцентилі рахують із підсумованих гістограм, а ціну метрик визначає кардинальність міток.

Приклад
using System.Diagnostics;
using System.Diagnostics.Metrics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<OrderMetrics>();
var app = builder.Build();
app.MapPost("/orders", async (OrderMetrics metrics) =>
{
long start = Stopwatch.GetTimestamp();
await Task.Delay(50); // обробка замовлення
metrics.OrderCreated("card", Stopwatch.GetElapsedTime(start));
return Results.Accepted();
});
app.Run();
sealed class OrderMetrics
{
private readonly Counter<long> _created;
private readonly Histogram<double> _duration;
private int _queueLength;
public OrderMetrics(IMeterFactory factory)
{
Meter meter = factory.Create("Shop.Orders");
_created = meter.CreateCounter<long>("shop.orders.created", unit: "{order}");
_duration = meter.CreateHistogram<double>("shop.orders.duration", unit: "s",
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5] });
// Значення читається лише в момент збору
meter.CreateObservableGauge("shop.orders.queue.length", () => Volatile.Read(ref _queueLength), unit: "{order}");
}
public void OrderCreated(string paymentMethod, TimeSpan elapsed)
{
// Кілька можливих значень — низька кардинальність; id замовлення сюди не кладуть
var tag = new KeyValuePair<string, object?>("payment.method", paymentMethod);
_created.Add(1, tag);
_duration.Record(elapsed.TotalSeconds, tag);
}
}
Окно терминала
# Власні та вбудовані метрики запущеного процесу
dotnet-counters monitor -n MyShop --counters Shop.Orders,Microsoft.AspNetCore.Hosting
Senior+

Як розібрати проблему продуктивності чи пам’яті на живому сервері?

Section titled “Як розібрати проблему продуктивності чи пам’яті на живому сервері?”
Відповідь

Що це

Діагностика на живому сервері — пошук причини без налагоджувача й без зупинки сервісу.

Як користуватися

  1. За метриками визначити симптом: що зросло і коли.
  2. Подивитися лічильники процесу: dotnet-counters monitor -n MyApp.
  3. Високий CPU — записати трейс на 30 секунд і відкрити у Visual Studio або PerfView.
  4. Росте пам’ять або процес завис — зняти дамп і відкрити в dotnet-dump analyze.

Інструменти

Інструмент Що дає
dotnet-counters живі метрики: CPU, GC, пул потоків, запити
dotnet-trace де витрачається час, події рантайму
dotnet-dump знімок усієї пам’яті й стеків потоків
dotnet-gcdump лише граф об’єктів купи

Типова помилка

Одразу перезапустити процес, нічого не знявши. Симптом зник, докази теж, і за день усе повториться.

Як влаштовано

Інструменти підключаються до процесу через diagnostic port: іменований канал у Windows або Unix-сокет у /tmp у Linux. Дані йдуть через EventPipe, тому не потрібні ні SDK на сервері, ні перезапуск. Потрібні той самий користувач, що в процесу, або root.

Коли що обирати

Симптом Що дивитися
Високий CPU dotnet-trace: гарячі стеки в PerfView або speedscope
Росте пам’ять зростання купи після GC у dotnet-counters, потім два dotnet-gcdump
Повільно, CPU низький черга й потоки пулу в dotnet-counters, стеки в дампі
Повільні запити до БД трейси з SQL-спанами, потім план запиту
Сплеск винятків dotnet.exceptions за error.type

Подробиці: витоки — у темі Збирання сміття й ресурси, голодування пулу — у Багатопотоковості, плани запитів — в Індексах.

Пастки

  • Профіль dotnet-trace за замовчуванням семплює всі керовані потоки, зокрема ті, що чекають. У звіті відокремлюють роботу від очікування. Профіль cpu-sampling зі старих інструкцій у нових версіях інструмента видалено.
  • У Linux інструменту й процесу потрібен спільний TMPDIR, інакше підключення відвалиться за тайм-аутом.
  • dotnet-gcdump викликає повне збирання сміття, на великій купі це помітна пауза.

Правило

Одна гіпотеза — один точковий збір. Спочатку лічильники, потім трейс, дамп — останнім.

План за кроками

  1. Пом’якшити. Відкотити реліз, якщо проблема збіглася з деплоєм, або вивести екземпляр із балансування. Один «хворий» екземпляр залишити для збору.
  2. Метрики. Коли почалося, усі екземпляри чи один, чи збігається з деплоєм, зростанням трафіку, паузами GC.
  3. Легкий збір. dotnet-counters, потім dotnet-trace на 30–60 секунд.
  4. Важкий збір. Дамп — лише коли потрібен. Перед ним перевірити місце на диску (дамп важить як пам’ять процесу) і ліміт пам’яті контейнера: збір може підкачати пам’ять і довести до OOM kill.
  5. Аналіз поза продакшеном. Дамп копіюють на робочу машину й розбирають там.
  6. Перевірка. Виправлення підтверджують тими самими метриками.

Контейнери

  • Sidecar бачить процес, якщо в контейнерів спільний простір процесів і спільний /tmp (наприклад, emptyDir). Для dotnet-dump потрібна можливість ptrace (SYS_PTRACE).
  • dotnet-monitor — готовий sidecar з HTTP API: /dump, /gcdump, /trace, /metrics. Його collection rules з тригерами на кшталт AspNetRequestDuration самі знімають артефакт у момент проблеми й вивантажують у сховище.
  • Для падінь рантайм пише дамп сам за DOTNET_DbgEnableMiniDump=1.

Ціна

  • Поки пишеться дамп, процес стоїть: на великій купі це секунди й більше.
  • У дампі вся пам’ять: рядки підключення, токени, персональні дані. Його зберігають і передають як секрет і видаляють після розбору.

Підсумок: у продакшені спочатку рятують користувачів, потім знімають мінімально достатні докази, а важкі знімки роблять на виведеному з балансування екземплярі.

Приклад
Окно терминала
# 1. Які .NET-процеси видно (той самий користувач або root)
dotnet-counters ps
# 2. Живі метрики: CPU, GC, пул потоків, запити
dotnet-counters monitor -p 1234 --counters System.Runtime,Microsoft.AspNetCore.Hosting
# 3. Високий CPU: 30 секунд семплів стеків, файл для speedscope або PerfView
dotnet-trace collect -p 1234 --duration 00:00:00:30 --format Speedscope
# 4. Зростання пам'яті: граф об'єктів (викликає повне збирання сміття)
dotnet-gcdump collect -p 1234 -o before.gcdump
# 5. Зависання або голодування пулу: повний дамп і стеки всіх потоків
dotnet-dump collect -p 1234 -o /diag/app.dmp
dotnet-dump analyze /diag/app.dmp -c "threadpool" -c "parallelstacks" -c "exit"
dotnet-monitor: трейс у разі сплеску повільних запитів · Senior
{
"CollectionRules": {
"SlowRequests": {
"Trigger": {
"Type": "AspNetRequestDuration",
"Settings": { "RequestCount": 10, "RequestDuration": "00:00:03", "SlidingWindowDuration": "00:01:00" }
},
"Actions": [
{ "Type": "CollectTrace", "Settings": { "Profile": "Cpu", "Duration": "00:00:30", "Egress": "TmpDir" } }
],
"Limits": { "ActionCount": 1, "ActionCountSlidingWindowDuration": "01:00:00" }
}
}
}

Десять запитів довших за три секунди за хвилину — і dotnet-monitor сам пише 30-секундний трейс, не частіше ніж раз на годину.

Senior+

Що таке SLI, SLO та error budget і як налаштувати алерти, що не будять даремно?

Section titled “Що таке SLI, SLO та error budget і як налаштувати алерти, що не будять даремно?”
Відповідь

Що це

  • SLI (service level indicator) — що вимірюємо: частка успішних запитів, частка запитів, швидших за 300 мс.
  • SLO (service level objective) — ціль: 99,9% за 30 днів.
  • SLA — договір із клієнтом із наслідками, зазвичай м’якший за внутрішній SLO.
  • Error budget — 100% − SLO: скільки невдач можна собі дозволити.

Навіщо

SLO відповідає на питання «чи достатньо ми надійні?». 100% недосяжно й не потрібно: користувач не відрізнить 99,99% від 100%.

Як користуватися

  • Бюджет є — викочуємо фічі. Бюджет скінчився — займаємося надійністю.
  • Алерт спрацьовує, коли страждають користувачі, а не коли «щось виглядає дивно».

Типова помилка

Алерт на кожен сплеск CPU або на одну помилку 500. Черговий звикає ігнорувати сповіщення й пропускає справжню відмову.

Як влаштовано

  1. SLI = добрі події / усі події. Його рахують із метрик, наприклад з http.server.request.duration з кодом відповіді.
  2. SLO задають на вікно, зазвичай ковзні 28 або 30 днів.
  3. Бюджет у хвилинах: 99,9% за 30 днів — 43,2 хвилини повного простою, 99,99% — 4,3 хвилини.

Коли що обирати

Алерт Приклад Куди
Симптомний частка 5xx, p99 вище за ціль SLO будить чергового
Причинний CPU 90%, диск заповнений на 80% дашборд або тікет
Причинний, але неминучий диск заповниться за 4 години будить: відмова ось-ось станеться

Пастки

  • Мало трафіку. Поріг «понад 1% помилок за 5 хвилин» уночі спрацьовує від однієї помилки на 50 запитів.
  • Алерт без дії. Якщо на сповіщення нічого зробити, це рядок на дашборді, а не алерт.
  • Лише доступність. SLO за помилками не помітить, що все стало повільним; потрібен ще SLI за затримкою.

Правило

Будить лише те, що потребує дії людини просто зараз, і в кожного такого алерту є runbook.

Під капотом: burn rate

Burn rate = частка помилок / (1 − SLO). За SLO 99,9% і 1,44% помилок burn rate дорівнює 14,4: місячний бюджет згорить за 50 годин. Рекомендація SRE Workbook для 99,9%:

Витрата бюджету Вікна: довге / коротке Burn rate і реакція
2% 1 год / 5 хв 14,4 — будити
5% 6 год / 30 хв 6 — будити
10% 3 дні / 6 год 1 — тікет

Алерт спрацьовує, коли перевищено обидва вікна: довге дає точність, коротке — швидке скидання після відновлення.

Ціна

  • Нічне пробудження — ціна в людях. Google SRE розраховує максимум на два інциденти за 12-годинну зміну: більше — і розбір перетворюється на гасіння пожеж.
  • Надто жорсткий SLO гальмує викочування, надто м’який не захищає користувачів. Ціль беруть від того, що помічає користувач, а не від поточних графіків.

Компроміси

  • Error budget policy заздалегідь домовляється про наслідки: бюджет вичерпано — фічі чекають, команда лагодить надійність. Без політики SLO лишається цифрою на дашборді.
  • Runbook — кроки діагностики й безпечні дії для конкретного алерту. Runbook, що повторюється, — кандидат на автоматизацію.

Розбір інциденту

Blameless postmortem фіксує хронологію, вплив на користувачів, причини й дії з власниками та строками. Питання — «чому система дозволила цьому статися», а не «хто винен»: інакше люди починають приховувати помилки.

Підсумок: алерт за швидкістю витрати error budget будить лише тоді, коли користувачі справді втрачають обіцяну якість.

Приклад
groups:
- name: orders-api-slo
rules:
# SLI: частка відповідей 5xx за вікно
- record: slo:error_ratio:rate5m
expr: |
sum(rate(http_server_request_duration_seconds_count{job="orders-api",http_response_status_code=~"5.."}[5m]))
/
sum(rate(http_server_request_duration_seconds_count{job="orders-api"}[5m]))
- record: slo:error_ratio:rate1h
expr: |
sum(rate(http_server_request_duration_seconds_count{job="orders-api",http_response_status_code=~"5.."}[1h]))
/
sum(rate(http_server_request_duration_seconds_count{job="orders-api"}[1h]))
# SLO 99,9% → бюджет 0,001; burn rate 14,4 = 2% місячного бюджету за годину
- alert: OrdersApiErrorBudgetBurn
expr: |
slo:error_ratio:rate1h > (14.4 * 0.001)
and
slo:error_ratio:rate5m > (14.4 * 0.001)
labels:
severity: page
annotations:
summary: "orders-api is burning its error budget 14x faster than allowed"
runbook_url: "https://wiki.example.com/runbooks/orders-api-errors"

Правило Prometheus будить чергового, лише якщо помилок багато і за останню годину, і за останні 5 хвилин.