Как мы внедряли SLO в Купере: от ручных формул до SLO as Code
Всем привет, меня зовут Вячеслав, я Team Lead SRE в Купер.тех. Рассказ в этой статье пойдет о том, как мы внедряли SLO, чего достигли и какие лайфхаки нашли по дороге.

Купер — сервис доставки из магазинов и ресторанов. Сейчас у нас 12 production-кластеров Kubernetes с 350+ продуктовыми сервисами. Это больше 11 тыс. подов, которые обрабатывают свыше 10 тыс. RPS.
Преамбула. Осень 2022 выдалась достаточно сложной для сервиса: тот самый «высокий сезон», «черные пятницы», «зеленые дни». Инцидентов было довольно много и они только прирастали.

Разумеется, в компании существовали практика мониторинга и культура инцидент-менеджмента. Однако при такой сложности инфраструктуры оперативных дашбордов с метриками было явно недостаточно: они помогали дежурить и отвечать на текущие вызовы, но мало способствовали принятию стратегических решений.
С чего все началось
В перерывах между тушением пожаров мы, команда SRE (Site Reliability Engineering), считали метрики, строили новые дашборды, искали новые инструменты и подходы (именно так в нашей платформе появился Slok/Sloth).
Часть SLI (Service Level Indicator — индикатор уровня сервиса) мы могли описать сами. Например, базовую доступность сервиса — долю успешных запросов — считали по метрикам Istio (сервис-меш метрик сетевого взаимодействия). Но дальше без владельцев сервисов двигаться было невозможно: только они могли сказать, какая задержка допустима, по каким API-методам ее считать и какие ошибки действительно влияют на пользователя. Но проблема существовала — и нужно было ее решать.
От индикаторов к дашбордам
На первом этапе мы разобрали метрики каждого сервиса, написали SLI, оформили изменения через merge request и фактически протолкнули их в кодовую базу. Помогли два фактора: выделенные 20% времени на технический долг и запрос руководства на понятную картину стабильности. Это был карт-бланш, которым мы и воспользовались.
Когда первые индикаторы были написаны и задеплоены, встал вопрос: как на них смотреть.
У Sloth есть встроенный дашборд, который показывает индикаторы последовательно — каждый отдельно, с целью, текущим бюджетом ошибок (Error budget). Удобно для старта, но при большом количестве формул экран превращается в бесконечный скролл, мешающий видеть общую картину.

К тому же сам по себе бюджет ошибок оказался не самым удобным операционным показателем. В теории он хорошо демонстрирует, сколько стабильности сервис уже «потратил». На практике месячный бюджет мог обнулиться уже на третий день, и это еще не самый плохой вариант.
Для ежедневной работы нам был важнее другой вопрос: динамика стала положительной или отрицательной?
Поэтому мы начали писать собственные дашборды вручную. В их основу легли два элемента:
средний SLI за период — мы брали неделю;
time series — временной график со всеми индикаторами сервиса.
Чуть позже к ним добавился показатель «неделя к неделе»: простой визуальный сигнал, который сразу показывал направление движения. Такая стратегия позволяла получить быстрый ответ о состоянии системы без погружения в детали: сервис деградирует, восстанавливается или остается на том же уровне.

Параллельно мы начали применять тот же подход к бизнес-процессам. Для этого разбирали Golden Path, например, полный сценарий от открытия приложения до получения заказа: поиск → корзина → оформление → сборка → логистика → доставка.
И вот здесь нам пришлось сложнее, так как путь пользователя через цепочку сервисов редко зависит от одного сервиса, чаще являясь цепочкой синхронных и асинхронных взаимодействий с участием разных команд, API, очередей и внутренних зависимостей. Чтобы описать такой SLI, нужно садиться вместе с владельцами сервисов и разбирать сценарий по шагам, стараясь понять, какие системы в нем участвуют, какие метрики уже есть, а каких не хватает.
От репозитория к ответственности
Поначалу все индикаторы бизнес-процессов описывались в репозитории одного из наших монолитов. Это работало, пока конфигурация была небольшой. Однако со временем файл вырос до десятков тысяч строк, и поддерживать его стало тяжело: в нем было сложно ориентироваться, ревьюить изменения и понимать, за какой бизнес-процесс отвечает конкретный фрагмент.
Поэтому мы вынесли SLO-конфигурацию в отдельный репозиторий. Деплойный процесс платформы пришлось научить собирать один большой конфигурационный файл из множества небольших фрагментов. Каждый такой фрагмент описывал отдельный бизнес-юнит или функцию, внутри которых находились формулы SLI.

Однако перенос конфигурации решил только техническую часть задачи. После этого возник более важный вопрос: кто и как должен описывать сами индикаторы. Стало понятно, что без глубокого понимания бизнес-процессов корректный SLI построить невозможно.
Чтобы написать эти формулы корректно, SRE-команда разбирала процессы вместе с владельцами сервисов, юнит-лидами и тимлидами. Например, брали доставку или сборку заказа и раскладывали по шагам: какие сервисы участвуют, как они взаимодействуют, где идут синхронные HTTP- или gRPC-вызовы, где используются асинхронные очереди, какие метрики уже есть и каких не хватает.
Все это нужно было отразить в SLI. Иначе индикатор показывал бы не реальное состояние процесса, а лишь упрощенную картину, которой нельзя доверять.
Постепенно стало понятно, что точность индикаторов — только половина задачи. Даже идеально рассчитанный SLI бесполезен, если его никто не воспринимает как сигнал к действию.
Поэтому следующим шагом стали алерты на снижение SLI. Если индикатор вдруг проседал, уведомления отправлялись в специально созданный чат с упоминанием команды-владельца и команды SRE.

С помощью административного ресурса мы закрепили обязательную реакцию: тимлиды участвовали в разборе, выясняли причины просадки и следили за тем, чтобы по результатам появлялись конкретные действия.
Но одних уведомлений оказалось недостаточно. Они помогали оперативно реагировать на проблемы, однако не создавали механизма системных улучшений, поэтому логичным продолжением оптимизации стали еженедельные встречи по стабильности.
На них мы разбирали просадки SLI за неделю, обсуждали инциденты в своей зоне ответственности и фиксировали задачи с конкретными исполнителями и сроками выполнения. Отдельную пользу этот процесс принес при работе с бизнес-сценариями: встречи помогали регулярно проверять, насколько индикаторы по-прежнему соответствуют реальности.
Например: случился инцидент в логистике, а дашборд бизнес-процесса показывает 100%. Значит, проблема не только в сервисе. Значит, сам индикатор устарел или описывает процесс неполно.
Особенно часто подобное происходило во время декомпозиции монолитов. Команда могла переключить трафик на новый сервис, но забыть обновить метрику, по которой рассчитывался SLI. В результате дашборд продолжал отображать старую реальность, а индикатор терял ценность как инструмент контроля.
Постепенно ответственность за стабильность начала закрепляться на всех уровнях: от отдельных команд до тимлидов и юнит-лидов. Инструментами стали пользоваться не только SRE-инженеры, но и люди, которые отвечали за сервисы и бизнес-процессы.
По мере расширения практики команды начали активнее приносить пожелания к дашбордам, алертам и самим формулам. Появилась постоянная предметная обратная связь о том, какие индикаторы действительно помогают в работе, а какие требуют доработки.
Именно этот фидбек в итоге подтолкнул нас к следующему этапу развития системы — автоматической генерации дашбордов и переходу к декларативному описанию индикаторов.
Технические лайфхаки: как мы считали SLI
Первые индикаторы мы строили на основе appErrorRate — одного из Golden Signals. Здесь сильно помог сервис-меш Istio. Он уже собирал метрики взаимодействия между сервисами, поэтому для многих сценариев достаточно было одного показателя с фильтрацией по лейблам источника и получателя запроса. При необходимости можно было смотреть как на клиентскую, так и на серверную сторону вызова.

Такой подход хорошо работал на старте, но с ростом платформы начали проявляться ограничения. При более чем 350 сервисах кардинальность метрик быстро становилась проблемой. Кроме того, в Istio для нас до сих пор недоступна детализация по отдельным эндпоинтам и методам через стандартные лейблы. Как только требовалось считать SLI для конкретного API-метода или части бизнес-процесса, приходилось обращаться уже к метрикам самих приложений.
Именно здесь возникла следующая сложность. В Купере два основных языка для бэкенда — Golang и Ruby, — и исторически команды использовали разные библиотеки и соглашения по именованию метрик. Даже одинаковые по смыслу показатели могли иметь разные названия и наборы лейблов.
Пока таких индикаторов было немного, это не создавало серьезных проблем. Но по мере роста числа SLI стало очевидно, что поддерживать десятки различных шаблонов невозможно. Чтобы масштабировать процесс, платформенная команда занялась унификацией метрик и привела основные соглашения к единому стандарту.
В результате формулы SLI перестали зависеть от конкретного языка или реализации сервиса. Это заметно упростило создание новых индикаторов и позволило описывать их по единым правилам для всей платформы.
Асинхронные взаимодействия: где SLI начинали врать
С синхронными HTTP- и gRPC-вызовами все было относительно понятно: есть запросы, ответы, коды ошибок, latency. С асинхронными взаимодействиями оказалось сложнее. Там легко получить формально корректную метрику, которая плохо отражает реальное состояние сервиса.
Inbox/outbox: как мы получили отрицательный SLI
Один из таких примеров — паттерн transactional inbox/outbox. Он помогает надежно доставлять события через промежуточные таблицы в базе данных: сервис сначала фиксирует событие в своей БД, а затем отдельный процесс публикует его в очередь или читает из нее.
В сервисах Купера на Golang и Ruby этот паттерн был реализован по-разному. В Go ошибки собирались в блоке defer — отложенном вызове, который выполняется перед выходом из функции. На практике это приводило к неожиданному эффекту: при лаге в топике метрика ошибок начинала расти позже, когда метрика общего количества обработанных сообщений уже возвращалась к нулю.

Для самого сервиса это не было проблемой, но для расчета SLI создавало неприятный побочный эффект. Индикатор считается как отношение ошибок к общему числу событий, и такая рассинхронизация ломала формулу. В нашем случае SLI в отдельные моменты уходил в отрицательные значения. Формально система считала именно то, что мы попросили. Практически — показывала абсурд.
Так как SLI считается как отношение ошибок к общему числу событий, такая рассинхронизация ломает формулу. В нашем случае индикатор в кейсе уходил в отрицательные значения. Формально система считала то, что мы попросили. Практически — показывала абсурд.
Kafka: почему бинарный SLI оказался слишком грубым
Похожая история возникла с Kafka. Стандартный Kafka-экспортер работает на уровне брокеров и отдает общие метрики для топиков. Для некоторых сценариев этого достаточно, но для SLO такой показатель быстро превращался в бинарный флаг.
Любая просадка сразу давала 0% доступности. На практике это плохо отражало реальное состояние системы: задержка могла быть заметной для пользователей, но до полной деградации сервиса было еще далеко.
Поэтому мы сделали progressive SLI — индикатор, который снижает доступность постепенно, а не переключает ее из 100% в 0% при первом нарушении порога.

Логика была простой: доступность начинала снижаться только после того, как количество сообщений с задержкой превышало заданное значение. Чем больше становилось таких сообщений, тем сильнее росла деградация. Визуально это выглядело как плавное изменение значения от 0 до 1, где 0 соответствовал нормальной работе, а 1 — полной деградации. После устранения проблемы индикатор быстро возвращался к исходному состоянию.
Для реализации такого расчета мы использовали функцию last_over_time, которая берет последнее значение метрики на заданном временном окне. В нашем случае она помогала преобразовать range-vector в instant-vector при работе с Gauge-метриками. Это было особенно важно из-за стека VictoriaMetrics/MetricsQL: на тот момент Sloth валидировал запросы как PromQL, тогда как мы использовали MetricsQL, и без подобных обходных решений валидация просто не проходила.
В итоге такой подход оказался заметно честнее бинарного переключателя: он показывал не просто факт нарушения, а степень деградации сервиса.
Почему мы осторожно используем histogram quantile
Похожее решение можно применить и к квантилям по гистограммам — например, к p99 latency, показывающей значение, в которое укладываются 99% запросов: если p99 равен 500 мс, значит, 99% запросов обрабатывались не дольше 500 мс.
Но с histogram_quantile() нужно быть осторожным. Prometheus считает квантиль по bucket’ам гистограммы, а не по каждому отдельному запросу. Если нужная граница не совпадает с bucket’ом, результат оценивается через интерполяцию. Это делает значение приближенным и чувствительным к тому, как выбраны bucket’ы.
Поэтому мы используем такой подход редко — только когда других вариантов нет и погрешность допустима.
Как исключали нерелевантные периоды
Иногда владельцы сервиса готовы отвечать за его работу только в определенный период. Например, сервис используется днем, а ночью трафика почти нет. В таком случае ночные метрики могут искажать SLI: одна случайная ошибка выглядит слишком значимой, хотя пользовательского влияния почти нет.
Для таких сценариев мы исключали нерелевантный временной диапазон с помощью логического объединения с функцией hour через AND. Если нужно исключить не ночь, а день, логика остается той же: меняется только условие.
Это не универсальное решение, но для сервисов с предсказуемым режимом работы оно помогает не смешивать полезный сигнал с шумом.
Низкая сатурация: когда одна ошибка ломает весь индикатор
Отдельная проблема возникала на сервисах с низким объемом трафика. Если запросов мало, одна ошибка может резко обрушить SLI, хотя реальной деградации сервиса нет. Проблема не в качестве работы сервиса, а в том, что выборка оказывается слишком маленькой.
Самое очевидное решение — исключать периоды низкого трафика целиком. Но этот подход быстро показал свои ограничения. Заранее неизвестно, когда именно нагрузка снизится, а жестко заданное расписание со временем начинает расходиться с реальным поведением пользователей.
Некоторые команды пытались решать проблему через ограничение метрики «снизу». Такой подход работал лучше, но тоже оставался не слишком надежным: выбор пороговых значений фактически превращался в подбор коэффициентов методом проб и ошибок.
В итоге мы пришли к более гибкому варианту на основе функции clamp, которая ограничивает значение заданным диапазоном — в нашем случае от 0 до 1.
Логика была простой: общее количество запросов делилось на порог, выше которого трафик считался достаточным. Если запросов мало, множитель получался меньше единицы и смягчал влияние единичных ошибок. Когда трафик превышал пороговое значение, множитель становился равен 1, и SLI работал в обычном режиме.

В результате индикатор переставал краснеть из-за одной ошибки на почти пустом трафике, но при этом продолжал честно реагировать на реальные проблемы под нормальной нагрузкой.
Slother: автогенерация дашбордов
К этому моменту формулы уже были достаточно выверены, и команды начали использовать их самостоятельно: брали примеры из соседних сервисов, копировали и адаптировали под свои задачи. Вместе с этим накапливалась и обратная связь: как индикаторы выглядят на дашбордах, что неудобно в визуализации и какие панели хочется видеть рядом с оперативными метриками.
И вот здесь, по мере роста числа SLI нас ожидала новая проблема. Все больше времени SRE-команда тратила не на развитие подхода, а на сопровождение дашбордов. Индикаторы менялись, команды добавляли новые формулы, а дашборды по-прежнему создавались и обновлялись вручную.
Это стало очередным ограничением масштабирования. Сначала мы попытались решить проблему организационно и передали поддержку дашбордов самим командам-владельцам сервисов: изменил индикатор — обнови визуализацию. Но предсказуемо такой подход не прижился.
Все чаще возникали вопросы:
Почему индикатор показывает не те цифры, что раньше?
Мы добавили новый SLI, а на дашборде его нет.
Почему формулу обновили, а график остался прежним?
Это нормальная судьба ручного процесса: он неизбежно расходится с реальностью. Люди забывают, пропускают, откладывают, им нельзя верить! Не потому, что плохо работают, а потому что стабильность в большой системе нельзя строить исключительно на дисциплине.
Ответом стал slother — самописная утилита на Go, которая генерирует дашборды Grafana автоматически. В основе подхода лежат Jsonnet — язык для описания и генерации JSON-конфигураций, и Grafonnet — библиотека для Jsonnet, которая позволяет описывать Grafana-дашборды как код.
Как работает slother

Утилита живет в отдельном репозитории, где хранятся код, шаблоны дашбордов и описание используемых панелей и метрик. Все это собирается в единый пакет и публикуется как артефакт. При деплое продуктового сервиса генерация дашборда запускается автоматически и проходит несколько этапов.
В качестве источника истины используется SLO.yaml сервиса. Сначала утилита читает актуальное описание индикаторов, а затем дополняет его метаданными из инфраструктурной базы платформы: именем сервиса, namespace, tier, манифестом, бизнес-юнитом и другой информацией, необходимой для построения дашборда.
После этого данные собираются в общий объект:
имя SLO-группы;
набор labels: например, availability, latency, kafka_lag;
целевое значение SLO;
имя сервиса и namespace;
tier и бизнес-юнит;
список индикаторов, относящихся к сервису.
Дальше этот объект передается в Jsonnet VM. Она применяет шаблоны Grafonnet и на их основе генерирует JSON-модель дашборда, которая затем отправляется в Grafana.
На выходе получается дашборд с одинаковой структурой для всех сервисов. В нем используются сворачиваемые row-панели: например, по бизнес-юниту или tier. Внутри находятся средний SLI за выбранный период, time series со всеми индикаторами сервиса и дополнительные технические метрики, такие как количество error-логов.
Главная идея остается простой: дашборд становится производной от SLO.yaml. Любые изменения в описании индикаторов автоматически попадают в визуализацию, поэтому конфигурация и отображение больше не расходятся со временем.
Library panels: когда автогенерации дашборда оказалось мало
После появления автоматически генерируемых дашбордов команды захотели видеть SLO-панели и на своих оперативных экранах. И здесь быстро вернулась уже знакомая проблема: панель не обновилась, отображает устаревшие данные или вообще потерялась после очередного изменения.
Для таких сценариев Grafana предлагает library panels — переиспользуемые панели, которые создаются один раз и затем встраиваются в разные дашборды. Обновление происходит централизованно: достаточно изменить панель в одном месте, и изменения становятся доступны везде.
На первый взгляд это выглядело как идеальное решение. Но при попытке встроить library panels в процесс автоматической генерации обнаружились технические ограничения.
В отличие от обычной панели, library panel содержит в JSON дополнительные структуры. Помимо самой модели панели присутствует объект meta с информацией о владельце, датах создания и обновления, а также родительском дашборде. Отдельно хранится объект model, в котором дублируются datasource, targets, настройки визуализации и другие параметры панели.
Проблема заключалась в том, что Grafonnet без доработок не позволял собрать нужную JSON-модель в том виде, в котором ее ожидала Grafana. Но и это еще не все. Стандартный endpoint для загрузки дашбордов — /api/dashboards/db — отказывался принимать JSON с library panels. При этом тот же самый JSON успешно импортировался через веб-интерфейс.
Разбор показал, что кнопка импорта в Grafana использует другой endpoint — /api/dashboards/import. Неожиданно оказалось, что он отсутствует в официальной документации, поэтому требования к формату пришлось восстанавливать экспериментально.
Когда и этот барьер удалось обойти, появилась следующая проблема. На родном дашборде panel работала корректно, но после добавления на другой дашборд оказывалась пустой. Причина нашлась в поле model: при импорте через API его содержимое не сохранялось в базе данных.
В итоге lifecycle library panels пришлось обрабатывать отдельно. Алгоритм получился таким:
получить текущее состояние library panel по UID;
забрать из базы имя, версию, дату обновления и модель;
обновить модель актуальными данными;
отправить новую версию обратно через PATCH.
Это добавило к генератору несколько десятков строк кода, но результат оказался оправданным.
Сейчас более 20 library panels используются как минимум на трех дашбордах каждая, не считая родного. Некоторые панели переиспользуются на пяти и более дашбордах. При этом они остаются синхронизированными автоматически: меняется описание SLO — обновляется и панель.
Так удалось убрать еще один участок системы, где актуальность данных раньше зависела от ручных действий и человеческой памяти.
Декларативные индикаторы и IaC-эскалации
Параллельно с автоматизацией дашбордов обострилась новая беда: команды активно копировали готовые формулы из соседних сервисов, но далеко не всегда делали это аккуратно. Кто-то добавлял произвольные множители, чтобы подогнать результат под ожидания, кто-то забывал менять условия фильтрации метрик по labels — например, namespace конкретного сервиса. В итоге SLI формально существовал, но мог считать совсем не то, что от него ожидали.
Сначала мы попытались решить это через документацию. Внутри Купера появился SLO Book — документ, в котором мы объясняли, что такое SLI, как устроена базовая математика, какие конструкции уже проверены на практике и какие шаблоны стоит использовать.
Однако довольно быстро выяснилось, что документация плохо масштабируется. При каждом новом вопросе мы отправляли команды читать SLO Book, но желающих внимательно разбираться во внутренних гайдах оказалось немного. Даже хорошие примеры не защищали от ошибок, если формулы по-прежнему приходилось собирать вручную.
Тогда стало понятно, что проблему нужно решать так же, как и раньше: не объяснять правильный способ действий, а убирать саму возможность ошибиться.
Поэтому логичным следующим шагом стало декларативное описание индикаторов.
Идея была простой: команда больше не пишет формулу самостоятельно, а описывает намерение. Например, для Istio proxy достаточно указать сервис, коды ошибок и допустимый порог latency. Дальше система сама генерирует необходимые индикаторы — availability для доступности сервиса и latency для времени ответа.
Так, мы убрали еще один источник ручной работы и снизили риск ошибок в формулах. Сотрудникам больше не нужно копировать чужой PromQL или MetricsQL и надеяться, что все labels заменены правильно. Она описывает, что именно хочет измерять, а корректные формулы собираются автоматически.
Grafana OnCall и эскалации как код
Следующим шагом стало развитие системы алертинга.
С внедрением Grafana OnCall мы перешли от уведомлений на всю команду к адресным сообщениям. Теперь алерт о снижении SLI приходил конкретному дежурному инженеру, а не растворялся в общем чате. Заодно получилось избавиться от постоянного участия SRE в каждом срабатывании. Если в процессе разбора действительно требовалась помощь, инженер мог отдельно привлечь нашу команду. Это заметно снизило объем шума для самих SRE.

Сама цепочка эскалации теперь выглядела так: сначала уведомление получал дежурный инженер команды-владельца. Если он не ставил acknowledgement — подтверждение того, что алерт принят в работу, — через заданное время уведомление поднималось выше: сначала к тимлиду, затем к юнит-лиду и далее вплоть до Engineering Manager.

Когда таких цепочек стало много, возник уже знакомый вопрос масштабирования: как поддерживать их в актуальном состоянии и не настраивать вручную каждое изменение.
Поэтому согласованную схему эскалаций мы описали как Infrastructure as Code. Для этого использовали Terraform: правила, маршруты и интеграции стали частью конфигурации и начали изменяться через код, а не через интерфейс Grafana OnCall.
Вокруг этого появилась небольшая утилита на Go — Oncaller. Она получает список команд, для которых необходимо настроить эскалации, обращается в корпоративный портал Space за актуальными контактами сотрудников — телефонами и никнеймами в мессенджерах, — а затем запускает terraform apply через Go tfexec. После этого Terraform через API обновляет интеграции, маршруты и цепочки эскалаций в Grafana OnCall, а результат применения публикуется в Mattermost.

Отдельно мы постарались минимизировать шум самих уведомлений. Шаблоны сообщений для Mattermost и Telegram содержали только необходимые labels и контекст. Алерт должен давать достаточно информации для начала разбора, но не превращаться в длинную техническую сводку.
Маршрутизация уведомлений строилась через routing regex, который генерировался шаблонами Jinja2. Возможности такого подхода ограничены, но для наших сценариев их оказалось достаточно: SLI-алерт попадал в нужную интеграцию и дальше автоматически проходил по заранее описанной цепочке эскалаций.
Не обошлось и без технических ограничений. API Grafana OnCall не позволяло обновлять контактную информацию пользователя от имени другого аккаунта — это мог сделать только сам пользователь. В нескольких случаях нам пришлось временно обращаться напрямую к базе данных, для чего даже появился соответствующий код в утилите.
Такой подход мы сознательно не стали делать частью штатного процесса. После устранения первопричины необходимость в подобных обходных решениях исчезла, а конфигурация эскалаций полностью вернулась в управляемый и воспроизводимый контур.
Над чем работаем сейчас
Декларативные индикаторы заметно упростили работу со SLO, но довольно быстро стало понятно, что абстракцию можно поднять еще на уровень выше.
Сейчас мы развиваем два основных направления.
Первое — дальнейшее развитие декларативных индикаторов. Мы продолжаем упрощать описание типовых SLI, чтобы командам не приходилось писать, копировать и поддерживать формулы вручную.
Второе направление связано уже не с отдельными индикаторами, а с описанием бизнес-процессов целиком. Идея заключается в том, чтобы команда задавала не набор метрик, а сам процесс: из каких шагов он состоит, какие сервисы участвуют в его выполнении, где проходят синхронные вызовы, где используются очереди и какие зависимости являются критичными.
В таком подходе система должна сама строить необходимые SLI, добавлять их на дашборды и настраивать маршрутизацию алертов. По сути, мы хотим перейти от описания технических метрик к описанию бизнес-логики, на основе которой вся остальная конфигурация будет формироваться автоматически.
Параллельно развиваются и более прикладные механизмы. Например, для групп SLI с сопоставимым влиянием мы используем агрегацию через max: итоговое значение определяется самым сильным проседанием среди входящих индикаторов. Подход достаточно простой и плоский, но для ряда сценариев его оказывается вполне достаточно.
Еще один полезный механизм — переиспользование SLO-групп. Если сервису нужен уже описанный SLO другого сервиса, он может импортировать группу по namespace и имени. После этого она автоматически появляется и в алертах, и на дашбордах.
Все это позволяет сократить объем повторяющейся конфигурации, а также экономить recording rules — заранее рассчитанные правила в базе метрик. В целом это продолжение той же идеи, вокруг которой постепенно строилась вся система: чем меньше ручной настройки требуется от команды, тем проще поддерживать SLO в актуальном состоянии.
Результаты: J-кривая изменений и свободные руки
Первые результаты после внедрения SLO выглядели контринтуитивно: количество инцидентов выросло.

На первый взгляд это выглядело как ухудшение ситуации. На практике мы просто начали замечать проблемы раньше, чем раньше. Где-то уже происходила деградация, где-то только появлялись первые признаки будущего инцидента, но теперь эти сигналы попадали в индикаторы, алерты и регулярные разборы. Такой эффект часто описывают через J-кривую изменений: в начале трансформации видимость проблем растет быстрее, чем система успевает их устранять.
Масштабировать подход во многом помогла внутренняя PaaS-платформа Купера. В ней уже хранились данные о сервисах и связанных с ними индикаторах, а инфраструктурная информация была доступна через API. Благодаря этому Slother, переиспользование SLI и другие механизмы автоматизации работали поверх единой модели сервиса, а не собирали данные из разрозненных источников.

Не менее важной оказалась стандартизация именования. Namespace, имя сервиса, имя SLO и имя индикатора подчинялись единым правилам. Без такого фундамента любая автоматизация быстро превратилась бы в набор специальных случаев и исключений.
Отдельно стоит сказать про error budget и remaining error budget. Несмотря на популярность этих показателей, в повседневной работе для нас до сих пор полезнее сравнение «неделя к неделе». Абсолютный error budget сильно зависит от того, как именно сформулирован SLO, и иногда провоцирует погоню за целевым числом. Относительная динамика информативнее: она позволяет быстро понять, становится сервис стабильнее, деградирует или остается на прежнем уровне.
Но самым заметным результатом оказались не новые метрики и не новые дашборды. После автоматизации SRE-команду перестали регулярно отвлекать вопросы вроде «где индикатор?» или «почему дашборд не обновился?». Освободившееся время удалось направить на задачи, которые напрямую влияют на надежность платформы: нагрузочное тестирование, chaos engineering, скоринг сервисов и инфраструктурные улучшения.
Именно это я считаю главным итогом всей работы. Мы не просто внедрили SLO и не просто построили красивые дашборды. Мы последовательно убирали ручные процессы, из-за которых система расходилась с реальностью, и в итоге превратили SLO из набора метрик в рабочий механизм управления стабильностью.
Что мы вынесли из внедрения SLO
По итогам этого пути у нас сложилось несколько практических выводов.
Заручитесь поддержкой — в идеале C-level. Карт-бланш помогает быстрее договариваться с командами, выделять время и продавливать первые изменения. Это кажется очевидным, но не обязательно сразу браться за все — торт можно есть по частям. Начните с одного бизнес-юнита, покажите результат, а потом масштабируйте подход на остальную компанию.
Занимайтесь просвещением и агитацией. Инструмент должен быть понятным. Важно нести знания людям и объяснять, что именно нужно делать и почему. Мало кто любит читать документацию, но без нее инструмент останется только в руках его создателей, а волна однотипных вопросов быстро накроет команду разработки. Часто люди просто не понимают, как правильно считать SLI и почему одна формула лучше другой. Поэтому нужны SLO Book, живые разборы, примеры, декларативные индикаторы и нормальная обратная связь. Чем понятнее правила, тем выше доверие к данным.
Слушайте и развивайте. Инструмент должен быть удобным и красивым. Невозможно понравиться всем сразу, но хороший внутренний инструмент обязан снижать сложность, а не перекладывать ее на команды. Поэтому обратная связь от пользователей дашбордов и алертов — не возможное дополнение, а обязательная часть процесса.
Бейте по площадям. SLO не самодостаточно и само по себе мало что меняет. Без алертов, эскалаций, регулярных встреч, владельцев и закрепленной ответственности SLO остается лишь еще одной метрикой на дашборде. Работает только весь контур: измерили → увидели просадку → нашли владельца → разобрали причину → завели задачу → проверили динамику и двигаемся дальше к трем или пяти девяткам.
Никогда не сдавайтесь. Первые результаты могут обескураживать, и на этом этапе важно не остановиться. Рост числа инцидентов не всегда означает, что система стала хуже. Часто это значит, что вы начали видеть скрытые проблемы раньше: не когда уже горит, а когда только начинает тлеть. Важно объяснять это командам и руководству, иначе первые результаты будут выглядеть демотивирующими.
И не забывайте: SLO — не инструмент наказания. Сам по себе он не должен становиться способом давления на команды. Цель всегда одна — повышение стабильности и надежности. Если SLO попадает в цель команды, важно сразу зафиксировать: это не механизм поиска виноватых. Когда люди начинают бояться плохих цифр, они неизбежно переходят в режим защиты — подгоняют формулы, спорят с метриками или пытаются скрывать деградацию. Когда цель понятна всем участникам процесса, SLO становится инструментом улучшения сервиса: помогает видеть динамику, находить системные причины проблем и принимать инженерные решения.
В итоге для нас SLO стало не отдельной практикой наблюдаемости, а способом управлять стабильностью. Не через ощущение «у нас вроде все нормально», а через измеримые показатели, понятную ответственность и автоматизацию там, где ручной процесс неизбежно расходится с реальностью.