Здравствуй, уважаемый читатель и ценитель историй о нагрузочном тестировании. Сегодня тебя ждет рассказ о том, как Антон Бородин, инженер по нагрузочному тестированию в компании Test IT, построил свой зоопарк решений и подходов с помощью Apache Airflow. Как он к этому пришел и каких зверей в нем поселил. Антон не хотел об этом рассказывать, но его заставил тимлид (то есть я, Head of QA - Карим Аминов).
Возрождение нагрузочного тестирования с JMeter и PyTest
Когда наш герой пришел работать в компанию, перед ним встала большая задача воскресить направление нагрузочного тестирования, которое к тому моменту начало покрываться мхом. Скрипты для JMeter потеряли актуальность, автоматизация сломалась, требовался глубокий рефакторинг. Началась кропотливая работа по восстановлению процесса.
Первым делом начался отбор приоритетных сценариев и создание соответствующих скриптов нагрузочного тестирования на JMeter. Эти скрипты параметризировались для возможности применения в разных тестах. С помощью параметров можно было управлять как стандартными вещами, окружением и тестовыми данными, так и дополнительными — длительностью тестов, действиями, которые проводятся, задержками действий пользователей.
Далее наш инженер воспользовался накопленными знаниями из области автоматизации и обернул нагрузочные тесты фреймворком PyTest — то есть превратил нагрузочный тест в автотест. Старт нагрузочного теста осуществлялся вызовом подпроцесса, завершение которого ожидал фреймворк. Такой подход позволил:
запускать нагрузочные тесты сериями;
легко параметризировать тесты;
объединять и группировать наборы разных нагрузочных тестов с помощью меток, например, выбрать регрессионный набор;
использовать фикстуры для подготовки к тестам и проведения анализа результатов;
автоматически верифицировать результаты.
Первый вариант отчетности в HTML
Одним из плюсов использования языка Python для управлениям нагрузочными тестами является богатая библиотека инструментов для анализа данных и легкость их применения. Каждый результат подвергался статистическому анализу, из него добывались ценные метрики, необходимые для подведения итогов. В качестве стека технологий использовались стандартные библиотеки: pandas, NumPy, Matplotlib. Полученные с их помощью таблицы и графики надо было где-то размещать, нужен был отчет.
Первым вариантом стал простой HTML. Практически сразу появились дополнительные модули в виде фикстур, обеспечивающие сбор данных с объекта тестирования. Они выполняли PromQL запросы к Prometheus, собирали данные и строили графики, которые попадали в HTML отчет. Полученный фреймворк упаковывался в виде контейнера для удобства запуска и обслуживания.
При старте данного контейнера проводился запуск серии нагрузочных тестов — нагрузочного прогона, по итогам которого появлялся набор HTML отчетов.
Инженер анализировал полученные отчеты, делал выводы и рассказывал о них заинтересованным лицам. Заинтересованные лица слушали и волновались. А если предмет беседы их озадачивал, то просили заводить дефект с наивысшим приоритетом!
Эволюция отчетности с Docusaurus
Следующим шагом в приключениях нашего инженера стало включение интеграции с Test IT. Благодаря использованию фреймворка PyTest удалось применить стандартные адаптеры нашей платформы. В результате нагрузочные прогоны стали появляться в системе управления тестированием, а отчеты — в виде вложений.
Отчеты с историей тестов начали накапливаться. Они были информативны, но не особенно красивы, а душе инженера хотелось эстетики. После недолгих метаний и наблюдения за тем, что из документации пробуют коллеги, выбор пал на Docusaurus.
Стало значительно лучше. Отчеты отдельных результатов начали отображаться на отдельных страницах, которые можно переключать. Теперь на выходе формировался набор Markdown, а точнее MDX-файлов, на основе которых генерировался итоговый отчет. Пусть он и стал выглядеть лучше, старый механизм генерации HTML не отключался из соображений, что «что-то может сломаться».
Создание хранилища на базе Apache CouchDB и S3 Minio
Одновременно начали расти потребности заинтересованных лиц. Хотелось наблюдать разницу в скорости работы между разными релизами, а также тренды по потреблению ресурсов. А отчетов по релизам было мало.
Инженер понял, что нужна аналитика между прогонами, разделенными во времени. Однако с текущим подходом получалось слишком много ручной работы, а наш герой слишком ленив, чтобы делать это из раза в раз. Появилась идея автоматически собирать данные, хранить их в удобном для анализа виде и быстро доставать по мере необходимости. Нужна база данных с результатами!
Сначала было непонятно, какая структура данных должна быть, по каким полям чаще придется искать информацию и что может понадобится. Инженеру хотелось гибкости в этом вопросе, поэтому он выбрал NoSQL СУБД Apache CouchDB. Он уже работал с этой базой на предыдущем проекте, плюс она достаточно популярна. Также рассматривали вариант с MongoDB, но их продукт не предназначен для жителей нашей северной страны.
В итоге появилась подсистема, состоящая из базы данных CouchDB с метаданными и агрегированными метриками результатов, S3 Minio для хранения графиков и самих файлов с отчетами, а также микросервис на базе FastAPI для выполнения операций с этими хранилищами. Данный микросервис позволял:
сохранять результаты в базе данных и S3;
запрашивать данные по названию теста, идентификатору или прогону;
запрашивать генерацию отчета из хранилища.
Для работы с этим сервисом нагрузочные тесты обросли соответствующим интеграционным кодом.
Storybook JS и Jupyter Book на пути к идеальному отчету
Параллельно с введением хранилища инженер занимался доработкой отчетности. Ненасытная душа теперь желала интерактивности: сортировки по значениям в таблицах, поиска запросов, удобства переходов между разделами. Красиво все реализовать в Docusaurus не удалось, да и компания выбрала для документации другой инструмент.
Началась новая итерация с подбором инструментария. Кратковременной остановкой на этом пути был Storybook JS. Это весьма нетрадиционный вариант для реализации отчетности, однако в нем можно было комбинировать Markdown и js-вставки.
С помощью данного инструмента удалось реализовать задуманное. Тем не менее, итерация поиска продолжилась. Причин было несколько — прежде всего, в таком инструменте приходилось изрядно возиться с JavaScript/TypeScript и использовать кодогенерацию. А для нашего инженера история с frontend напоминает уличную магию. На первый взгляд все происходит как обычно — компиляция, сборка, но при этом остается ощущение, что случилось нечто необъяснимое.
Второй причиной является использование инструмента нестандартным образом, ведь Storybook JS — это система управления библиотекой UI-компонентов. Итогом поиска стал Jupyter Book, он удачно подошел для отчетов по нагрузочному тестированию.
JB предназначен для создания документов с динамическим содержимым, статей и результатов научных исследований. Он умеет обрабатывать как Markdown файлы, так и Jupyter Notebook файлы с исполняемым содержимым. Наш инженер хорошо знаком с Jupyter Notebook, поэтому составить с его помощью отчет не составило большого труда. Кроме того, для использования нужен лишь язык Python. Поэтому вплоть до настоящего времени отчеты для Test IT и TeamStorm (оба продукта разрабатывает холдинг Yoonion) генерируются в Jupyter Book.
Рефакторинг механизмов взаимодействия компонентов
С появлением нового хранилища стало возможно удобно и быстро рассчитывать разницу между релизами, просматривать исторические данные по мере необходимости, делать прогнозы. По мере использования становилось понятно, что чаще используется, что реже, чего не хватает. Данные также начали накапливаться.
В то же время нагрузочные тесты обрастали функциональностью: появлялись новые источники метрик, новые графики, дополнительный анализ результатов, поддержка облачного окружения. Все это ложилось на плечи фикстур Pytest.
Заинтересованные лица также не отставали: релизные циклы становились короче, регресс проводили все чаще. Без нагрузочного тестирования выходить стало страшно. В результате нагрузочные тесты стали похожи на крепко связанный монолит. Проблемы были соответствующие: маленькая поломка в интеграции портила тест, а вместе с ним и весь прогон. Приходилось его перезапускать. Расширение возможностей и добавление новых функций становилось все более трудоемким. Стало понятно, что нужно двигаться дальше.
Анализируя процесс нагрузочного тестирования и реализованные модули, удалось выделить три больших компонента:
Модуль генерации нагрузки
Модуль анализа результатов
Модуль хранения и генерации отчетов
Эти компоненты должны быть независимыми и легко расширяемыми, сбой одного из них не должен затрагивать остальные.
Первым делом работа началась над модулем анализа. Он оказался хрупким, плюс требовал расширения функциональности. Инженер, используя свой опыт и глобальную сеть, набрел на мысль использования фреймворка Celery. На одном из прошлых проектов на нем реализовывались background задачи. С помощью данного фреймворка удалось разбить модуль анализа на набор независимых задач, часть из которых Celery мог выполнять параллельно, а часть последовательно.
После построения прототипа началось обдумывание, как связать эти задачи с модулем генерации нагрузки. Хотелось сделать его со «сменным картриджем» — то есть, чтобы можно было запускать разные инструменты генерации нагрузки без необходимости переписывания кода.
Хорошим видом «картриджа» является контейнер — стандартный способ поставки современного приложения. Только как запускать контейнер с нагрузочным инструментом без необходимости много возиться с API? Нужен инструмент для запуска контейнеров c которым можно интегрировать логику анализа результатов. В какой-то момент инженер закопался в эти мысли. Спастись из ямы рефлексии помог коллега девопс, который подсказал, что проблему можно решить с помощью Apache Airflow.
Данная система позволяет описывать процессы в виде ориентированного ациклического графа задач DAG. Пользователь описывает связи задач между собой, а Airflow строит на его основе граф, который в конечном итоге исполняется. Он автоматически определяет зависимости, способен распараллеливать выполнение этих задач, следить за их состоянием, а также запускать их по расписанию.
Создание платформы для НТ на базе Apache Airflow
Прогрызая свой путь сквозь документацию, инженер все чаще ловил себя на горестной мысли, что новый виток рефакторинга затронет все модули — не только модуль генерации и анализа, но и хранилище. Накопленный опыт использования подсказывал, что хранилище можно упростить.
Первая мысль, что через REST к результатам будут обращаться разные отделы, не оправдалась. С данными работает лишь инженер, остальным интересны лишь общие результаты — плохо/хорошо и насколько. То есть микросервис особенно и не нужен.
С течением времени стало понятно, какие поля в базе данных важнее других, на что лучше составить индексы, какая структура нужна. Советы со стороны коллег также возымели действие. Для хранения результатов не нужна NoSQL база данных, хватило бы и PostgreSQL.
Итогом мозговой деятельности инженера стал новый план. Предстояло построить DAG для нагрузочного процесса, а также для генерации отчетов в Jupyter Book с помощью Apache Airflow. Результаты при этом будут складываться в СУБД PostgreSQL.
Реализация процесса шла достаточно быстро. Переезд задач из Celery в Airflow произошел практически мгновенно, что не удивительно, так как первый используется в Airflow. Остальные задачи процесса были также быстро выстроены. Описывать их достаточно
просто, главное первоначально определиться, какие метаданные будут передаваться между задачами.
Тут стоит сказать спасибо большой библиотеке готовых операторов, хуков и компонентов, созданной сообществом Apache Airflow. Она упростила многие интеграционные задачи. Итогом такой работы стала целая система для нагрузочного тестирования на базе Airflow, назовем ее платформой для НТ.
Граф-процесс нагрузочного тестирования работает следующим образом:
На вход DAG передается информация о нагрузочном тесте, уровне нагрузки, идентификатор окружения и данные о нагрузочном прогоне.
Полученный запрос обогащается фактической информацией, необходимой для запуска нагрузочного теста: URL объекта тестирования, расположение его Prometheus, расположение машины генерации нагрузки, какой образ использовать, его базовые параметры и т. д. Эти данные хранятся в виде переменных Airflow.
На нужном окружении осуществляется запуск контейнера с нагрузочным тестами.
После окончания нагрузочного теста запускается контейнер, который считывает метрики и сохраняет их в базе данных TimeScale.
После этого запускаются параллельные задачи, считывающие и обрабатывающие метрики из объекта тестирования. Свои результаты они сохраняют в S3 Minio.
По их окончанию формируются метаданные результата, которые сохраняются в итогую таблице базы данных TimeScale.
Как можно видеть, наш инженер вновь выписывает кренделя — вместо чистого PostgreSQL взял TimeScale. Виной всему красивые отчеты о скорости работы и отсутствие деградации с ростом данных.
Данное расширение позволяет эффективно хранить и считывать данные, привязанные ко времени. Так уж получилось, что результаты нагрузочного тестирования являются как раз такими данными. Все метрики имеют временные метки, так же как и финальные результаты с метаданными. К настоящему моменту база данных с исходными метриками достигла объема в 50 ГБ, и пока ее поведение соответствует тому, что описано в статьях и документах ее разработчиков.Таким образом, TimeScale прекрасно подходит для нужд нагрузочного тестирования.
Интеграция платформы НТ с Test IT
Благодаря Airflow удалось легко расширить процесс нагрузочного тестирования за счет простого добавления новых задач. Стало возможно удобно следить за процессом и по мере надобности перезапускать его лишь частично. Вышеописанный DAG нагрузочного теста вызывается из другого DAG — нагрузочного прогона. После завершения тестов он автоматически проводит анализ результатов всего прогона, агрегацию данных и расчет индексов производительности.
Полученный процесс стал рельсами, на которые можно быстро водрузить нагрузочное тестирование для нового продукта. Как только понадобилось проводить исследование второго продукта TeamStorm, не пришлось ничего переделывать. Нужно было лишь собрать образ с нагрузочными тестами и указать метаданные в Airflow.
Для удобства команды TeamStorm создали отдельный DAG, который вызывает все тот же вышеописанный процесс. Таким образом процесс нагрузочного тестирования очень хорошо ложиться на сущности Airflow.
Есть у данного инструмента свои недостатки и особенности:
Высокий порог вхождения. Документация не всегда бывает понятна и структурирована. Некоторые вещи можно узнать заглянув в исходный код или готовые примеры.
Подключение дополнительных Celery workerов может быть не совсем прозрачным процессом. Особенно если они запускается внутри контейнера.
Стоит помнить о том, что работа процессов порождает обилие логов и файлов, которые надо периодически самостоятельно чистить.
Запускать составленные DAGи удобно через REST Airflow, CLI или веб-интерфейс, но коллегам все равно было сложно запускать нагрузочные тесты самостоятельно. Ведь процесс ожидает на вход список нагрузочных тестов и идентификаторы, с которыми можно легко ошибиться. Тут-то нам и помогла Test IT, наша система управления тестированием!
Внимательный читатель вспомнит про интеграцию с Test IT с помощью Pytest в первой итерации улучшения нагрузочного процесса. После начала загрузки результатов в базу данных связь с TMS оказалась не нужна и отмерла. С введением в строй платформы для НТ спираль развития связи с Test IT начала новый оборот. Пользователи нашей системы знают про функционал веб-хуков — на их основе была выстроена прямая интеграция платформы. Реализовали это следующим образом:
В Test IT создан отдельный проект для нагрузочных тестов.
В него были загружены автотесты, содержащие метаданные нагрузочных тестов. Это то, что ожидает DAG на входе. Метаданные хранятся внутри шагов автотеста.
Настроен вебхук на запуск автотестов. Вебхук вызывает адаптер (см. следующий шаг) и передает стандартный контекст. Подробнее о настройке вебхуков — по ссылке.
Создан специальный REST адаптер на базе Rocket языка Rust. Он принимает на вход стандартный контекст, извлекает данные о нагрузочных тестах и проводит запуск DAG в Airflow.
Теперь стало возможно запускать нагрузочные тесты из интерфейса Test IT простым набором шагов:
Выбрать нагрузочные тесты, составляющие прогон
Нажать кнопку «Запустить автотесты»
Выбрать окружение при помощи конфигурации
Нажать кнопку «Сохранить»
Данная интеграция позволила легко запускать нагрузочные тесты. Помимо нее была создана и обратная интеграция со стороны платформы. Для этого завели отдельные задачи внутри DAG процесса, которые выполняли следующие действия:
Создание прогонов внутри Test IT. При этом идентификатор прогона являются одинаковыми для TMS и платформы.
Результаты нагрузочных тестов появляются в Test IT по мере готовности. При этом они содержат краткую сводку с результатами, а также ссылку на сгенерированный отчет.
Созданные интеграции значительно упростили взаимодействие с нагрузочными тестами. Например, когда коллегам из команды DevOps потребовалось исследовать работу системы в разных конфигурациях, они смогли самостоятельно провести всю работу, используя нагрузочные тесты без привлечения инженера.
Краткое резюме
Итогом работы стала расширяемая платформа на базе Airflow, которая позволяет запускать произвольный инструмент НТ в виде контейнера с автоматическим анализом и хранением результатов в TimeScale. Платформа интегрирована с TMS Test IT.
Для достижения этого результата герой нашего рассказа перебрал множество подходов и технологий. Процесс нагрузочного тестирования постоянно менялся и преобразовывался. Происходила своего рода эволюция, в ходе которой какие-то технологии приспосабливались и выживали, а какие-то плавно отмирали. Ее двигателем были постоянные потребности и желания заинтересованных лиц, а также самого инженера. И этот процесс не останавливается и сейчас.