company_banner

Тестирование в Яндексе. Как сделать отказоустойчивый грид из тысячи браузеров

    Любой специалист, причастный к тестированию веб-приложений, знает, что большинство рутинных действий на сервисах умеет делать фреймворк Selenium. В Яндексе в день выполняются миллионы автотестов, использующих Selenium для работы с браузерами, поэтому нам нужны тысячи различных браузеров, доступных одновременно и 24/7. И вот тут начинается самое интересное.



    Selenium с большим количеством браузеров имеет много проблем с масштабированием и отказоустойчивостью. После нескольких попыток у нас получилось элегантное и простое в обслуживании решение, и мы хотим поделиться им с вами. Наш проект gridrouter позволяет организовать отказоустойчивый Selenium-грид из любого количества браузеров. Код выложен в open-source и доступен на Github. Под катом я расскажу, на какие недостатки Selenium мы обращали внимание, как пришли к нашему решению, и объясню, как его настроить.

    Проблема


    Selenium с момента своего создания не раз кардинально менялся, текущая архитектура, называющаяся Selenium Grid, работает так.

    Кластер состоит из двух приложений: хаба (hub) и ноды (node). Хаб – это API, принимающее запросы пользователей и отправляющее их на ноды. Нода – исполнитель запросов, запускающий браузеры и выполняющий в них шаги теста. К одному хабу может быть теоретически подключено бесконечное число нод, каждая из которых умеет запускать любой из поддерживаемых браузеров. А что же на практике?

    • Есть уязвимое место. Хаб – это единственная точка доступа к браузерам. Если по каким-то причинам процесс хаба перестает отвечать, то все браузеры становятся недоступны. Ясно, что сервис также перестает работать, если у дата-центра, где стоит хаб, происходит отказ по сети или питанию.
    • Selenium Grid плохо масштабируется. Наш многолетний опыт эксплуатации Selenium на разном оборудовании показывает, что под нагрузкой один хаб способен работать не более чем с несколькими десятками подключенных нод. Если продолжать добавлять ноды, то при пиковой нагрузке хаб может перестать отвечать по сети или обрабатывает запросы слишком медленно.
    • Нет квотирования. Нельзя создать пользователей и указать, какие версии браузеров какой пользователь может использовать.

    Решение


    Чтобы не страдать при падении одного хаба, можно поднять несколько. Но обычные библиотеки для работы с Selenium рассчитаны на работу только с одним хабом, поэтому придется научить их работать с распределенной системой.

    Балансировка на клиенте


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

    Вот как это работает:

    1. Информация о хостах с хабами и доступных на них версиях браузеров сохраняется в файл конфигурации.
    2. Пользователь подключает библиотеку в свои тесты и запрашивает браузер.
    3. Из списка случайным образом выбирается хост и делается попытка получить браузер.
    4. Если попытка удачная, то браузер отдается пользователю и начинаются тесты.
    5. Если браузер не удалось получить, то опять случайно выбирается следующий хост и т. д. Поскольку разные хабы могут иметь разное количество доступных браузеров, хабам в файле конфигурации можно назначить разные веса, и случайная выборка делается с учетом этих весов. Такой подход позволяет добиться равномерного распределения нагрузки.
    6. Пользователь получает ошибку только в том случае, если браузер не удалось получить ни на одном из хабов.

    Реализация такого алгоритма несложная, но требует интеграции с каждой библиотекой для работы с Selenium. Допустим, в ваших тестах браузер получается таким кодом:

    WebDriver driver = new RemoteWebDriver(SELENIUM_SERVER_URL, capabilities);

    Здесь RemoteWebDriver – это стандартный класс для работы с Selenium на Java. Для работы в нашей инфраструктуре придется обернуть его в наш собственный код с выбором хаба:

    WebDriver driver = SeleniumHubFinder.find(capabilities);

    В коде тестов больше нет URL до Selenium, он содержится в конфигурации библиотеки. Также это значит, что код тестов теперь привязан к SeleniumHubFinder и без него не запустится. Кроме того, если у вас есть тесты не только на Java, но и на других языках, то придется писать клиентский балансировщик для них всех, а это может быть затратно. Гораздо проще вынести код балансировки на сервер и указать его адрес в коде тестов.

    Балансировка на сервере


    При проектировании сервера мы заложили следующие естественные требования:

    1. Сервер должен реализовывать API Selenium (протокол JsonWire), чтобы тесты работали с ним, как с обычным Selenium-хабом.
    2. Можно расставить сколько угодно голов сервера в любых дата-центрах и забалансировать их железным или программным балансировщиком (SLB).
    3. Головы сервера совершенно независимы друг от друга и не хранят общее состояние (shared state).
    4. Сервер из коробки обеспечивает квотирование, то есть независимую работу нескольких пользователей.



    Архитектурно полученное решение выглядит так:

    • Балансировщик нагрузки (SLB) раскидывает запросы от пользователей на одну из N голов с сервером, слушающих на стандартном порту (4444).
    • Каждая из голов хранит в виде конфигурации информацию обо всех имеющихся Selenium-хабах.
    • При поступлении запроса на браузер сервер использует алгоритм балансировки, описанный в предыдущем разделе, и получает браузер.
    • Каждый запущенный браузер в стандартном Selenium получает свой уникальный идентификатор, называемый ID сессии. Это значение передается клиентом хабу при любом запросе. При получении браузера сервер подменяет настоящий ID сессии на новый, дополнительно содержащий информацию о хабе, на котором была получена данная сессия. Полученная сессия с расширенным ID отдается клиенту.
    • При следующих запросах сервер извлекает адрес хоста с хабом из ID сессии и проксирует запросы на этот хост. Поскольку вся нужная серверу информация есть в самом запросе, не надо синхронизировать состояние голов – каждая из них может работать независимо.

    Gridrouter


    Сервер мы назвали gridrouter и решили поделиться его кодом со всеми. Сервер написан на Java с использованием Spring Framework. Исходники проекта можно посмотреть по ссылке. Мы также подготовили Debian-пакеты, устанавливающие сервер.

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

    Как настраивать gridrouter


    Для того чтобы настроить gridouter, нужно задать список пользователей и квоты для каждого пользователя. Мы не ставили цель сделать супербезопасную аутентификацию с хэш-функциями и солью, поэтому используем обычную basic HTTP-аутентификацию, а логины и пароли храним в открытом виде в текстовом файле /etc/grid-router/users.properties вида:

    user:password, user
    user2:password2, user
    

    Каждая строчка содержит логин и пароль через двоеточие, а также роль, которая на данный момент одна и та же, – user. Что касается квот, то здесь все тоже очень просто. Каждая квота представляет собой отдельный XML-файл /etc/grid-router/quota/<login>.xml, где <login> – имя пользователя. Внутри файл выглядит так:

    <qa:browsers xmlns:qa="urn:config.gridrouter.qatools.ru">
        <browser name="firefox" defaultVersion="33.0">
            <version number="33.0">
                <region name="us-west">
                    <host name="my-firefox33-host-1.example.com" port="4444" count="5"/>
                </region>
                <region name="us-east">
                    <host name="my-firefox33-host-2.example.com" port="4444" count="5"/>
                </region>
            </version>
            <version number="38.0">
                <region name="us-west">
                    <host name="my-firefox38-host-1.example.com" port="4444" count="4"/>
                    <host name="my-firefox38-host-2.example.com" port="4444" count="4"/>
                </region>
                <region name="us-east">
                    <host name="my-firefox38-host-3.example.com" port="4444" count="4"/>
                </region>
            </version>
        </browser>
        <browser name="chrome" defaultVersion="42.0">
            <version number="42.0">
                <region name="us-west">
                    <host name="my-chrome42-host-1.example.com" port="4444" count="1"/>
                </region>
                <region name="us-east">
                    <host name="my-chrome42-host-2.example.com" port="4444" count="4"/>
                    <host name="my-chrome42-host-3.example.com" port="4444" count="3"/>
                </region>
            </version>
        </browser>
    </qa:browsers>
    

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

    Как запустить тесты


    Хотя в основном мы пишем на Java, мы проверяли наш сервер с Selenium тестами на других языках программирования. Обычно в тестах URL хаба указывается примерно так:

    http://example.com:4444/wd/hub
    

    Поскольку мы используем basic HTTP-аутентификацию, при работе с gridrouter следует использовать такие ссылки:

    http://username:password@example.com:4444/wd/hub
    

    Если у вас возникнут проблемы с настройкой, обращайтесь к нам, заводите issue на Github.

    Рекомендации по настройке хабов и нод


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



    При использовании gridrouter можно поставить сколько угодно хабов, поэтому проще всего настроить на одной машине один хаб и несколько нод, подключенных к localhost:4444. Особенно удобно так делать, если все разворачивается на виртуальных машинах. Например, мы выяснили, что для виртуальной машины с двумя VCPU и 4 Гб памяти оптимальным является сочетание хаба и пяти нод. На одну виртуальную машину мы устанавливаем только одну версию браузера, поскольку в этом случае легко измерять потребление памяти и переводить число виртуальных машин в число имеющихся браузеров.

    Яндекс

    757,00

    Как мы делаем Яндекс

    Поделиться публикацией
    Комментарии 31
      +7
      А какой такой «тяжелой» нагрузкой занимается хаб, что он может обслуживать насколько мало нод?
        0
        Нагрузка на хабы у нас более менее обычная: открытие страниц, выполнение действий в текстовых полях, снятие скриншотов и т.п. Тем не менее десятки нод на одном хабе приводят к ощутимым тормозам, даже на мощном железе.
          +5
          Ваш ответ ничего не объясняет. Я понимаю в чем состоит нагрузка на ноду — она занимается парсингом страниц, рендерингом в браузере и обработкой всяких яваскриптов. Но я не понимаю чем занят хаб. Интуитивно кажется, что он должен быть I/O-bound, но вы утверждаете, что он упирается в CPU. Что кажется неправильным и могло бы быть исправлено путем профилирования и устранения бессмысленных занятий, пожирающих проц на хабе.
            0
            Мне кажется Selenium не сильно оптимизирован, поэтому собственно такая история.
          +2
          Тут на самом деле несколько аспектов
          1. Хаб так или иначе держит таблицу доступных нод и постоянно обменивается с ними данными, пишет логи итп. (видимо действительно что-то из этого он делает неоптимально). Наш прокси, при этом, крайне легковесный, и его реализация изначально позволяет бесконечно масштабироваться за балансером.
          2. При этом, как показали наши опыты, действительно есть предел производительности хаба, который, видимо, пропорционален железу. На небольших машинках в облаке 2хядерный хаб плохо держит более 20 нод. (ранее у нас была клиентская балансировка и хабы стояли на железе, тогда было просто 16 хабов и отдельные ноды)
          3. Мы не изучали подробно внутреннюю архитектуру хаба, тк на данном этапе переписывать и оптимизировать хаб своими силами не входило в наши планы. Держать свою кастомную реализацию хаба мы не хотели тем более.
          4. При этом одной из задач, которую мы решали, являлась задача повышения надежности. Один хаб, выходя из строя, тащит за собой весь грид. Два — половину.
          5. Организовав конфигурацию с большим количеством машин, содержащих хаб и 5 изолированных нод, мы решили сразу три задачи — строго лимитировали нагрузку на один хаб, убрали фактор хаба как точки отказа и при этом остались в рамках стандартных непатченных selenium артефактов.
            +1
            На сколько я помню сам selenium-server содержит код как хаба, так и ноды (собственно роль банально задается через флаг role). Что наталкивает на мысль о том, что в принципе имеем в итоге большой комбайн который вроде как умеет все. Причины видимо исторические.
            –13
            То-то в я браузере вкладки падают и падают. Уже смотрю в сторону вивальди, а то задолбало наблюдать сие
              +4
              В техподдержку писать не пробовали? Мне вот например отвечали и помогали.
                –3
                мне тоже, только проблемы были не мои, а браузера. А тут на их новомодной почте (интерфейс) как начало падать пару дне назад, что пришлось вернуть классический интерфейс ну и заодно начать вивальди для себя готовить, за которым слежу с первого билда. Нет, и не собираюсь. Ибо в данном случае, это ни к чему не приведет. Проблема того что внезапно разные вкладки само собой падают в такие сообщения имеется давно, а воз и ныне там.
                  +3
                  Хм… Вот у меня тоже новый интерфейс почты, и бета версия браузера — и пока ни разу не падало на ней. Вообще если они просто падают, но никто не сообщает в техподдержку — то чинить будут долго — анализ логов обычно имеет меньший приоритет, чем обращения в техподдержку. И далеко не всегда по логам можно понять сценарий воспроизведения. Но вот чем больше обращений в техподдержку — тем выше шанс, что проблеме повысят приоритет.
                    0
                    У вас много табов открыто?
                      0
                      В среднем — от десятка до двух, но в некоторые моменты бывало 3-4 десятка
                        0
                        может дело в этом. у меня не опускается ниже 2ух десятков.
                +1
                Отписал лицам, отвечающим за разработку Яндекс.Браузера. В ближайшее время они отреагируют на ваш комментарий.
                  +2
                  Напишите, пожалуйста, через «Сообщить о проблеме» в меню браузера (в этом случае передается еще техническая информация о компьютере). Желательно описать сценарии, когда падения происходили.
                    0
                    Я уверен — виноват именно роутер из статьи, поэтому лучшего места для этот репорта не найти
                    0
                    Я не понял вот эту фразу:
                    мы выяснили, что для виртуальной машины с двумя VCPU и 4 Гб памяти оптимальным является сочетание хаба и пяти нод

                    Я всегда думал, что каждая нода ставится на отдельную машину. А вы тут предлагаете на одну машину ставить 5 нод. В чём смысл?
                      0
                      Поскольку мы работаем с большими объемами браузеров, то довольно важный вопрос — об эффективности использования железа. Типичный железный сервер имеет 24 и более ядра и больше 50 Гб памяти. Для того, чтобы не думать об аппаратном уровне мы используем облако. Для экономии ресурсов и обеспечения изоляции браузеров на одной виртуальной машине мы поднимаем несколько нод в разных виртуальных рабочих столах по 1 браузеру в каждой. Если этого не делать, то, например, начинаются проблемы с пропаданием фокуса на окнах браузера.
                        0
                        На одной виртуальной машине живет только какая-то одна версия браузера (допустим там FireFox последней версии), так? И если нужен тест на более старой мажорной версии, то её нужно искать уже на другой ноде?
                          +1
                          Selenium позволяет держать разные мажорные версии браузера на одной ноде, но с точки зрения администрирования делать так — одна большая проблема. Поэтому оказалось удобным ставить разные мажорные версии браузера на разные виртуальные машины: легче настраивать, легче переводить железо в браузеры и так далее. С точки зрения тестов ничего не меняется — указывается название браузера и версия, а gridrouter сам знает (это прописано в XML конфигурации) на какую машину пойти, чтобы отдать нужный браузер.
                          0
                          А что используете для виртуальных столов?

                          Desktops v2.0 от Microsoft?
                          technet.microsoft.com/en-us/sysinternals/cc817881.aspx

                          Или что-то ешё?
                            0
                            Под Linux: xvfb, под Windows — desktop-utils.exe.
                              0
                              А под linux что-то другое были попытки использовать?
                                +1
                                Например? Поскольку мы запускаем все на серверах без монитора, то xvfb — напрашивающееся решение. Обычный X сервер требует наличия монитора.
                        0
                        Уважаемый, пока ещё, Яндекс! А вы расскажете, как на свет вывалился гениальный продукт — обновленный интерфейс Кинопоиска?
                          0
                          К сожалению, я не владею информацией по данному вопросу, но уверен, что реакция последует.
                          0
                          Кинопоиск верните.
                          0
                          А тестирование на OS X сюда входит? Как и чем решаете эту задачу?
                            +1
                            Извините за долгий ответ. Мы используем Gridrouter с OS X аналогично остальным браузерам, только вместо стандартной Selenium ноды используем Appium и Xcode, который тащит за собой нужные симуляторы и SDK. К сожалению по лицензионным соображениям нельзя устанавливать MacOS не на виртуальные машины в облаке, только на железки, на каждую из которых можно поставить не больше 2 параллельно работающих операционных систем. Это сильно ограничивает масштабирование решения.

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

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