Перейти к содержанию

Сценарии отказов

На карте есть пульт, и его кнопки ломают сервис по-настоящему. Смысл не в развлечении: наблюдаемость проверяется только отказом. Дашборд, на котором никогда не было аварии, — это картинка, а не инструмент.

Каждый сценарий выключается сам не позже чем через десять минут, ограничен двенадцатью запусками в минуту на весь стенд и затрагивает только приложение.

Утечка памяти

Что делает. Приложение начинает удерживать ссылки на блоки по 3 МБ и перестаёт их отпускать — до предела 256 МБ. Предел подобран так, чтобы балласт не уместился в кучу (325 МБ при обычном расходе 80–100 МБ), но и не вывел под за предел памяти контейнера: иначе память кончилась бы на всём узле, и вытеснять пришлось бы не только виновника.

Сценарий действует только на ту реплику, которая приняла запрос. Это намеренно: вторая продолжает обслуживать посетителей, и на дашборде видно, как расходятся показатели двух подов.

Что видно на дашборде.

  1. Куча растёт ступеньками и не опадает после сборки мусора. Это главный признак утечки: при пике нагрузки уровень после сборки возвращается к обычному, при утечке — нет.
  2. Доля времени в сборке мусора растёт: сборщик работает всё чаще, освобождая всё меньше.
  3. Опоздание такта карты увеличивается — паузы сборщика съедают то самое время, в которое нужно было рассылать кадр.
  4. На карте поезда начинают двигаться рывками. Именно так деградация выглядит для посетителя.
  5. Примерно через минуту виртуальная машина Java объявляет, что памяти нет. Флаг -XX:+ExitOnOutOfMemoryError заставляет её завершиться сразу, а не пытаться работать дальше в невменяемом состоянии: контейнер перезапускается, и под возвращается в строй чистым. Оставшаяся реплика всё это время отвечает — бюджет допускающего сбои развёртывания не даёт увести обе разом.

Чему учит. Разница между утечкой и пиком видна в одной метрике — объёме живых данных после сборки мусора. Без неё спор «у нас утечка или просто нагрузка» решался бы гаданием.

Медленные ответы

Что делает. Обработчик добавляет 350 мс задержки к каждому запросу к API, кроме самого потока событий и управления сценариями.

Что видно.

  1. Перцентили времени ответа уезжают вверх, 95-й пробивает границу 300 мс.
  2. Доля быстрых ответов падает — начинается расход бюджета ошибок по задержке.
  3. Через несколько минут число реплик растёт: запросы копятся, нагрузка на пода растёт, автомасштабирование добавляет третью.

Чему учит. Сервис, который отвечает кодом 200 за две секунды, формально доступен и фактически бесполезен. Поэтому цель по времени ответа — такая же цель, как доступность, и следить за ней нужно по доле быстрых ответов, а не по среднему времени: среднее прячет как раз тех посетителей, которым плохо.

Час пик

Что делает. Переводит модельное время на 08:30 — интервал выпуска сокращается, число составов на схеме растёт примерно вдвое, поток обращений жителей усиливается.

Что видно. Больше составов в каждом кадре, дольше расчёт такта, крупнее кадры потока событий. Это не авария, а честная проверка того, как сервис ведёт себя под предметной нагрузкой. Модельное время само вернётся к настоящему через пятнадцать минут.

Наплыв запросов

Что делает. Поднимает цель по нагрузке для генератора: тот забирает новое задание и увеличивает частоту запросов к API.

Что видно.

  1. Частота запросов растёт на графике.
  2. Через минуту-две HorizontalPodAutoscaler добавляет реплику — не по загрузке процессора, а по прикладной метрике «запросов в секунду на пода».
  3. Время ответа остаётся в пределах цели: масштабирование успевает.

Чему учит. Загрузка процессора — плохой признак для сервиса, который в основном ждёт. Масштабироваться нужно по той величине, которая описывает работу.

Вернуть в обычный режим

Выключает все сценарии, отпускает удержанную память, сбрасывает модельное время и цель по нагрузке. Куча после этого опадает не мгновенно — сборщику нужно несколько циклов, и на графике это тоже интересно посмотреть.

Как это устроено внутри

Сценарии — обычная часть приложения, а не внешний инструмент. Состояние живёт в одном компоненте на атомарных переменных, задержку добавляет обработчик запросов, рост кучи — планировщик, который раз в такт добавляет блок в список. Сами переключатели опубликованы как метрики, поэтому на любом графике видно, был ли в этот момент включён сценарий:

metroskop_chaos_active{scenario="memory_leak"} 1
metroskop_chaos_ballast_megabytes 384

Такой подход стоит трёх десятков строк и не требует ни оператора, ни отдельного инструмента внедрения отказов. Для стенда это верный размер решения; в промышленной среде отказы вносят снаружи, чтобы не тащить их код в приложение.