Что такое инцидент
Инцидент — это незапланированное событие, которое приводит к нарушению или деградации сервиса. Не каждый баг — инцидент. Инцидент — это когда пользователи затронуты.
Классификация инцидентов
| Severity | Определение | Время реакции | Пример |
|---|---|---|---|
| SEV1 | Полная недоступность сервиса | < 5 минут | Сайт лежит, платежи не проходят |
| SEV2 | Значительная деградация | < 15 минут | 50% запросов с ошибками |
| SEV3 | Частичная деградация | < 1 час | Медленный ответ, один регион |
| SEV4 | Минимальное влияние | В рабочие часы | Косметический баг, один пользователь |
Жизненный цикл инцидента
Detection → Triage → Response → Mitigation → Resolution → Postmortem
│ │ │ │ │ │
Алерт Оценка Команда Остановить Починить Выводы
severity собрана кровотечение причину и улучшения
1. Detection (Обнаружение)
Как мы узнаём об инциденте:
| Источник | Описание | Скорость |
|---|---|---|
| Мониторинг/Алерты | Автоматическое обнаружение | Секунды-минуты |
| Синтетические проверки | Регулярные health checks | Минуты |
| Жалобы пользователей | Тикеты, звонки, соцсети | Минуты-часы |
| Внутренние команды | QA, разработчики заметили | Минуты-часы |
Цель: Обнаруживать инциденты быстрее пользователей. Если о проблеме первыми узнают пользователи — это провал мониторинга.
2. Triage (Сортировка)
Быстрая оценка ситуации:
- Кто затронут? Все пользователи? Один регион? Один клиент?
- Что сломалось? Какой сервис/компонент?
- Когда началось? Коррелирует с деплоем? Изменением конфига?
- Severity? SEV1-4 по критериям выше
3. Response (Реагирование)
Формирование команды реагирования:
| Роль | Ответственность |
|---|---|
| Incident Commander (IC) | Координация, решения, коммуникация |
| Technical Lead | Техническое расследование и fix |
| Communications Lead | Обновления статуса для stakeholders |
| Scribe | Документирование timeline |
| Subject Matter Experts | Привлекаются по необходимости |
4. Mitigation (Смягчение)
Первый приоритет — остановить влияние на пользователей, даже если это не устраняет root cause.
Типичные тактики:
| Тактика | Описание |
|---|---|
| Rollback | Откатить последний деплой |
| Feature flag off | Отключить проблемную фичу |
| Failover | Переключить на резервную систему |
| Scale up | Добавить ресурсы при перегрузке |
| Rate limiting | Ограничить трафик |
| Circuit breaker | Отключить зависимость |
5. Resolution (Устранение)
Полное устранение root cause. Может произойти после mitigation.
6. Postmortem (Разбор)
Документирование инцидента и выводы (см. раздел ниже).
On-Call
On-call — практика дежурства инженеров для реагирования на инциденты в нерабочее время.
Принципы здорового on-call
| Принцип | Реализация |
|---|---|
| Справедливая ротация | Равномерное распределение нагрузки |
| Компенсация | Оплата дежурств или отгулы |
| Runbooks | Документированные процедуры для каждого алерта |
| Escalation | Чёткий путь эскалации при необходимости |
| Follow-the-sun | Распределение по часовым поясам |
| Обратная связь | Регулярный анализ on-call нагрузки |
Политика эскалации
Уровень 1 (0-15 мин): On-call инженер команды
↓ нет ответа или SEV1
Уровень 2 (15-30 мин): Tech Lead / Senior инженер
↓ нет прогресса
Уровень 3 (30-60 мин): Engineering Manager + архитектор
↓ бизнес-влияние
Уровень 4 (60+ мин): VP Engineering / CTO
Система управления инцидентами
<?php
declare(strict_types=1);
namespace App\Incident;
enum Severity: string
{
case Sev1 = 'SEV1';
case Sev2 = 'SEV2';
case Sev3 = 'SEV3';
case Sev4 = 'SEV4';
public function maxResponseMinutes(): int
{
return match ($this) {
self::Sev1 => 5,
self::Sev2 => 15,
self::Sev3 => 60,
self::Sev4 => 480,
};
}
public function requiresPageOut(): bool
{
return match ($this) {
self::Sev1, self::Sev2 => true,
self::Sev3, self::Sev4 => false,
};
}
}
enum IncidentStatus: string
{
case Detected = 'detected';
case Triaged = 'triaged';
case Investigating = 'investigating';
case Mitigated = 'mitigated';
case Resolved = 'resolved';
case PostmortemPending = 'postmortem_pending';
case Closed = 'closed';
}
final class Incident
{
/** @var array<TimelineEntry> */
private array $timeline = [];
private IncidentStatus $status;
public function __construct(
public readonly string $id,
public readonly string $title,
public readonly Severity $severity,
public readonly \DateTimeImmutable $detectedAt,
public readonly string $detectedBy,
private ?string $incidentCommander = null,
) {
$this->status = IncidentStatus::Detected;
$this->addTimelineEntry('Incident detected', $detectedBy);
}
public function assignCommander(string $commander): void
{
$this->incidentCommander = $commander;
$this->addTimelineEntry("Incident Commander assigned: {$commander}", 'system');
}
public function updateStatus(IncidentStatus $status, string $updatedBy, string $note = ''): void
{
$previousStatus = $this->status;
$this->status = $status;
$message = "Status changed: {$previousStatus->value} → {$status->value}";
if ($note !== '') {
$message .= " — {$note}";
}
$this->addTimelineEntry($message, $updatedBy);
}
public function getStatus(): IncidentStatus
{
return $this->status;
}
/**
* Calculate time from detection to mitigation.
*/
public function getTimeToMitigate(): ?int
{
$mitigatedEntry = null;
foreach ($this->timeline as $entry) {
if (str_contains($entry->message, IncidentStatus::Mitigated->value)) {
$mitigatedEntry = $entry;
break;
}
}
if ($mitigatedEntry === null) {
return null;
}
return $mitigatedEntry->timestamp->getTimestamp() - $this->detectedAt->getTimestamp();
}
/**
* Check if response time exceeds SLA.
*/
public function isResponseOverdue(): bool
{
if ($this->status !== IncidentStatus::Detected) {
return false;
}
$elapsedMinutes = (time() - $this->detectedAt->getTimestamp()) / 60;
return $elapsedMinutes > $this->severity->maxResponseMinutes();
}
/** @return array<TimelineEntry> */
public function getTimeline(): array
{
return $this->timeline;
}
private function addTimelineEntry(string $message, string $author): void
{
$this->timeline[] = new TimelineEntry(
timestamp: new \DateTimeImmutable(),
message: $message,
author: $author,
);
}
}
final readonly class TimelineEntry
{
public function __construct(
public \DateTimeImmutable $timestamp,
public string $message,
public string $author,
) {}
}
Postmortem — это документ, который описывает инцидент, его причины, последствия и действия по предотвращению повторения. Постмортемы — это blameless процесс (без обвинений).
Структура постмортема
| Секция | Содержание |
|---|---|
| Summary | Краткое описание инцидента |
| Impact | Кто и как был затронут, метрики |
| Timeline | Хронология событий с точностью до минут |
| Root Cause | Что вызвало инцидент |
| Contributing Factors | Что усугубило ситуацию |
| Detection | Как обнаружили, почему не раньше |
| Mitigation | Что сделали для остановки влияния |
| Resolution | Как полностью исправили |
| Action Items | Конкретные задачи с ответственными и сроками |
| Lessons Learned | Что сработало, что нет, где повезло |
Blameless Culture
Принцип: Люди не делают ошибки. Системы позволяют ошибкам случаться. Постмортем анализирует систему, а не ищет виноватых.
| Blame | Blameless |
|---|---|
| «Вася уронил прод» | «Деплой-процесс позволил небезопасный rollout» |
| «Кто-то не проверил» | «Нет автоматической проверки перед деплоем» |
| «Человеческая ошибка» | «Недостаточная защита от ошибок (guardrails)» |
Шаблон Action Items
AI-1: [P1] Добавить circuit breaker для Payment API
Owner: @backend-team | Deadline: 2024-02-15
Status: In Progress
AI-2: [P2] Настроить алерт на latency p99 > 1s для Payment Service
Owner: @sre-team | Deadline: 2024-02-10
Status: Done
AI-3: [P3] Добавить runbook для Payment Service degradation
Owner: @on-call-team | Deadline: 2024-02-20
Status: Not Started
Метрики инцидентного управления
| Метрика | Формула | Цель |
|---|---|---|
| MTTD | Mean Time To Detect | < 5 минут |
| MTTA | Mean Time To Acknowledge | < 15 минут |
| MTTM | Mean Time To Mitigate | < 30 минут |
| MTTR | Mean Time To Resolve | < 4 часа |
| MTBF | Mean Time Between Failures | Увеличивать |
<?php
declare(strict_types=1);
namespace App\Incident;
final readonly class IncidentMetrics
{
/**
* @param array<Incident> $incidents Resolved incidents for the period
* @return array{mttr_hours: float, mttm_minutes: float, count: int, by_severity: array}
*/
public function calculate(array $incidents): array
{
if (empty($incidents)) {
return [
'mttr_hours' => 0,
'mttm_minutes' => 0,
'count' => 0,
'by_severity' => [],
];
}
$totalMitigationSeconds = 0;
$mitigatedCount = 0;
$bySeverity = [];
foreach ($incidents as $incident) {
$ttm = $incident->getTimeToMitigate();
if ($ttm !== null) {
$totalMitigationSeconds += $ttm;
$mitigatedCount++;
}
$sev = $incident->severity->value;
$bySeverity[$sev] ??= 0;
$bySeverity[$sev]++;
}
return [
'mttr_hours' => 0, // Requires resolution timestamp
'mttm_minutes' => $mitigatedCount > 0
? round($totalMitigationSeconds / $mitigatedCount / 60, 1)
: 0,
'count' => count($incidents),
'by_severity' => $bySeverity,
];
}
}
Status Page обновления
| Когда | Что сообщать |
|---|---|
| Обнаружение | «Мы расследуем проблему с [сервисом]» |
| Каждые 15-30 мин | Обновление статуса, что делаем |
| Mitigation | «Проблема устранена, мониторим» |
| Resolution | «Полностью решено, root cause: ...» |
Внутренняя коммуникация
- War Room — выделенный канал (Slack/Teams) для инцидента
- Регулярные апдейты — каждые 15 минут для SEV1
- Stakeholder updates — отдельный канал для менеджмента
- Customer communication — через support и status page
Chaos Engineering
Chaos Engineering — практика намеренного внесения сбоев для проверки устойчивости системы.
| Практика | Описание |
|---|---|
| Chaos Monkey | Случайное завершение инстансов |
| Latency injection | Искусственные задержки |
| Network partition | Разрыв связи между сервисами |
| Dependency failure | Отключение внешних зависимостей |
| Load spike | Резкое увеличение трафика |
Правило: Chaos engineering проводится в production с контролируемым blast radius. Если система не выдерживает хаос в тестовой среде — она точно не готова к production chaos.
Шаблоны Runbook
Runbook — это пошаговая инструкция для дежурного инженера. Хороший runbook позволяет решить проблему даже без глубокого знания системы.
Структура runbook
| Секция | Описание |
|---|---|
| Название | Краткое описание проблемы |
| Триггер | Какой алерт вызывает этот runbook |
| Severity | Critical / High / Medium / Low |
| Шаги диагностики | Что проверить в первую очередь |
| Шаги исправления | Конкретные команды |
| Верификация | Как убедиться что проблема решена |
| Эскалация | К кому обращаться если не помогло |
Runbook: Высокий CPU (>90%)
Триггер: HighCPUUtilization alert (CPU > 90% more than 5 min)
Диагностика:
- Определить какой процесс потребляет CPU:
top -c/htopdocker stats(если контейнеры)
- Проверить текущий RPS: Grafana → API Dashboard → Request Rate
- Проверить наличие long-running queries:
pg_stat_activity
Исправление:
- Если высокий RPS → проверить наличие DDoS/бот-атаки, включить rate limiting
- Если медленные SQL → найти и оптимизировать запрос, добавить index
- Если утечка в приложении → перезапустить контейнер как временная мера
- Если легитимный рост нагрузки → scale out (увеличить replicas)
Верификация: CPU < 70% в течение 10 минут.
Эскалация: Если не решено за 30 минут → вызвать senior backend engineer.
Runbook: Database Replication Lag (>10 секунд)
Триггер: HighReplicationLag alert
Диагностика:
- Проверить текущий lag:
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, (extract(epoch from now()) - extract(epoch from replay_lag))::int as lag_seconds FROM pg_stat_replication; - Проверить нагрузку на primary: CPU, IOPS, connections
- Проверить сеть между primary и replica: latency, bandwidth
- Проверить наличие long-running transactions на replica
Исправление:
- Если высокая нагрузка на primary → оптимизировать запросы, перенести analytics на replica
- Если сетевые проблемы → проверить VPC Peering / networking
- Если long transaction на replica →
pg_terminate_backend(pid) - Если lag растёт бесконечно → рассмотреть re-sync replica
Верификация: Replication lag < 1 секунда в течение 15 минут.
Эскалация: DBA team если lag > 30 секунд или replica не восстанавливается.
Runbook: Out of Memory (OOM Kill)
Триггер: ContainerOOMKilled alert или MemoryUtilization > 95%
Диагностика:
- Проверить какой контейнер убит:
docker events --filter event=oom - Проверить текущее потребление:
docker stats - Проверить memory trend в Grafana за последние 24 часа
- PHP: проверить
memory_get_peak_usage()в логах - Go: профилирование
pprof→go tool pprof http://localhost:6060/debug/pprof/heap
Исправление:
- Если постепенный рост (leak) → перезапустить контейнер, создать задачу на поиск утечки
- Если резкий скачок → проверить последний deploy, откатить если нужно
- Если легитимный рост данных → увеличить memory limit
- PHP: проверить
memory_limitв php.ini, Doctrine hydration (use iterators) - Go: проверить goroutine leak, незакрытые HTTP body, большие slice allocations
Верификация: Memory usage стабильно < 80% в течение 1 часа.
Эскалация: Backend team если OOM повторяется после restart или причина не найдена.
Итоги
| Концепция | Суть |
|---|---|
| Incident lifecycle | Detection → Triage → Response → Mitigation → Resolution → Postmortem |
| Severity levels | SEV1-4 определяют скорость реакции |
| On-call | Справедливая ротация + runbooks + эскалация |
| Postmortem | Blameless анализ с конкретными action items |
| MTTD/MTTR | Ключевые метрики для улучшения процесса |
| Chaos Engineering | Проверка устойчивости через контролируемые сбои |