Всем привет! Меня зовут Найля, я инженер по обеспечению качества в Т-Банке. В поисках полезного материала часто слушаю доклады с личными кейсами и разборами инструментов. Это помогает увидеть проблемы глазами других и почерпнуть что-то полезное для себя и своего проекта. На весенней конференции Heisenbug выступал мой коллега Александр Бодня — тоже инженер по качеству. Его доклад показался мне интересным, и я захотела им поделиться. 

Сегодня в ИТ-сообществе все чаще говорят про T-shaped-специалистов — людей, обладающих глубокими знаниями в своей области и широким кругозором в смежных. Обычно этот подход обсуждается в контексте QA и Dev. А сейчас предлагаю посмотреть в другую сторону и обсудить интеграцию с SRE. 

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

Именно о такой интеграции рассказал Александр, делая акцент на том, как QA-подход может и должен применяться в задачах эксплуатации сервисов.

Надежность — одна из граней качества продукта

В большинстве компаний, где важна высокая доступность сервисов, в той или иной форме присутствует SRE. Это может быть отдельная команда, часть обязанностей разработчиков или даже внешний аутсорсинг. Инженеры по обеспечению качества к процессам надежности привлекаются меньше, чем хотелось бы, несмотря на то что надежность — один из аспектов качества. 

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

В своем докладе Александр делится личным опытом работы в роли QA- и SRE-инженера, рассказывая о трех сбоях, которые помогли ему понять, насколько важна интеграция QA и SRE в повседневной практике. Пройдемся по каждому из них и разберем, как это может пригодиться и в нашей практике.

Сбой 1: BCrypt и высокое потребление CPU

Есть Java-приложение, которое встроено в цепочку других сервисов, все исправно работает. Но приходит сотрудник безопасности и говорит, что нужно добавить аутентификацию в сервис. Команда решает внедрить Basic Auth через HTTPs для внутреннего использования, чтобы было удобнее отслеживать, кто и куда заходит.

Задача оперативно выполняется, и после внедрения Basic Auth в сервис QA-инженер идет по стандартному пути проверять функциональность авторизации:

  • на все эндпоинты навешена авторизация;

  • возвращаются корректные коды и ошибки;

  • валидация полей проверена;

  • инъекции не проходят;

  • и так далее.

QA заканчивает тестирование и говорит, что задача готова к релизу. Но после выкатки на продакшен наблюдается резкий рост потребления CPU.

График потребления CPU после релиза аутентификации в сервис
График потребления CPU после релиза аутентификации в сервис

В чем причина сбоя? С помощью инструмента профилирования JFR команда разработки выяснила, что проблема высокого потребления CPU связана именно с авторизацией. Один проход с дебаггером — и корневая проблема найдена: spring-security-web использует для хэширования пароля алгоритм BCrypt с количеством циклов хэширования 10 по умолчанию. Такой алгоритм оказался слишком ресурсоемким для нагрузки на продакшен. 

И так как речь шла о межсервисном взаимодействии, а не о пользовательских паролях, разработчик решает снизить количество циклов хэширования и выпускает внеплановый хотфикс. После хотфикса проблема решена, график потребления CPU выравнивается. 

График потребления CPU после фикса проблемы
График потребления CPU после фикса проблемы

Недостаточно только решить проблему. После каждого сбоя или критического бага важно задаться вопросом: почему пропустили дефект и как его можно было поймать до релиза? Ведь если не пытаться понять причины и бороться только с последствиями, то сервис будет сбоить постоянно.

Решением в сбое стало внедрение практики анализа метрик приложения уже на этапе тестирования. До этого момента QA-инженеры в команде Александра такими метриками не пользовались и даже не знали, где их найти: это считалось зоной ответственности SRE. Разбор сбоя показал: если бы QA во время проверки обратил внимание на показатели подов, можно было бы избежать выпуска проблемного релиза и доработать функциональность в штатном режиме, без спешки.

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

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

QA получает возможность выявлять скрытые дефекты, которые неявны при функциональном тестировании или даже их невозможно отловить вручную:

  • утечки памяти и рост потребления ресурсов;

  • дисбаланс нагрузки, например аномальное количество запросов в базу;

  • фоновые ошибки, которые могут быть незаметны пользователю, но критичны для стабильности;

  • неоптимизированные запросы или узкие места в архитектуре.

Метрики позволяют работать не только с техническими, но и с бизнес-показателями. Например, если в приложении пивоварни на тестовом стенде «наливается» аномально мало кружек в час, это уже сигнал к разбору. Такой подход помогает заранее проверять, как продукт ведет себя с точки зрения пользовательской ценности.

Анализ метрик на этапе тестирования тоже имеет свои сложности и подводные камни:

  1. Поддержка тестовой инфраструктуры. Метрики нужно собирать и хранить даже на промежуточных стендах, чтобы они были полезны. А это повышает затраты на сопровождение. Показатели могут отличаться от боевых, поэтому нужна дополнительная интерпретация или подгонка под реальные данные.

  2. Рост компетенций. QA необходимо учиться работе с метриками: понимать, какие показатели релевантны, как читать графики, что является аномалией, а что — ожидаемым поведением.

  3. Время на анализ. Интеграция метрик в тестирование удлиняет цикл проверки, особенно если процесс еще не автоматизирован.

Большинство минусов решаемы: грамотная автоматизация, базовые тренинги для QA и выверенная система алертов значительно снижают порог вхождения. Важно работать в связке с коллегами из команды, где SRE-инженер расскажет про инфраструктурные метрики, разработчик поможет найти уникальные метрики приложения, PM (Product Manager) и аналитики помогут подобрать метрики, привязанные к бизнесу, а тимлид поможет внедрить регулярное использование метрик в процессы команды.

Для внедрения метрик в команде Александра использовался такой стек:

  • Prometheus — сбор и хранение данных;

  • Grafana — визуализация метрик и построение дашбордов;

  • Micrometer — библиотека для публикации метрик прямо из Java-приложений.

Такой технологический стек — одно из стандартных решений, к которому легко подключить алертинг и интеграции с CI/CD. Кроме того, рынок предлагает готовые пакеты под разные стеки, так что выбор не ограничивается этими инструментами. Важно лишь обеспечить доступность метрик на тестовых окружениях и обучить QA-инженеров работать с ними.

Сбой 2: Конфликт JVM и автоскейлера

Разберем другой интересный случай, связанный с масштабированием в Kubernetes. Существует два типа масштабирования: горизонтальное и вертикальное. 

Горизонтальное скалирование относительно прозрачно — если нагрузка превышает определенный порог, мы просто добавляем поды. Нагрузка снижается, и поды убираются.

С вертикальным все сложнее. Автоскейлер отслеживает статистику потребления CPU и памяти и на ее основе динамически перераспределяет ресурсы: увеличивает или уменьшает их для деплоймента. Казалось бы, механизм удобный. Но в связке с JVM (Java Virtual Machine) дает неожиданный эффект.

Работа автоскейлера в связке с JVM
Работа автоскейлера в связке с JVM

По умолчанию JVM использует некий необходимый минимум памяти, который она забирает из хостовой системы. Если нужно, JVM забирает больше памяти, а когда необходимость отпадает — отпускает. В результате получается конфликт двух не связанных друг с другом систем управления оперативной памятью. Автоскейлер занижает доступную приложению память, и в моменты пиковой нагрузки JVM может попытаться закомитить больше памяти, чем ей доступно. Может случиться нехватка памяти — OOM (Out of memory) — и падение приложения.

Команда зарезервировала в JVM необходимое количество памяти, чтобы избежать падения. Для резерва нужно указать одинаковые значения для параметров Xmx (максимальное количество аллоцированной оперативной памяти) и Xms (стартовое количество аллоцированной оперативной памяти). При такой конфигурации каждый раз, когда автоскейлер получит показатели, он получит и данные о том, что приложение утилизирует память и нет необходимости снижать квоту. 

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

На продакшене ждала неожиданность. Поды поднимались, но процессор начинал тротлить, приложение уходило в отказ и обрушивалось 500-ми ошибками. На тесте все было чисто, а в реальности релиз провалился.

График потребления CPU, где видно, что новые поды сервиса стали потреблять больше CPU
График потребления CPU, где видно, что новые поды сервиса стали потреблять больше CPU

Причина провала оказалась проста. Spring-приложение в процессе деплоя активно потребляет CPU для инициализации бинов. На тесте его (процесс деплоя) проверяли без нагрузки, и система успевала стабилизироваться. В продакшене запросы пошли сразу, когда Spring еще не отпустил ресурсы процессора. В результате — полный отказ.

График с метриками во время старта приложения
График с метриками во время старта приложения

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

Обычно в зоне внимания QA находятся функциональные сценарии и пользовательский опыт. Но эксплуатация приложения — целый жизненный цикл после релиза: деплоймент, масштабирование, обновления, управление ресурсами. Игнорировать эти процессы опасно: сбои в эксплуатации способны вызвать куда более болезненные простои, чем баги в бизнес-логике. Опыт команды показал, что такие сценарии необходимо тестировать заранее. 

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

Плюсы нагрузочного тестирования во время деплоймента:

  1. Более реалистичная проверка. Система тестируется не только в статике, но и в динамике, под реальными изменениями окружения.

  2. Предотвращение аварийных сценариев. Сбои на этапе деплоя можно поймать до релиза.

  3. Рост прозрачности. QA начинают понимать эксплуатационный контур приложения, лучше взаимодействуют с SRE и DevOps.

  4. Фокус на надежность. Система проверяется не только с точки зрения пользователя, но и с точки зрения устойчивости в бою.

В то же время есть трудности, из-за которых многие команды не могут позволить себе постоянное проведение нагрузочного тестирования:

  1. Высокая стоимость тестов. Нужны мощные тестовые стенды, максимально приближенные к продакшену. Это требует ресурсов и поддержки.

  2. Сложность организации. Не всегда удается воспроизвести боевую нагрузку или условия развертывания.

  3. Рост требований к компетенциям QA. QA-инженерам нужно учиться работать с инструментами нагрузочного тестирования.

  4. Длительность проверок. Эксплуатационные сценарии сложно встроить в быстрые циклы CI/CD, они увеличивают время подготовки релиза.

Для проведения нагрузочного тестирования нужно, чтобы SRE-инженер помогал определить требования, а разработчик — реализовать тестовое окружение. Задача QA — разработать необходимые тест-кейсы и проверять на предоставленном окружении с помощью инструментов нагрузочного тестирования. Проведение эксплуатационных проверок требует ресурсов и компетенций, но результат оправдывает усилия: меньше аварий на проде, больше уверенности в надежности системы.

В команде Александра для таких тестов используется технологический стек из Gatling в качестве инструмента НТ и скрипты, которые написаны на Scala. Данные о результатах хранятся в Clickhouse.

У нас в компании уделяют большое внимание нагрузочному тестированию, на многих сервисах его внедрение обязательно. Как мы нагружаем в Т-Банке, что мы для этого используем, какие бывают ошибки в процессе и какая от этого польза, вы можете прочитать в статьях от наших коллег: «Космопути отдела нагрузочного тестирования — Cosmos» и «Ошибки в нагрузочном тестировании».

Сбой 3: Переполнение очереди Kafka

Переходим к последнему примеру. Дан исправно работающий сервис: он получает сообщения из Kafka и передает их дальше по цепочке. Все шло как по маслу, пока не начало таймаутить одно из соседних приложений, которое вызывалось в рамках работы сервиса. 

Ошибки быстро заметили — метрики показали рост фона, команду оповестили, дежурный инженер следил за восстановлением. Вроде бы все под контролем, но через несколько минут к команде приходит инженер из подразделения KaaS (Kafka as a Service) и сообщает, что партиция сервиса раздулась до критических размеров. Еще немного — и начнется потеря сообщений. А для команды Александра это критично: потеря или дублирование сообщений в клиринговой системе недопустимы, так как это может привести к расхождениям в реестрах сверки.

График, на котором виден риск переполнения очереди сообщениями
График, на котором виден риск переполнения очереди сообщениями

Проблема появилась из-за того, что сервис обрабатывал сообщения синхронно, в один поток, дожидаясь 15-секундного таймаута на каждый запрос к упавшей системе. В то же время в Kafka прилетало порядка 40 сообщений в секунду. Простая арифметика показывала: очередь скоро переполнится. К счастью, зависший сервис починили довольно быстро и удалось избежать фатального сценария.

Команда внедрила паттерн Circuit Breaker, чтобы избежать повторения. Паттерн позволяет не ждать истечения таймаута, а разрывать соединение и отбрасывать запросы при массовых отказах, переключаясь между состояниями:

  • закрытый режим: обычное выполнение запросов;

  • открытый режим: быстрый отказ запросов;

  • полуоткрытый режим: постепенное восстановление, проверка состояния.

Риск переполнения очередей снизился, и ускорилась реакция системы на сбои.

 Процесс работы паттерна Circuit Breaker
 Процесс работы паттерна Circuit Breaker

Произошедшее стало для команды толчком к внедрению хаос-тестирования — практики, которая проверяет, как система ведет себя в условиях сбоев.

Chaos Engineering

Хаос-тестирование (Chaos Engineering) — методология, разработанная для тестирования и улучшения надежности системы путем внедрения контролируемого хаоса и сбоев в инфраструктуру и приложения. Его цель — создание уверенности в устойчивости и способности системы работать в условиях неблагоприятных ситуаций. В крупных распределенных системах, где десятки сервисов связаны между собой, отказ одного компонента может вызвать цепочку проблем. Если не проверять такие сценарии заранее, последствия могут оказаться дорогими.

Хаос-тестирование строится вокруг гипотез: что будет, если сервис А станет медленнее отвечать или перестанет отвечать вовсе? Что произойдет при полной или частичной недоступности базы? 

Эксперимент состоит из четырех этапов: выбор цели, формулировка гипотезы, запуск теста и анализ результата. Важно именно мышление сбоями: не от требований, а от возможных точек отказа. Такой подход помогает находить дыры, которые в обычных QA-процессах легко упускаются.

Как проводить хаос-тестирование:

  1. Выбирать цель и определять влияние. Важно изучить прошлые сбои, постмортемы, точки отказа и насколько все это критично. После определения цели необходимо выделить список интеграций для проверки и предположить, что может с ней случиться.

  2. Формировать гипотезы. Нужно для каждой интеграции задать вопрос «Что будет, если...?" и предположить ответ. К формированию гипотез обязательно привлекать разработчиков и SRE, так как у них больше экспертизы и понимания, что произойдет при различных условиях. Примеры вопросов:

    Что будет, если зависимость N окажется недоступна при старте сервиса? 

    Что будет, если зависимость N станет недоступна во время работы сервиса?

    Что будет, если время ответа зависимости увеличится на 100ms во время старта сервиса?

    Что будет, если зависимость станет отвечать на 300ms дольше во время работы сервиса?

    Что будет, если зависимость корректно обработает запрос, но не уложится в таймаут?

  3. Проводить эксперименты. Запускаем тесты с помощью инструментов, позволяющих управлять трафиком, и проходимся по каждой составленной гипотезе. Тестирование можно проводить на тестовом стенде, который приближен к продовым условиям, или прямо на продакшене, если есть такая возможность. Если же таких условий нет и не хватает ресурсов, можно экстраполировать результаты, установив завышенные ожидания надежности сервисов. Это не поможет проверить корнер-кейсы, но покроет немалую часть возможных проблем.

  4. Анализировать результаты. Проверяем полученные результаты с предположительными ответами, которые сделали в начале. Если где-то они разнятся с ожидаемым поведением, заводим на это задачи и передаем команде на фикс.

На примере предыдущего сбоя гипотеза формулируется как «Что будет, если сторонний сервис А перестанет отвечать?». Ожидаемое поведение (после фикса, разумеется), что начнет заполняться база, из которой потом будут досылаться сообщения. Пройдясь по всем точкам стыковки приложения с внешними интеграциями, команда Александра сформировала пару десятков гипотез и автоматизировала их.

В ходе хаос-тестов команда не раз сталкивалась с неожиданностями. К примеру, приложение успешно поднималось и проходило readiness-пробу даже при полном отключении базы и очередей. То есть оно считалось «живым», хотя работать не могло. На первый взгляд такие вещи легко упустить: пока все работает — кажется, что все в порядке. Но именно хаос-тестирование помогает заранее увидеть уязвимости, которые в продакшене привели бы к критическим авариям. Такие находки помогли улучшить архитектуру и скорректировать тесты.

Почему отказоустойчивость стоит тестировать заранее:

  1. Снижение рисков простоев. Даже короткий сбой интеграции может приводить к потерям данных и финансовым убыткам.

  2. Реалистичная проверка. Мы оцениваем систему не только при нормальной работе, но и в условиях реальных отказов.

  3. Повышение доверия. Команда знает, как именно приложение поведет себя при сбое, и не надеется на авось.

  4. Формирование культуры надежности. QA начинают мыслить категориями SRE и архитектуры, что сильно укрепляет кросс-функциональное взаимодействие.

У подхода есть и минусы: хаос-тестирование сложно встроить в CI/CD, оно требует значительных усилий по автоматизации и компетенций в области эксплуатации. Но выгоды перевешивают.

Для проведения экспериментов команда Александра использовала:

  • toxiproxy — open-source TCP-прокси с возможностью управлять трафиком;

  • STORM — тулинг для запуска тестов (внутренняя разработка Т-Банка). Аналоги — Chaos Monkey, Pumba (Docker), Gremlins (JS)

Примерная схема работы выглядит так: в namespace приложения поднимается контейнер с toxiproxy, Chaos-tester получает данные об открытых портах, меняет конфиг-мапу приложения так, чтобы его вызовы шли через прокси. Прокси перенаправляет трафик на реальные эндпоинты, а Chaos-tester предоставляет интерфейс для управления задержками, отказами или потерей соединения. Весь процесс заслуживает отдельного доклада, но главное — команда получила возможность системно тестировать сбои.

Заключение

Из рассмотренных кейсов можно выделить несколько ключевых мыслей, которые будут полезны любой команде, работающей над качеством и надежностью сервисов.

Метрики — это приборная панель QA. QA-инженеры не должны ограничиваться пользовательскими сценариями и UI. Метрики приложения — инструмент раннего обнаружения проблем. Утечки памяти, рост ошибок, неравномерная нагрузка на базу — все можно увидеть заранее, если QA будут работать с метриками наравне с SRE и разработчиками. Если метрик нет — их нужно внедрять. Если они есть, но ими пользуются только эксплуатационные инженеры, стоит вовлечь в этот процесс и QA.

Эксплуатация тоже подлежит тестированию. Функциональные и бизнесовые требования важны, но они не покрывают все сценарии. То, как сервис ведет себя при деплойменте, скейлинге или обновлении, не менее критично. Игнорирование сценариев эксплуатации приводит к простоям и авариям, даже если все работает в функциональных тестах. Эксплуатационное тестирование должно стать частью процесса обеспечения качества.

Надежность всей системы зависит от связей между сервисами. В распределенных системах мы никогда не работаем в вакууме: наше приложение зависит от десятков внешних сервисов, баз, брокеров сообщений. Их сбои неизбежны, и это необходимо учитывать при проверках. Хаос-тестирование и проверки отказоустойчивости помогают выявить слабые места заранее, до того как они выльются в потерю данных или падение критичных бизнес-процессов.

Качество продукта — не только про функциональность. Современный QA должен смотреть шире: работать с метриками, думать категориями эксплуатации, учитывать окружение и поведение соседних систем. Да, это повышает требования к компетенциям QA и усложняет процесс тестирования. Но именно такой подход формирует культуру надежности, которая становится неотъемлемой частью зрелой инженерной практики. Если раньше QA-инженер ассоциировался прежде всего с проверкой пользовательских сценариев, то сегодня его роль все ближе к инженеру по надежности. Это не значит, что QA должен заменить SRE. Но это значит, что он должен говорить с ними на одном языке, использовать общие инструменты и разделять ответственность за качество и стабильность продукта.