Runbook для инцидента: шаблон
Симптомы → first response → escalation → проверки → восстановление → пост-мортем. Живой документ, не отчёт.
Напиши runbook для {{service}} на тип инцидента: {{incident_type}}.
Базовая аксиома: runbook читается в 3 часа ночи человеком в панике после звонка PagerDuty. У него нет времени думать, нет контекста, не знает кода. Каждое предложение должно быть командой. Без воды, без объяснений «почему». Объяснения идут в постмортем, не в runbook.
Шаблон
# Runbook: [{{service}}] {{incident_type}}
**Severity:** SEV-1 / SEV-2 / SEV-3
**Owner:** team-name (slack channel)
**On-call:** PagerDuty schedule name
**Last updated:** YYYY-MM-DD by @author
**Last verified:** YYYY-MM-DD (game day / real incident)
---
## 1. Это оно? (симптомы)
Проверь ВСЕ три условия. Если совпадает — продолжай. Если нет — ищи другой runbook.
- [ ] **Алерт:** `HighLatencyAlert` / `5xxBurst` / `QueueLagCritical` — точное имя
- [ ] **Метрика:** `http_request_duration_seconds{service="X"} p99 > 2s` за последние 5 минут
- [ ] **Симптом у пользователя:** [конкретное наблюдение — медленный checkout / ошибки в логине]
**Похожие но НЕ это:**
- Если только увеличена latency, но 2xx-ответы — см. `high-latency.md`
- Если 5xx, но только из одного региона — см. `regional-outage.md`
- Если только background jobs тормозят — см. `worker-lag.md`
---
## 2. First response (первые 5 минут)
**Дашборд:** [ссылка на grafana / datadog с заранее настроенным view]
**Скажи в #incidents:**
INCIDENT: {{service}} {{incident_type}} Severity: SEV-? IC: @me Status page: yes/no
**Сделай СРАЗУ (не думая):**
1. Открой дашборд (выше)
2. Открой логи: `kubectl logs -n {{service}} --tail=200 | grep ERROR`
3. Открой traces: [ссылка на jaeger/honeycomb с фильтром на error]
4. Сделай screenshot текущего состояния (для постмортема)
**НЕ делай в первые 5 минут:**
- НЕ деплой
- НЕ перезагружай поды массово
- НЕ меняй конфиг
- НЕ паникуй в публичных каналах
---
## 3. Escalation (когда звать помощь)
| Условие | Кого | Канал |
|---|---|---|
| SEV-1 (revenue impact) | Incident Commander | PagerDuty escalation |
| Не понял что происходит за 15 мин | Senior on-call | @senior-oncall in #incidents |
| Нужен доступ которого нет | Tech lead | @tech-lead-{{service}} |
| Возможна потеря данных | Database team + CTO | DM + #db-emergency |
| Влияет на другой сервис | Owner того сервиса | их PagerDuty |
**Когда поднимать статус-страницу:** > 1% пользователей затронуто на > 5 минут.
---
## 4. Что в логах и метриках
### Проверка 1: ошибки в логах
```bash
kubectl logs -n prod -l app={{service}} --since=10m | grep -E "ERROR|FATAL|panic"
Что искать: новый класс ошибок (не было 30 минут назад) — это коренная причина.
Проверка 2: метрики golden signals
- Latency:
p50,p99за 1ч / 24ч — сравни - Traffic: RPS — упал или вырос?
- Errors:
rate(http_5xx[5m])— взрыв? - Saturation: CPU / memory / connection pool / queue length
Проверка 3: недавние изменения
- Деплои за 2 часа: [ссылка на CI/CD timeline]
- Feature flags toggled: [ссылка на flag changelog]
- Конфиг changes:
git log --since="2 hours ago" -- config/ - Инфра changes: Terraform applies, kubectl rollout
Эмпирика: 80% инцидентов — недавнее изменение. Если был deploy/flag/config за 2 часа — это первый подозреваемый, не «странный баг в проде».
Проверка 4: зависимости
- БД: alive, latency, connections, slow queries
- Redis / cache: alive, hit rate, eviction rate
- External APIs: их status page, latency
- Очередь: длина, age oldest message, consumer lag
5. Восстановление (mitigation)
Иерархия: сначала остановить кровь, потом разобраться. Восстановление != fix.
Опция A: rollback (≤ 5 минут)
kubectl rollout undo deployment/{{service}} -n prod
# или
helm rollback {{service}} <previous-revision>
Когда: проблема началась после deploy в последние 2 часа. Риск: низкий. Может потерять hotfix.
Опция B: feature flag off (≤ 2 минуты)
flagctl set {{service}}.new_feature off --reason "incident-NNNN"
Когда: проблема в фиче за флагом. Риск: функционал недоступен пользователям.
Опция C: scale up (≤ 5 минут)
kubectl scale deployment/{{service}} --replicas=20 -n prod
Когда: saturation (CPU/mem high, queue grows), не код-bug. Риск: маскирует реальную проблему. Не использовать как permanent fix.
Опция D: shed load (≤ 5 минут)
- Включить rate limit на тяжёлом endpoint
- Drop low-priority background jobs
- Включить cached fallback
Когда: dependency сдох (БД, downstream), нужно снизить нагрузку.
Опция E: failover (≤ 15 минут)
# Переключение на standby DB
./scripts/failover.sh --to=replica-2 --confirm
Когда: primary мёртв, replica здорова. Риск: возможна потеря последних писем (RPO). Кто разрешает: Database team + IC.
Опция F: communication only
Иногда лучшее действие — обновить status page и подождать. Если dependency-провайдер (Stripe, AWS) лежит — ничего не сделать, кроме как сообщить.
6. Verification (как понять что отлегло)
- Алерт прекратил срабатывать на 10 минут подряд
- Метрики вернулись в норму (latency p99 < target)
- Synthetic check проходит:
curl https://{{service}}/health - Пользовательский сценарий работает: [конкретные шаги]
- Логи: нет нового error class за 5 минут
- Нагрузка вернулась к baseline (не маска от scale up)
Закрытие инцидента:
- Скажи в #incidents:
RESOLVED at HH:MM. Owner: @me. Postmortem: <link> - Сбрось status page
- Открой postmortem задачу
- Если SEV-1/2: уведомь stakeholders (email/slack)
7. Постмортем (в течение 48 часов)
Шаблон в [link]. Минимум:
- TL;DR: одно предложение, что произошло
- Impact: длительность, % пользователей, $ revenue если оценимо
- Timeline: ключевые события с временем (UTC)
- Root cause: 5 whys, дойти до корня не «human error»
- Что сработало: хвалить
- Что не сработало: без обвинений
- Action items: owner + due date + JIRA ticket
- Detection time: alert → human awareness — сколько минут
- Mitigation time: awareness → impact stopped — сколько минут
Blameless: ищем процесс, не человека. «Почему было легко сделать ошибку» вместо «кто виноват».
8. История инцидентов с этим runbook
| Дата | Кто | Сколько minutes | Что пошло не так в runbook | Action |
|---|---|---|---|---|
| 2026-03-15 | @alice | 23m | Опция B не сработала, flag был уже off | Добавлен check |
| 2026-04-02 | @bob | 12m | OK |
9. Game day
- Последний раз протестирован: YYYY-MM-DD
- Следующий: YYYY-MM-DD (квартально)
- Известные пробелы: ...
## Принципы хорошего runbook
1. **Команды, не описания.** Не «проверьте логи» — конкретный `kubectl logs ...`.
2. **Копипаст-готово.** Команды должны работать с заменой одной переменной.
3. **Иерархия по времени:** что сделать за 1 минуту, за 5, за 15.
4. **Конкретные пороги:** не «много ошибок», а «`rate(5xx) > 10/s`».
5. **Branching:** если симптом такой → этот runbook, иначе → другой runbook.
6. **Owner viable:** на runbook обязан быть owner который его поддерживает.
7. **Verified:** game day минимум раз в квартал. Не протестированный runbook не работает.
8. **Постмортем-driven updates:** каждый инцидент → пункт в runbook что было неочевидно.
## Anti-patterns
- ❌ Runbook на 50 страниц с теорией системы — никто не прочитает в 3 часа ночи
- ❌ Команды с placeholder'ами без примера (`kubectl logs <pod>`) — какой pod, формат?
- ❌ Ссылки на дашборды без сохранённых фильтров — оператор настраивает grafana вместо тушения
- ❌ «Если что — позвони Алисе» — Алиса в отпуске, нет escalation matrix
- ❌ Не указан severity и нет критериев — нет понимания эскалировать или нет
- ❌ Recovery options без trade-offs — оператор не знает что выбрать
- ❌ Не проверяется (не было game day) — половина команд устарела
- ❌ Не обновляется по постмортемам — те же грабли
- ❌ Описывает «как работает система» вместо «что нажать» — теория не нужна в инциденте
- ❌ Игнорирует communication — статус-страница, stakeholder updates
- ❌ Шаблон постмортема не привязан — постмортем не пишется или формально
- ❌ Нет verification checklist — закрытие инцидента «на ощупь», recurrence через час
## На выходе
- Заполненный runbook по шаблону для {{incident_type}}
- Список зависимостей runbook'а (дашборды, скрипты, доступы) — что нужно подготовить заранее
- Game day сценарий (как протестировать без реального инцидента)
- Owner и расписание review (квартал)
- Связанные runbook'и для соседних симптомов
Playbook отката деплоя
От симптома до отката: как обнаружить, как откатить (git revert / pm2 prev / db), smoke-тесты, пост-мортем.
Структура README
Что и в каком порядке должно быть в README — чтобы новый человек запустил проект за 5 минут.
API-документация
OpenAPI/Swagger, описание endpoints, примеры запросов и ответов, ошибки.