Тестування в Яндексі. Як зробити відмовостійкий грід з тисячі браузерів

Будь-який фахівець, причетний до тестування веб-додатків, знає, що більшість рутинних дій на сервісах вміє робити фреймворк 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.ua">
<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 Гб пам'яті оптимальним є поєднання хаба і п'яти нсд. На одну віртуальну машину ми встановлюємо тільки одну версію браузера, оскільки в цьому випадку легко вимірювати споживання пам'яті і переводити число віртуальних машин в число наявних браузерів.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.