пятница, 15 ноября 2019 г.

Нагрузочное тестирование как сервис

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

Как решить некоторые проблемы с организацией тестирования вы можете прочитать в моей предыдущей статье: "Тестирование в общем сборочном конвейере: решение организационных проблем". В этой статье я расскажу про возможность интегрировать ваши нагрузочные тесты в общий CI-конвейер на GitLab CI используя концепцию "Нагрузочное тестирование как сервис" (Load testing as a service).  Также вы узнаете: как и какие докер-образы источников нагрузки Apache JMeter и Yandex.Tank можно использовать в CI-конвейере; как подключить источники нагрузки в свой проект при помощи шаблона .gitlab-ci.yml; как выглядит демо-пайплайн для запуска нагрузочных тестов и публикации результатов. Статья будет полезна инженерам по тестированию ПО и инженерам-автоматизаторам.

План:
  1. Суть концепции
  2. Основные понятия и определения
  3. Основные оцениваемые метрики
  4. Принципиальная схема нагрузочного тестирования
  5. Подключение в шаблоне .gitlab-ci.yml источников нагрузки: Yandex.Tank и Apache JMeter
  6. Заключение

Суть концепции


Концепция "Load testing as a service" подразумевает возможность интегрировать инструменты нагрузки Apache JMeter, Яндекс.Танк и собственные фреймворки в систему continuous integration GitLab CI. Она позволяет реализовать централизованный сервис для проведения нагрузочного тестирования. Нагрузочные тесты запускаются в выделенных пулах агентов, публикация результатов происходит автоматически в общие GitLab Pages, Influx DB, Grafana или иные системы тест-репортинга (TestRail, ReportPortal etc). Автоматизация всего этого реализуется максимально просто — через добавление и параметризацию в CI-проекте обычного шаблона gitlab-ci.yml.

При этом вся CI-инфраструктура, нагрузочные агенты, докер-образы источников нагрузки пайплайны тестирования и публикация отчётов — поддерживаются силами выделенного отдела автоматизации (DevOps), а инженеры по нагрузочному тестированию могут сосредоточить свои усилия на разработке тестов и анализе их результатов.

Будем считать, что целевое тестируемое приложение или сервер были развёрнуты и настроены заранее (для этого могут быть использованы автоматизированные сценарии на Python, SaltStack, Ansibe etc). Тогда вся концепция нагрузочного тестирования как сервиса укладывается в три этапа: подготовка, тестирование, публикация отчётов. Подробнее на схеме (картинки кликабельны):


Основные понятия и определения


Нагрузочный агент (load agent) — виртуальная машина, на которой будет запущено приложение источник нагрузки (Apache JMeter, Яндекс.Танк или самописный нагрузочный модуль).

Цель тестирования (target) — сервер или приложение, установленное на сервере, которое будет подвергаться нагрузке.

Тестовый сценарий (test case) — набор параметризованных шагов: действия пользователей и ожидаемые реакции на эти действия, с зафиксированными сетевыми запросами и ответами, в зависимости от заданных параметров.

Профиль или план нагрузки (profile) — в методологии ISTQB (п. 4.2.4, стр. 43) профили нагрузки определяют критичные для конкретного теста метрики и варианты изменения параметров нагрузки в течение теста. Примеры профилей вы можете увидеть на картинке.

Тест (test) — сценарий с предопределённым набором параметров.

Тест-план (test-plan) — набор тестов и профиль нагрузки.

Тестран (testrun) — одна итерация запуска одного теста с полностью выполненным сценарием нагрузки и полученным отчетом.

Сетевой запрос (request) — http-запрос, отправляемый от агента к цели.

Сетевой ответ (response) — http-ответ, отправляемый от цели к агенту.

HTTP-код ответа (HTTP responses status) — код ответа от сервера приложений.

Транзакция (transaction) — полный цикл запрос – ответ. Транзакция считается от начала отправки запроса (request) до завершения приёма ответа (response).

Статус транзакции (transactions status) — удалось ли успешно завершить цикл запрос – ответ. Если в этом цикле была любая ошибка, то транзакция считается неуспешной.

Время отклика (latency) — время от окончания отправки запроса (request) до начала приёма ответа (response).

Метрики нагрузки (metrics) — определяемые в процессе нагрузочного тестирования характеристики нагружаемого сервиса и нагрузочного агента.

Основные оцениваемые метрики


Некоторые, наиболее общеупотребительные и рекомендуемые в методологии ISTQB (стр. 36, 52) метрики, приведены в таблице ниже.

Метрики нагрузочного агента
Метрики целевой системы или приложения, тестируемых под нагрузкой
Количество vCPU и памяти RAMDisk (в Mb или Gb) — "железные" характеристики нагрузочного агента.CPU, Memory, Disk usage — динамика загрузки процессора, памяти и диска в процессе тестирования. Обычно измеряется в % от максимально доступных значений.
Network throughput (load agent) — пропускная способность сетевого интерфейса на сервере, где установлен нагрузочный агент. Обычно измеряется в байтах в секунду (bps).Network throughput (target) — пропускная способность сети на целевом сервере. Обычно измеряется в байтах в секунду (bps).
Virtual users — количество виртуальных пользователей, реализующих сценарии нагрузки и имитирующие реальные действия пользователей.
Virtual users status, Passed/Failed/Total — количество успешных, неуспешных статусов работы виртуальных пользователей для сценариев нагрузки, и их общее количество.
Обычно ожидается, что все пользователи смогли выполнить все свои задачи, указанные в профиле нагрузки. Любая ошибка будет означать то, что и реальный пользователь не сможет решить свою задачу при работе с системой.
Requests per second (minute) — количество сетевых запросов в секунду (или минуту).
Важная характеристика нагрузочного агента: сколько он может генерировать запросов. Фактически, это обращения к приложению виртуальными пользователями.
Responses per second (minute) — количество сетевых ответов в секунду (или минуту).
Важная характеристика целевого сервиса: сколько было получено ответов на отправленные с нагрузочного агента запросы.

HTTP responses status — количество различных кодов ответа от сервера приложения. Например, 200 OK означает успешное обращение, а 404, что ресурс не обнаружен.

Latency (время отклика) — время от окончания отправки запроса (request) до начала приёма ответа (response). Обычно измеряется в миллисекундах (ms).

Transaction response time — время одной полной транзакции: завершение цикла запрос – ответ. Это время от начала отправки запроса (request) до завершения приёма ответа (response).
Время транзакции может измеряться в секундах (или минутах) несколькими способами: считают минимальное, максимальное, среднее и, например, 90-й перцентиль. Минимальные и максимальные показания показывают крайние состояния производительности системы. 90-й перцентиль используется наиболее часто, поскольку он показывает большинство пользователей, комфортно работающих на пороге производительности системы.

Transactions per second (minute) — количество полных транзакций в секунду (минуту), то есть сколько приложение смогло принять и обработать запросов и выдать ответов. Фактически, это пропускная способность системы.

Transactions status, Passed / Failed / Total — количество успешных, неуспешных и общее количество транзакций.
Для реальных пользователей неуспешная транзакция фактически будет означать невозможность работы с системой под нагрузкой.


Принципиальная схема нагрузочного тестирования


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


На схеме:
  • QA.Tester — эксперт по нагрузочному тестированию
  • Target — целевое приложение для которого нужно узнать его поведение под нагрузкой

Этапы и шаги на схеме


Этапы и шагиЧто происходит на этапе или шагеЧто на входеЧто на выходе
Prepare: этап подготовки к тестированию
LoadParametersЗадание и инициализация пользователем параметров нагрузки, выбор метрик и подготовка тест-плана (профиля нагрузки).Пользовательские параметры для инициализации агента нагрузки.
Тест-план.
Цель тестирования.

VMРазвёртывание в облаке (vSphere или внешние) виртуальной машины с требуемыми характеристиками.Параметры ВМ для агента нагрузки.
Скрипты автоматизации для создания ВМ.
Настроенная ВМ в облаке.
EnvНастройка ОС и подготовка окружения для работы нагрузочного агента.Параметры окружения для агента нагрузки.
Скрипты автоматизации для настройки окружения.
Подготовленное окружение: ОС, сервисы и приложения, необходимые для работы агента нагрузки.
LoadAgentsУстановка, настройка и параметризация нагрузочного агента. Либо скачивание докер-образа с преднастроенным приложением источником нагрузки.Докер-образ источника нагрузки (ЯТ, JM или самописный фреймворк).
Параметры настройки агента нагрузки.
Настроенный и готовый к работе агент нагрузки.
Test: этап выполнения нагрузочных тестов. Источниками являются агенты нагрузки, размещённые в выделенных пулах агентов для GitLab CI
LoadЗапуск параметризованного агента нагрузки с выбранным тест-планом.Пользовательские параметры для инициализации агента нагрузки.
Тест-план.
Цель тестирования.
Логи выполнения нагрузочных тестов.
Системные логи.
Динамика изменения метрик цели и нагрузочного агента.
RunAgentsИсполнение агентом нагрузки тестовых сценариев в соответствии с профилем нагрузки. Взаимодействие агента нагрузки с целью тестирования.Тест-план.
Цель тестирования.

LogsСбор "сырых" логов в процессе нагрузочного тестирования: о действиях агента нагрузки, состоянии цели тестирования и ВМ, на которой запущен агент.
Логи выполнения нагрузочных тестов.
Системные логи.
MetricsСбор "сырых" метрик в процессе тестирования.
Динамика изменения метрик цели и нагрузочного агента.
Report: этап подготовки отчёта по тестированию
GeneratorОбработка собранных нагрузочной системой и системой мониторинга, "сырых" метрик и логов. Формирование отчёта в человеко-читаемом виде, возможно, с элементами аналитики.Логи выполнения нагрузочных тестов.
Системные логи.
Динамика изменения метрик цели и нагрузочного агента.
Обработанные "сырые" логи в формате, пригодном для выгрузки во внешние хранилища.
Статический отчёт по нагрузке, пригодный для анализа человеком.
PublishПубликация отчёта по нагрузочному тестированию во внешнем сервисе.Обработанные "сырые" логи в формате, пригодном для выгрузки во внешние хранилища.Сохранённые во внешнем долговременном хранилище отчёты по нагрузке, пригодные к анализу человеком.


Подключение в шаблоне .gitlab-ci.yml источников нагрузки: Yandex.Tank и Apache JMeter


Давайте перейдём к практической части. Я попытаюсь рассказать про то, как примерно реализована концепция нагрузочного тестирования как сервиса внутри нашей компании Positive Technologies.

Сначала силами нашего отдела автоматизации (DevOps) в GitLab CI был создан выделенный пул агентов, которые используются только для запуска нагрузочных тестов. Чтобы не путать их в шаблонах с другими, например, сборочными пулами, мы протегировали эти агенты, tags: load. Можно использовать любые другие понятные теги. Они задаются во время регистрации GitLab CI Runners.

Характеристики нагрузочных агентов, достаточное количество vCPU, RAM и Disk было рассчитано исходя из того, что на агенте должны быть запущены:
  • Docker,
  • Python (для Яндекс.Танка),
  • агент GitLab CI,
  • Java (для Apache JMeter).

    Про Java для JMeter также есть рекомендации использовать минимум 512Mb RAM и, в качестве верхнего предела, 80% доступной памяти ("Just increase the maximum heap size to ~80% of your total available physical RAM. For example, if you want to set the maximum heap size to 4 gigabytes, you’ll need to change the line to: HEAP="-Xms512m -Xmx4096m").
Таким образом, исходя из практического опыта, рекомендуется использовать для нагрузочных агентов как минимум: 4 vCPU, 4GB RAM, 60GB SSD.

У нас, в основном, используются два источника нагрузки: докер-образы Apache JMeter и Yandex.Tank.

Yandex.Tank — это опенсорс-инструмент для проведения нагрузочного тестирования от компании Yandex. В основе его модульной архитектуры — высокопроизводительный асинхронный hit-based-генератор HTTP-запросов Phantom. Танк имеет встроенный мониторинг ресурсов тестируемого сервера по протоколу SSH, может автоматически остановить тест по заданным условиям, умеет выводить результаты в консоль и в виде графиков, а также к нему можно подключить свои модули для расширения функциональности. Мы уже применяли когда-то этот инструмент для проведения нагрузочного тестирования одного из продуктов компании: PT Appllication Firewall. Подробнее читайте об этом в статье: "Автоматизация нагрузочного тестирования при помощи инструмента Яндекс.Танк" (или на Хабре).

Apache JMeter — это опенсорс-инструмент для проведения нагрузочного тестирования от компании Apache. Он может использоваться одинаково хорошо как для тестирования статических, так и динамических веб-приложений. JMeter поддерживает огромное количество протоколов и способов взаимодействия с приложениями: HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET etc), SOAP / REST Webservices, FTP, TCP, LDAP, SMTP(S), POP3(S) и IMAP(S), базы данных через JDBC, умеет исполнять shell-команды и работать с Java-объектами. Инструмент обладает полнофункциональной IDE для создания, отладки и исполнения тест-планов. Также имеется возможность работы через CLI в командной строке в любой совместимой с Java ОС (Linux, Windows, Mac OS X). JMeter умеет генерировать динамический html-отчет по результатам тестирования.

Для удобства использования внутри компании и возможности изменять или добавлять окружение для этих инструментов самими тестировщиками, мы сделали простые сборочные pipelines докер-образов на GitLab CI с их публикацией во внутренний докер-регистри на Artifactory. Так получается быстрее и проще подключать их затем в пайплайнах для выполнения нагрузочных тестов. Как сделать docker push в registry через GitLab CI смотрите в инструкции.

Базовый докер-файл для Yandex.Tank мы взяли такой:
Dockerfile 
1 | FROM direvius/yandex-tank
2 | ENTRYPOINT [""]

А для Apache JMeter такой:
Dockerfile 
1 | FROM vmarrazzo/jmeter
2 | ENTRYPOINT [""]

Как устроена наша система Continuous Integration вы можете прочитать в статье "Как мы в Positive Technologies внедряли идеи DevOps" (или на Хабре).

Шаблон и пайплайн


Демо-пример шаблона GitLab CI для проведения нагрузочных тестов, аналогичный используемым у нас, доступен в проекте по ссылке: demo-load. В README-файле можно прочитать инструкцию по доработке и использованию шаблона. В самом шаблоне .gitlab-ci.yml есть примечания, за что отвечает тот или иной шаг.

Шаблон очень простой и демонстрирует три этапа нагрузочного тестирования, описанных выше на схеме: подготовка, тестирование и публикация отчётов. За это отвечают stages: Prepare, Test и Report в шаблоне.
  1. Этап Prepare можно использовать для предварительной настройки целей тестирования или проверки их доступности. Окружение для источников нагрузки настраивать не потребуется, если они уже были собраны как докер-образы и выложены в локальный docker registry: достаточно указать нужную версию на этапе Test.
     
  2. Этап Test в шаблоне используется для выбора источника нагрузки и сохранения артефактов тестирования. Можно указать любой источник нагрузки: Yandex.Tank, Apache JMeter, свой или все вместе. Также можно отключить ненужные источники, просто закомментировав или удалив job-у.

    Параметры запуска Yandex.Tank отредактируйте в файле ./tests/yandextank.sh, а параметры запуска Apache JMeter отредактируйте в файле ./tests/jmeter.sh.

    Примечание: шаблон сборочной конфигурации предполагает настройку взаимодействия с CI-системой, а способы запуска тестов и формирование отчётов вынесены во внешние файлы и тестовые сценарии. В шаблоне указывается точка входа, где находится управляющий скрипт, а его логика и тестовые сценарии должны быть реализованы QA-инженерами. В нашем демо-примере для обоих источников нагрузки в качестве простейшего теста используется реквест основной странички Яндекса. Сценарии и настройки для JMeter и Танка лежат в каталоге ./tests.
     
  3. Этап Report описывает способы публикации результатов тестирования, сформированных на этапе Test, во внешние хранилища: GitLab Pages или иные системы отчётности.

    Для GitLab Pages требуется, чтобы после окончания тестов каталог ./public был не пустой и содержал, как минимум, файл index.html. О нюансах работы GitLab Pages вы можете почитать по ссылке.

    Примеры, как экспортировать данные:

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



Apache JMeter умеет сам формировать html-отчёт, поэтому его выгоднее запушить в GitLab Pages штатными средствами. Пример, как выглядит отчёт Apache JMeter на GitLab Pages:


Yandex.Tank из коробки поддерживает публикацию в InfluxDB и Grafana, поэтому в демо-примере на GitLab Pages мы разместили лишь фейковый текстовый отчёт. Yandex.Tank умеет сам в процессе тестирования сохранять результаты в базу InfluxDB, а оттуда его можно отобразить, например, в Grafana (настройки выполняются в файле ./tests/example-yandextank-test.yml). Пример, как отчёт Танка выглядит в Grafana:


Заключение


Из статьи вы узнали, что концепция "Нагрузочное тестирование как сервис" (Load testing as a service) подразумевает возможность интеграции различных источников нагрузки в GitLab CI. Для этого используется инфраструктура преднастроенных пулов нагрузочных агентов, докер-образы источников нагрузки, системы отчётности и объединяющий их пайплайн в GitLab CI на простом шаблоне .gitlab-ci.yml. Всё это поддерживается силами выделенной команды инженеров-автоматизаторов (DevOps) и тиражируется по запросу команд инженеров по нагрузке.

На этом знакомство с концепцией будем считать завершённым. Надеюсь, это поможет вам в подготовке и реализации аналогичной схемы в вашей компании. Благодарю за внимание!