В ходе тестирования некоторых продуктов компании Positive Technologies наша команда тестировщиков-автоматизаторов столкнулась с необходимостью проведения быстрых стресс-тестов для одного http веб-сервиса. Такие тесты должны были быть простыми и быстрыми в разработке, не требовательными к аппаратным ресурсам, должны давать значительную нагрузку однотипными http-запросами, предоставлять статистические данные для анализа системы под нагрузкой.
Для их реализации мы исследовали и опробовали некоторое количество инструментов, среди которых был и Apache JMeter, о котором было рассказано ранее, и даже сделанный нами самодельный инструмент LogSniper, написанный на Python, который выполнял реплей заранее подготовленных серверных логов с http-запросами на цель.
Использование JMeter-а мы отклонили из-за значительной сложности подготовки и проведения тестов, высоких требований к производительности нагрузочного стенда и довольно малых мощностей нагрузки при этом, хотя это и компенсировалось высокой информативностью показателей собираемой статистики. А LogSniper был отклонён из-за малой мощности генерируемой нагрузки и даже простота подготовки нагрузочных http-пакетов не могла стать большим преимуществом. Другие инструменты также были отклонены по тем или иным причинам.
В итоге мы с моим коллегой Олегом Каштановым остановились на инструменте Яндекс.Танк (yandex-tank), о котором узнали побывав на конференции YAC-2013 и пообщавшись со специалистами Яндекса на эту тему. Этот инструмент полностью отвечал всем нашим требованиям к простоте подготовки теста и к генерируемой нагрузке.
1. Вкратце об инструменте Яндекс.Танк
Яндекс.Танк - инструмент для проведения нагрузочного тестирования, разрабатываемый в компании Яндекс и распространяемый под лицензией LGPL.
В основе инструмента лежит высокопроизводительный асинхронный генератор нагрузки phantom, который был изначально переделан из одноимённого веб-сервера, который "научили" работать в режиме клиента. При помощи phantom возможно генерировать десятки и сотни тысяч http-запросов в секунду (далее http-rps - http-requests per second).
В процессе своей работы Яндекс.Танк сохраняет полученные результаты в обычных текстовых лог-файлах, сгруппированных по директориям для отдельных тестов. В течение теста специальный модуль организует вывод результатов в табличном виде в консольный интерфейс. Одновременно с этим запускается локальный веб-сервер, позволяющий видеть те же самые результаты на информативных графиках. По окончании теста возможно автоматическое сохранение результатов на сервисе Loadosophia.org. Также имеется модуль загрузки результатов в хранилище Graphite.
Некоторые полезные ссылки:
- Код проекта на GitHub:
https://github.com/yandex-load/yandex-tank - Официальная документация по настройке и использованию инструмента:
http://yandextank.readthedocs.org/en/latest/ - Информация о модулях Яндекс.Танка в wiki разработчиков:
https://github.com/yandex-load/yandex-tank/wiki/%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D0%B8 - Презентация, в которой рассказывается об истории возникновения инструмента:
http://tech.yandex.ru/events/yasubbotnik/msk-jul-2012/talks/296/ - История возникновения сервиса нагрузочного тестирования в Яндексе и разработка Яндекс.Танка:
http://habrahabr.ru/company/yandex/blog/202020/ - Яндекс-клуб, посвященный вопросам использования инструмента:
http://clubs.ya.ru/yandex-tank/
Функциональные особенности Яндекс.Танка:
- возможность простого конфигурирования инструмента используя ini-файлы, либо опции командной строки;
- гибкая настройка профилей нагрузки;
- настраиваемый авто-стоп теста по различным критериям, например, если время отклика сервера превышает заданный порог или допустимое количество сетевых ошибок;
- высокая производительность (до 300k http-rps на стенде 16 vCPU, 8 GB, 10 Gb/s) при использовании генератора phantom, которая однако, может быть ограничена производительностью тестируемого веб-сервера - до ~64k http-rps, связанной с количеством одновременно открываемых соединений;
- наглядная визуализация процесса тестирования с использованием локального веб-сервера, а также наличие подробной статистической информации в консоли;
- наличие собственного агента для мониторинга ресурсов на стороне сервера с тестируемым веб-приложением по протоколу ssh;
- возможность использовать Apache Jmeter в качестве альтернативного генератора нагрузки.
2. Настройка и стандартные режимы работы Яндекс.Танка
Для разворачивания нагрузочного стенда мы использовали виртуальную машину с Ubuntu 12.04.2 Server x64.
Установка Яндекс.Танка из пакета осуществляется одной простой командой:
sudo apt-get install yandex-tank-base
Настройка профилей производится в файлах с расширением *.ini, которые должны находиться в директории /etc/yandex-tank/
Запуск Яндекс.Танка осуществляется под рутом одной командой (при этом нужно находиться в директории с ini-файлами):
yandex-tank [ammo_file]
После запуска Яндекс.Танк ищет ini-файлы с конфигурациями профилей нагрузки, а также подгружает, при указании ammo_file, так называемый патрон - специальным образом подготовленный файл с http-запросами. Простейшие GET http-запросы можно указать в файле со стандартным профилем load.ini. Из заданного патрона Яндекс.Танк предварительно готовит ленту запросов, генерируя *.stpd-файлы. Лента хранит сразу все запросы, которые будут отправлены в течение теста для определенного профиля. После смены патрона или настроек профиля она будет сгенерирована заново.
Задание профилей нагрузки через стандартный файл с настройками load.ini
[phantom] address=example.com:80 ; Target's address and port rps_schedule=const(65000,2m) ; load scheme header_http = 1.1 headers = [Host: example.com] [Connection: close] uris = /test [web] port = 80 ; interval = 1 ; manualstop = 1 ;
Задание профиля включает в себя определение тегов с параметрами настроек:
- используемый генератор нагрузки,
- адрес нагружаемого веб-сервера и его порт,
- используемая схема нагрузки.
Далее указываются, формируя при этом патрон, используемый при нагрузке:
- хедеры http-пакета,
- путь для GET-запроса (uris).
Дополнительно могут задаваться параметры веб-интерфейса Яндекс.Танка (тег [web] рассмотрен ниже).
Файлы с настройками чувствительны к пробелам: для задания комментария после параметра требуется указывать один пробел и точку с запятой, затем ещё один пробел.
В приведённом примере:
[phantom] - используемый генератор нагрузки, можно также указать модуль JMeter, тег [jmeter].
rps_schedule - схема нагрузки. В ней можно указать одну из функций: const(load,dur), line(a,b,dur), step(a,b,step,dur), либо комбинации данных функций.
Например, комбинация:
rps_schedule=const(1,30s) line(1,1000,2m) const(1000,3h)
задаёт схему нагрузки при использовании которой нагрузка в 1 http-rps будет держаться в течении 30 секунд, затем нагрузка будет линейно возрастать с 1 до 1000 http-rps в течении 2 минут, после чего будет держаться на уровне 1000 http-rps в течение 3 часов.
step(a,b,step,dur) - пошаговое увеличение нагрузки, где a и b начальное и конечное значения нагрузки в http-rps, step - шаг увеличения нагрузки, dur - время, через которое увеличивается нагрузка на указанный шаг.
line(a,b,dur) - линейная нагрузка, где a и b - начальная и конечная нагрузка, dur - время, в течение которого нагрузка линейно увеличивается от a до b.
const(load,dur) - постоянная нагрузка, где load - значение нагрузки, dur - время нагрузки.
Запросы можно размещать как в теге [phantom] файла load.ini, так и в отдельном файле-патроне.
3. Подготовка собственных запросов для Яндекс.Танка
При создании патрона внутри файла load.ini вид запросов может следующий:
header_http = 1.1 headers = [Host: example-domain.ptsecurity.ru] [Connection: close] uris = /test /test1 /test2
Таким образом, для каждого uri будут добавлены заголовки, описанные в headers.
При размещении запросов в отдельном файле вид запросов может быть следующий:
[Connection: close] [Host: example-domain.ptsecurity.ru] [Cookies: None] /?arg / /buy /buy/?rt=0&station_to=7&station_from=9
Для нестандартных запросов, например POST-http, необходимо создавать текстовый файл следующего формата:
[size_of_request] [tag]\n [body_request] \r\n [size_of_request2] [tag]\n [body_request2] \r\n
Алгоритм подготовки запросов по формату Яндекс.Танка:
- Создать ammo.txt - текстовый файл *nix-формата (с переносами вида \n).
- В текстовом редакторе записать пакет как есть: так как он обычно выглядит, например, в браузере.
- Добавить в конце пакета последний перенос (тоже только \n без \r).
- Сохранить, затем определить длину файла в байтах.
- Если в файле один запрос, то перед ним нужно записать первым числом определённый размер пакета в байтах, в конце строки также поставить перенос \n.
- Для разделения нескольких запросов в файле нужно вставлять между ними перенос строки \n.
- Для GET http-запроса с пустым боди - нужно сделать дополнительный перенос строки \n, чтобы пакет соответствовал RFC 2068.
- Последний перенос строки \n для разделения двух запросов также обязателен.
Примечание: вообще, по спецификации RFC 2068, любой http-запрос должен завершаться двумя байтами CRLF (0x0D 0x0A, \r\n), но опытным путем было установлено, что при подготовке ленты запросов Яндекс.Танк сам добавляет нужные окончания строк. А первое число - размер запроса в байтах - он требует вычислять считая, что пакет подготовлен в *nix-формате.
Подготовив таким образом патрон с запросами, достаточно запустить Яндекс.Танк и указать его, а ленту запросов (.stpd-файл) он сгенерит сам:
yandex-tank ammo.txt
Дополнительно можно почитать о способах генерации различных видов запросов с помощью perl-скриптов.
4. Дополнительная конфигурация Яндекс.Танка
Включение веб-монитора
Тег [web] в ini-файле конфигурации подключает модуль веб-монитора. Его основные параметры:
- port = 80 ; порт на котором запущен веб-сервер,
- interval = 60 ; интервал в секундах, который должен быть отображен на графике, по умолчанию, 1 минута,
- manualstop = 1 ; 1 - означает, что для остановки веб-сервера после тестирования в консоли Яндекс.Танка необходимо нажать Enter.
Типы отображаемых графических данных:
- На центральном графике отображается схема нагрузки и распределение квантилей времени http-ответов. Переключатели под ним управляют отображением графиков времени, в которое укладывается соответствующее число процентов http-ответов. Также можно включить или отключить отображение схемы текущей нагрузки.
- На левом нижнем графике отображается информация о статус-кодах сетевых ответов или протокола.
- На правом нижнем графике отображаются усредненные значения временных задержек для отправленных запросов (send), при обработке данных на стороне сервера (latency), полученных ответов (receive) и время соединения с сервером (connect).
Данные графики очень просты и наглядны для анализа. Если в какой то момент времени на них наблюдаются всплески или скачки показателей для среднего времени отклика, резко увеличивается число сетевых ошибок или появляются статус-коды http-ответов 5xx, значит при соответствующей нагрузке веб-сервер начинает испытывать проблемы с производительностью.
Подключение агента Яндекс.Танка для измерения параметров на тестируемом сервере
Для получение метрик с целевой машины - той, на которой установлен тестируемый веб-сервер - необходимо настроить к ней доступ по ssh и выполнить следующие шаги:
- Убедиться, что на целевой машине установлен и запущен ssh-сервер.
- На стенде Яндекс.Танка создать пару ключей под тем пользователем, под которым будет создаваться нагрузка:
ssh-keygen - Запустить ssh-агента на стенде Яндекс.Танка:
exec ssh-agent bash - Добавить агенту сгенеренные ключи:
ssh-add - Передать на целевую машину сгенеренные ключи:
ssh-copy-id <hostname> - Указать собираемые метрики в отдельном xml-файле. Пример содержимого такого файла config_file:
<Monitoring> <Host address="example-domain.ptsecurity.ru" comment="Load testing"> <CPU measure="user,system"/> <Memory measure="free,used"/> <Net measure="tx,rx,recv,send"/> </Host> </Monitoring>
- В ini-файле с настройками для Яндекс.Танка необходимо указать путь к метрикам, определив тег [monitoring]:
[monitoring]
config = config_file ;
После этого, Яндекс.Танк при запуске скопирует своего агента по ssh на целевую машину и будет выдавать значения её метрик в консоли в реальном времени:
Настройка параметров автостопа для тестов
Критерии автостопа теста определяются в секции [autostop] ini-файла с настройками Яндекс.Танка. Для этого задается опция autostop - это список критериев автостопа, разделенных пробелами.
Формат критериев:
type(parameters)
Базовые типы критериев:
- time - остановить тест, если среднее время ответа превышает заданный порог в течение заданного времени, код выхода 21. Например:
time(1s500ms,30s)
time(50,15) - http - остановить тест, если число http статус-кодов, соответствующих маске, превысит заданный абсолютный или относительный порог, код выхода 22. Например:
http(404,10,15)
http(5xx,10%,1m) - net - аналогично http, но применяется к сетевым статус-кодам, код выхода 23. Допустима маска xx, означающая "все ненулевые".
- quantile - остановить тест, если выбранный процентиль находится выше определенного уровня в течение N секунд. Список допустимых квантилей для процентиля: 25, 50, 75, 80, 90, 95, 98, 99, 100. Например:
quantile(95,100ms,10s) - instances - тип добавляется при подключении модуля phantom. Тест останавливается, если число активных инстансов выше абсолютного или относительного порога, код выхода 24. Например:
instances(80%, 30)
instances(50,1m) - metric_lower и metric_higher - срабатывают, если значение метрики ниже или выше порога в течение заданного времени, коды выхода 31 и 32 соответственно. Например:
metric_lower(127.0.0.1,Memory_free,500,10)
Нужно обратить внимание на то, что имена метрик (кроме кастомных) пишутся не через пробел, а через нижнее подчеркивание. В именах хостов также допустимо использовать маски, например:
target-*.load.net
target-*.load.net
Продвинутые типы критериев:
- total_time - остановить тест, если N% ответов превышает порог времени ответа в течение заданного интервала времени, код выхода 25. От time отличается тем, что аккумулирует информацию. Это означает, что в заданном интервале могут быть моменты времени, которые не удовлетворяют критерию, но весь интервал в целом ему удовлетворяет. Например:
total_time(100ms,70%,3s) - total_http - остановить тест, если N% (или абсолютное значение) ответов пришли с кодом по заданной маске в течение заданного времени, код выхода 26. Так же использует аккумуляцию данных. Например:
total_http(5xx,10%,10s)
total_http(3xx,40%,10s) - total_net - остановить тест, если N% (или абсолютное значение) ответов пришли с кодом по заданной маске в течение указанного промежутка времени, код выхода 27. Аккумулирующий критерий. Например:
total_net(79,10%,10s)
total_net(11x,50%,15s) - negative_http - остановить тест, если более N% кодов ответов не подходят под заданную маску, код выхода 28. Например:
negative_http(2xx,10%,10s)
В этом примере тест остановится, если количество http статус-кодов неравных 200 OK превысит 10% от общего числа http-ответов. - negative_net - остановить тест если более N% кодов ответов не подходят под заданную маску, код выхода 29. Например:
negative_net(0,10%,10s)
В этом примере тест остановится, если доля всех сетевых ошибок превысит 10% за последние 10 секунд. - http_trend - автостоп, который следит за трендом ответов по заданной маске, код выхода 30. Например:
http_trend(2xx,10s).
Он означает, что если тренд http статус-кодов 200 OK за последние 10 секунд начал снижаться с учетом погрешности измерения, то требуется остановить тест. Критерий может использоваться для того, чтобы не подбирать отдельно для каждого сервиса и его конфигурации границы времени ответа.
Настройки логирования
Данные о полученных ответах на http-запросы добавляются в процессе тестирования в файлы phout_*.log, которые содержат столбцы в следующем порядке: time, tag, interval_real, connect_time, send_time, latency, receive_time, interval_event, size_out, size_in, net_code, proto_code. Часть этих данных отображается также на веб-мониторе.
Заголовки полей означают следующее:
- time - unix-время в миллисекундах,
- tag - кастомный маркер для идентификации и анализа различных URL,
- interval_real - полное время прохождения пакета, mcs (microseconds),
- connect_time - время соединения с сервером (например, tcp-handshake), mcs,
- send_time - время отправки запроса на сервер (с первого до последнего байта запроса), mcs,
- latency - продолжительность обработки данных на стороне сервера (от последнего байта запроса, до первого байта ответа), mcs,
- receive_time - время получения данных с сервера (с первого до последнего байта ответа, время загрузки), mcs,
- interval_event - полное время прохождения данных, mcs,
- size_out - размер запроса (включая хедеры и спец-знаки \n, \r и т. п.), bytes,
- size_in - размер ответа (включая хедеры, http статус-коды, POST данные и т. п.), bytes,
- net_code - статус-код сетевого уровня, например, 0 - OK, 101 - Timeout, 104 - Reset by peer и т. д.,
- proto_code - статус-код http-протокола, например, 404 - Not Found, 200 - OK и т. д.
Дополнительно можно включить лог запросов и ответов опцией writelog=1 в теге [phantom]. Тогда их запись будет осуществляться в файлы answ_*.log.
5. Использование Яндекс.Танка для сравнения производительности двух аналогичных веб-сервисов
Схема тестовых стендов
В ходе работы нам потребовалось сравнить характеристики двух веб-сервисов, работу которых можно примерно описать как «прозрачные HTTP-прокси, перенаправляющие входящие запросы на backend-приложение». Именно для этого мы использовали Яндекс.Танк.
Общую схему работы можно изобразить следующим образом:
На стенде с Яндекс.Танком использовался генератор нагрузки phantom со включенным монитором производительности.
В качестве стенда web-proxy на схеме использовались два тестируемых веб-сервиса, с которых снимались показатели производительности при помощи агента Яндекс.Танка. Условно назовем их Первый веб-сервис и Второй веб-сервис. Нам требовалось сравнить: соответствует ли производительность Второго веб-сервиса Первому.
Для backend использовалось небольшое веб-приложение, запущенное под Nginx и возвращающее одну простую html-страничку.
Выявленные ограничения
Перед началом работ мы выяснили ограничения наших виртуальных стендов, на которых была построена вся тестовая инфраструктура.
Стенд backend-приложения с характеристиками:
8 vCPU, 4 GB, 10 Gb/s.
и веб-сервер Nginx, установленный на нём, выдерживали режимы нагрузки:
~25000 http-rps - максимальная отдача сервера, которой удалось добиться, но и при нагрузке выше 25k http-rps его работа не была нарушена.
Стенд Яндекс.Танка с характеристиками:
16 vCPU, 8 GB, 10 Gb/s,
позволил реализовать нагрузку:
до 300000 http-rps.
Пропускная способность виртуальной среды ESXi, определенная с помощью Iperf:
8 Gb/s в одну сторону, 4 Gb/s при двухсторонней нагрузке между двумя виртуальными машинами.
Метрики и критерии сравнения
Мы определили и измеряли в процессе тестирования следующие метрики для каждого профиля нагрузки:
- http_rps_out - значение http-rps отправляемое с Яндекс.Танка на веб-приложение,
- http_rps_in - значение http-rps принимаемое на Яндекс.Танке со стороны веб-приложения,
- http_request_size - размер http-запроса в байтах,
- send_requests - количество отправленных http-запросов,
- bs_out - bytes per seconds, байт в секунду - параметр определяет скорость отправки данных с Яндекс.Танка,
- bs_in - значение bs отправляемое с веб-приложения в сторону Яндекс.Танка,
- test_time - время теста в секундах,
- response_time_med - среднее время в которое укладывается 90% всех ответов.
Зная число http-запросов и их размер получаем, что bs и http-rps связаны по формуле:
bs = http_rps * http_request_size
Основными критериями для сравнения работы веб-сервисов под нагрузкой мы выбрали следующие:
- За всё время теста значение параметра "время, в которое укладывается 90% ответов" у Второго веб-сервиса должно быть не больше, чем у Первого веб-сервиса.
- На отрезке возрастания нагрузки на очередные 1000 http-rps значение параметра "время, в которое укладывается 90% ответов" у Второго веб-сервиса должно быть не больше, чем у Первого веб-сервиса.
- За всё время теста общее количество правильно обработанных запросов у Второго веб-сервиса должно быть не меньше, чем у Первого веб-сервиса.
Аналогичным образом можно определить иные критерии и профили нагрузочных тестов для своих проектов.
Тестовые http-запросы
Для одного из профилей нагрузочных тестов нам требовалось создать смешанный http-трафик из GET и POST запросов с линейным возрастанием нагрузки до 10k http-rps в течение 10 минут.
HTTP-запросы, вошедшие в патрон Яндекс.Танка:
GET /loadtest/index.php?id=1&login=user&pwd=password HTTP/1.1 X-Sniffer-Forwarded-For: yandex-tank-example-domain.ptsecurity.ru Host: backend-example-domain.ptsecurity.ru User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) POST /loadtest/index.php HTTP/1.1 X-Sniffer-Forwarded-For: yandex-tank-example-domain.ptsecurity.ru Host: backend-example-domain.ptsecurity.ru User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Content-Length: 32 id=1&login=user&pwd=password POST /loadtest/index.php HTTP/1.1 X-Sniffer-Forwarded-For: yandex-tank-example-domain.ptsecurity.ru Content-Type: multipart/form-data; boundary=validFile Host: backend-example-domain.ptsecurity.ru User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Content-Length: 150 --validFile Content-Disposition: form-data; name="login"; filename="validFile.txt" Content-Type: text/plain Valid file content --validFile--
Для упрощения подготовки патрона для такого смешанного трафика мы сделали скрипты, аналогичные perl-скриптам, предлагаемым на форуме Яндекс.Танка.
Сбор данных и анализ результатов
После подготовки запросов мы просто запустили Яндекс.Танк стандартным образом и выполнили нагрузочный тест со смешанным трафиком для обоих тестируемых веб-сервисов.
Результаты для Первого веб-сервиса
Информация веб-монитора Яндекс.Танка:
Информация консоли Яндекс.Танка:
Результаты для Второго веб-сервиса
Информация веб-монитора Яндекс.Танка:
Информация консоли Яндекс.Танка:
Даже судя по графикам, испытуемый сервис показывал результаты не хуже, чем эталонный. Проверив все три формально определенных выше критерия, мы в этом убедились.
- Второй веб-сервис удовлетворяет первому критерию, так как для 90% запросов среднее время ответов для Второго веб-сервиса не превышало такой же показатель для Первого веб-сервиса.
- Требование второго критерия выполнялось для каждого этапа нагрузки.
- Судя по анализу статус-кодов ответов, записанных в журналы Танка, Второй веб-сервис принял и корректно обработал запросов больше, чем Первый веб-сервис.
6. Выводы
Инструмент Яндекс.Танк может помочь всюду, где требуется быстро провести нагрузочное тестирование веб-приложений без их сложной подготовки и при этом получить множество полезных статистических данных для анализа производительности.
Также данный инструмент хорошо внедряется в имеющиеся системы автоматизации. Например, для упрощения работы со стендом Яндекс.Танка: его управлением, запуском, подготовки патронов для лент, контролем за процессом тестирования и сбором результатов, мной, без особых усилий, был написан класс-обвязка на Python, который подключается к стенду по ssh и выполняет все перечисленные действия. Затем он был встроен в нашу существующую систему авто-тестирования.
Дополнительно вы можете посмотреть, как подключить и использовать высокопроизводительную систему Graphit для анализа большого числа графиков, о которой рассказывалось в одной из презентаций на конференции YAC-2013. Её также можно приспособить для нужд нагрузочного тестирования с использованием Яндекс.Танка.