company_banner

Тестирование в Яндексе: строим свой Лунапарк



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

    Кстати, я буду рассказывать о Яндекс.Танке и Graphite на Тестовой Среде, регистрация на которую будет открыта ещё до 18:00 18 ноября. Там можно будет задать свои вопросы вживую.

    Если вы читали статью doctornkz о том, как организовано нагрузочное тестирование в Яндексе, то знаете, что результаты стрельб у нас лежат в хранилище, которое умеет их показывать через веб-интерфейс. Называется оно Лунапарк. Это очень удобно — провести тест и отправить ссылку на него всем заинтересованным людям (да и самому увидеть все на одной страничке). Сервис представляет собой веб-приложение, которое заточено на внутренние процессы (там и геймификация, и провязка с другими внутренними ресурсами), выкладывать которое в открытый доступ мы не планируем. Поэтому я решил рассказать, как построить подобную систему, используя только open-source продукты.

    Архитектура системы




    Система автоматизации — это модуль, который управляет запуском тестов, позволяет их параметризовывать, выполнять дополнительные действия (скачать лог продакшн-сервера или поднять тестовую среду). Все это можно осуществить с помощью таких инструментов, как Jenkins, Maven, Rake. Об этом сегодня рассказывать не буду, это тема для отдельного большого поста.

    Генератор нагрузки — это рабочая лошадка, модуль, который создает нагрузку на мишень (тестовый стенд). Рассказ будет о Яндекс.Танке — это модульная и расширяемая стрелялка, позволяющая использовать внутри разные генераторы, в частности, знакомый многим JMeter. Отмечу, что Танк — это open-source проект, опубликованный Яндексом в 2012 году. Он не для новичков, нужно быть на «привет, как дела?» с линуксом, а еще лучше уметь писать простые скрипты.

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

    Graphite — это высокопроизводительное и масштабируемое хранилище временных рядов, написанное на Python. Open-source. В него очень просто загружать данные (а еще для этого существует много разных способов на любой вкус) и потом их удобно крутить через Web-API (и для этого тоже есть куча фронтэндов). Подробно о том, как Graphite используется в Яндексе, о его архитектуре и производительности можно послушать тут.

    Установка Яндекс.Танка


    Если у вас Ubuntu — вы везунчик. Потому что вам всего-то нужно подключить репозиторий Танка и установить его также, как все другие пакеты — зависимости вытянутся сами (здесь и далее могут потребоваться права root — обеспечьте их):

    # эти строки нужно добавить в sources.list:
    deb http://ppa.launchpad.net/yandex-load/main/ubuntu precise main
    deb-src http://ppa.launchpad.net/yandex-load/main/ubuntu precise main
    


    apt-get update && sudo apt-get install yandex-load-tank-base
    


    Если у вас не Ubuntu (я вот, например, хочу на MacOS попробовать), можете попробовать скачать .deb и сделать из него .rpm, но самый универсальный способ — это скачать исходники с github.

    git clone https://github.com/yandex-load/yandex-tank.git
    


    Собирать сам Танк не нужно — он на Python, однако нужно будет скачать и установить зависимости. Среди них есть Phantom — это высокопроизводительный веб-сервер, который Танк использует в качестве срелялки, тоже родом из Яндекса. Рассказ о нем можно послушать тут.

    Необходимые python-библиотеки устанавливаются так:

    pip install ipaddr lxml progressbar psutil mysqldb sqlalchemy
    


    Кроме этого вам придется собрать из исходников Phantom. Не буду тут объяснять, как это сделать, кому нужно — пишите, расскажу.

    Пришло время пострелять


    Чтобы пострелять танком, нужно написать для него конфигурационный файл (я сегодня немного КО). Я не буду вдаваться в тонкости, которых много, приведу простейший пример:

    [phantom]
    address=example.org
    rps_schedule=line(1, 100, 10m)
    headers = [Host: example.org] [Connection: close] [Bloody: yes]
    uris=/
      /list
      /img
    


    После создания файла — просто запускаем танк командой yandex-tank. По умолчанию он ищет конфиг с именем load.ini в текущей директории. Оно пошуршит-пошуршит, постреляет, на выходе будет текстовый файл phout*.log с данными, который обычно советуют запихнуть в gnuplot. Но мы ведь не такие, правда?

    Ставим Graphite


    К сожалению, официального deb-пакета для Graphite на данный момент нет, поэтому ставить будем из репозитория Python (pypi):

    apt-get install python python-dev python-cairo
    pip install whisper carbon graphite-web django==1.5.1 Twisted==11.1.0 django-tagging
    


    После установки копируем дефолтную конфигурацию (*.conf.example -> *.conf), например, так:
    for file in /opt/graphite/conf/*.example; \
    do cp $file ${file%.*}; done
    


    По умолчанию Graphite хранит данные с разрешением в 1 минуту. Нам этого, конечно, мало, в нагрузочных тестах важна каждая секунда. Настраиваем политики хранения данных:

    [load]
    pattern = ^one_sec\.yandex_tank\.
    retentions = 1s:7d,5s:1y
    


    Что за древние письмена? Я попросил Graphite, чтобы все метрики, подпадающие под regexp, указанный в параметре pattern, он хранил в соответствии с политикой, указанной в параметре retentions:

    1s:7d, 5s:1y
    


    Тут все просто: секундная точность — семь дней, потом пятисекундная — в течение года.

    And one more thing. Нужно обязательно настроить временную зону на вашу локальную, иначе, указав локальное время, графиков вы не увидете — попросту промахнетесь мимо ваших данных. Временная зона указывается в файле local_settings.py, например, так (по умолчанию файла нет):

    echo TIME_ZONE = "Europe/Moscow" \
    > /opt/graphite/webapp/graphite/local_settings.py
    


    Теперь создадим таблички в django:

    cd /opt/graphite/webapp/graphite
    python manage.py syncdb
    


    Чтобы запустить Graphite, нужно стартовать хранилище carbon и веб-фронтенд:
    /opt/graphite/bin/carbon-cache.py start
    /opt/graphite/bin/run-graphite-devel-server.py /opt/graphite/
    


    Carbon по умолчанию ждет данных на 2003-м порту. Попробуем записать что-то в Graphite. Это очень просто, например:

    echo my.favourite.metric 1 $(date +%s) | nc -q0 localhost 2003
    


    Тут мы просто отправляем значение 1 с текущим таймстемпом в метрику «my.favourite.metric». А теперь зальем в Graphite содержимое /proc/vmstat (это уже юзабельно):

    while read -r metric; \
    do echo one_sec.vmstat.$metric $(date +%s); \
    done < /proc/vmstat \
    | nc -q0 localhost 2003
    


    И конечно же, для заливки данных о системных ресурсах уже придумали много инструментов. Взгляните, например, на проекты Diamondи CollectD.

    Посмотреть на залитые данные можно через веб-интерфейс, который по умолчанию слушает на порту 8080. Поиграйтесь с ним немного, а потом продолжим.



    Подключаем Танк к Графиту


    Ну что же! Пришло время подружить наших новых знакомых. Это тоже просто. Добавляем к конфигурационному файлу Танка вот такую секцию:

    [graphite]
    address=localhost
    


    Все, теперь снова можно стрелять и видеть наши результаты уже в Graphite. Кроме того, в папке с результатами теперь можно найти HTML-ку, которую Танк для нас заботливо сгенерил. В ней уже собраны графики и проставлены временные интервалы. Вот какие графики мы там видим:

    Квантили и среднее время ответа



    По графику квантилей можно видеть распределение времен ответа каждую секунду.

    Число запросов в секунду с разбивкой по маркерам



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

    Средние времена с разбивкой по маркерам



    Как сервер реагирует на разные типы запросов — ответ тут.

    Коды ответов



    Если возникнут ошибки — вы увидите их на этом графике.

    Кумулятивные квантили



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

    Шаблон отчета


    Все помнят, что в предыдущей секции мы обсуждали, как залить в Graphite данные о системных ресурсах? Как же увидеть и эти метрики тоже? Для генерации HTML-ки с картинками, Танк использует шаблон, который можно указать в опциях:

    [graphite]
    template = ./my.tpl
    


    Шаблон — это просто HTML-ка с переменными, которые Танк подменяет. Например:

    <h2>RPS by marker</h2>
    <img src="" />
    
    <h2>Average response time by marker</h2>
    <img src="" />
    
    <h2>HTTP codes</h2>
    <img src="" />
    


    Одна ссылка на график в Graphite выглядит так:

    http://{host}:{web_port}/render/?
    width={width}&
    height={height}&
    from={start_time}&
    until={end_time}&
    target=aliasByMetric({prefix}.overall.quantiles.25_0)&
    target=aliasByMetric({prefix}.overall.quantiles.50_0)&
    target=aliasByMetric({prefix}.overall.quantiles.75_0)&
    target=aliasByMetric({prefix}.overall.quantiles.90_0)&
    target=aliasByMetric({prefix}.overall.quantiles.95_0)&
    target=aliasByMetric({prefix}.overall.quantiles.99_0)&
    target=aliasByMetric({prefix}.overall.quantiles.100_0)&
    target=aliasByMetric({prefix}.overall.avg_response_time)&
    areaMode=all
    


    В фигурных скобках мы видим подменяемые поля, название которых говорит само за себя. Вместо {host} будет хост, указанный в настройках, вместо {start_time} и {end_time} — времена начала и конца стрельбы. Ну, вы поняли.

    Что в итоге?


    Итак, мы получили стрелялку, которая заливает данные в Graphite и генерит HTML-ку со ссылками на эти данные. Как теперь запускать стрельбы автоматически? По cron? Можно и так. Но удобнее использовать Jenkins. Об этом как-нибудь в следующий раз. Stay tuned!

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

    Ссылки


    tech.yandex.ru/events/yac/2013/talks/1122 — dkulikovsky@ о Graphite
    github.com/graphite-project/graphite-web — Graphite на github
    github.com/yandex-load/yandex-tank — Yandex Tank на github
    github.com/BrightcoveOS/Diamond — Diamond
    collectd.org — CollectD
    twitter.com/direvius — Follow me on Twitter
    Яндекс 597,52
    Как мы делаем Яндекс
    Поделиться публикацией
    Комментарии 11
    • +2
      Яндекс.Танк в Graphite умеет отправлять данные о мониторинге, которые настраиваются в load.ini?
      • +3
        Нет, не умеет. Работаю над этим.
        • 0
          Есть новости?
          • 0
            Есть.
            Мы научили танк генерить локальные отчеты в виде html с графиками на базе highcharts. Данные лежат прямо внутри этих html-ек, включая данные мониторинга — при их использовании в графите нет необходимости.
            Я думаю, что это круче, чем заливка в графит, поэтому модулем графита сейчас не занимаюсь (однако, графитовые отчеты теперь тоже с highcharts).
            Если вам прямо очень нужны мониторинги в графите — воспользуйтесь Diamond и кастомным шаблоном.
            Или опишите ваш кейс, возможно, я не прав, что не стоит заниматься графитом.
      • 0
        Нет, не умеет. Я работаю над этим. промахнулся веткой 8(
        • 0
          Возможно как-то обойти запрос на reverse dns?
          Пример — тестовые площадки с virtualost'ами.
          • 0
            прямой IP подойдет?
            • +1
              Нет, просто IP не подходит. Если только можно передать какой-нибудь заголовок.
              Суть в нагрузке виртуалхоста. Если можно как-то передать заголовок дополнительный — тоже пойдет. Но лучше флаг для отключение rev-dns запроса.
              Пока что танк может стрелять по замку. А мне надо по башенкам :)
              • 0
                хм. ну вот совершенно точно вы можете использовать заголовок. Например так
                [phantom]
                address=example.com
                rps_schedule = line(1,10,1m)
                uris = /
                headers = [Host: my.example.com] [Connection: close]

                подойдет?
                Про флаг — можно открыть обсуждение в ярушке. Сделать?
                • +1
                  Спасибо, я совсем проглядел секцию headers в конфиге :(
                  Host отлично подходит для этих целей.

                • 0
                  Или я туплю совсем, вечер.

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

            Самое читаемое