Как команда по решениям в Stuart выполняет автоматическое тестирование

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

Прежде чем перейти к нашим рекомендациям, мы расскажем, кто мы и что пытаемся сделать.

👋 Контекст: команда по разработке решений

В январе 2020 года мы создали новую команду в Stuart Tech: команду инженеров по решениям.

Цель команды - преодолеть разрыв, существующий между бизнесом и продуктами, которые у нас есть в Stuart. Если бизнес-группа хочет опробовать новый бизнес-сценарий использования, они обращаются за поддержкой к команде инженеров по новым решениям. Затем мы создаем решения, чтобы разблокировать их потребности за короткий период времени.

Другими словами: мы стартап внутри стартапа!

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

  • Node JS и Express JS (с использованием Typescript) для бэкэнда;
  • React JS для интерфейса;
  • NPM как менеджер пакетов;
  • Шутка для тестов;
  • ESlint как линтер;
  • Дженкинс как CI.

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

🤔 Проблема

Шут - пожиратель ресурсов. Неважно, есть ли у вас 8 ГБ, 16 ГБ или 32 ГБ ОЗУ. Jest будет использовать все это. Как насчет процессора? Точно так же. Два ядра? Вкуснятина! Восемь ядер? Юууумми! 😋

Это довольно интересная ситуация. Мы очень привыкли работать с программным обеспечением, которое крайне плохо оптимизировано. Настолько плохо, что большинство из них обычно использует один процессор и всего несколько МБ или ГБ ОЗУ, потому что однопоточное приложение действует как узкое место. Но со шуткой дело обстоит иначе. Jest адаптируется к вашим максимальным ресурсам и истощает их все.

Вот почему во всем Интернете вы можете очень легко найти рекомендации по принудительному запуску Jest в одном потоке или максимум на 50% ваших процессоров.

И, к счастью, вы можете сделать это довольно просто, задав параметры в Jest, такие как maxWorkers. Этого будет достаточно для разработки. Но ... Как насчет того, чтобы запустить его в CI-инструменте, таком как Jenkins? Если на каждый узел Jenkins доступно 4 воркера, то 4 задания Jenkins могут выполняться параллельно.

В сценарии выполнения 4 заданий параллельно с Jest от maxWorkers до 1 вы получите 4 процессора, потребляющих 100% (не совсем так, но для цель этого примера достаточно близка!).

С другой стороны, вы не можете контролировать использование Jest RAM. При необходимости он будет использовать всю вашу оперативную память. Итак, опять же, если у вас 16 ГБ ОЗУ, ваши индивидуальные исполнения Jest, выполняемые на том же узле Jenkins, будут пытаться потреблять 16 ГБ каждое!

Из-за высокой загрузки ЦП и ОЗУ демоны Jenkins не смогут взаимодействовать с узлом контроллера, и все выполнение Jenkins завершится ошибкой.

😱 Нам нужно, чтобы Jest не убивал наши узлы!

👩‍🏫 Наш подход

Наши машины Jenkins - это машины емкостью 15 ГБ, с 4 рабочими на узел Jenkins. В результате мы можем иметь до 4-х команд Jest, работающих одновременно на одной машине.

Ограничение ЦП

Благодаря Jest это очень просто. Просто используйте:

--maxWorkers = 20%

Вы можете спросить, почему 20%? Простая математика: 4 (рабочие Jenkins) * 20% ЦП = 80%. Мы оставим 20% для системы (чтобы демон Jenkins все еще мог отвечать, ОС и т. Д.).

Ограничение ОЗУ

Вот самая большая проблема. Jest не позволяет установить максимум ОЗУ. Что еще хуже, Jest не запустит какой-либо процесс сборки мусора, если у него не закончится оперативная память или вы заставите его сделать это. Это означает, что использование ОЗУ будет увеличиваться, увеличиваться и увеличиваться - до момента, когда запускается сборщик мусора. Если этого недостаточно для освобождения памяти (: cough: leaks: cough :), начинается подкачка. К тому времени тесты идут очень-очень медленно.

Чтобы ограничить объем оперативной памяти, мы выбрали подход Docker. Для каждого набора тестов, который мы хотим запустить, мы запускаем образ Docker и запускаем его, ограничивая его ресурсы на основе ресурсов хоста.

Учитывая, что наши узлы Jenkins имеют 4 рабочих, нам нужно ограничить ресурсы на основе максимально доступной оперативной памяти, которая в нашем случае составляет 15 ГБ.

Нам нужно понимать, что ОЗУ ведет себя немного иначе, чем ЦП. Для полного использования ОЗУ требуется время, поэтому в этом случае мы можем немного более агрессивно распределять ресурсы. В нашем случае мы решили установить лимит Docker на 4,3 ГБ. 4,3 ГБ * 4 = 17,2 ГБ - немного выше лимита ОЗУ.

Вы можете спросить, как можно ограничить оперативную память в Docker?

docker run -m 4300m tests_image

Обе вещи вместе

Если вы действительно хотите убедиться, что Jest не истощает все ваши ЦП, вы также можете воспользоваться ограничениями ЦП Docker. У нашего хоста 8 логических процессоров. Таким образом, чтобы соответствовать 20% -ному ограничению ЦП, которое мы установили для команды Jest, мы установим ограничение ЦП для каждого контейнера Docker, чтобы оно потребляло до 6,5 логических ЦП.

Опять же, поскольку мы будем запускать не более 4 докеров параллельно в узле Jenkins, это примерно 1,5 процессора.

docker run --cpus = ’1.5’ tests_image

И это в сочетании с настройкой RAM становится:

docker run -m 4300m --cpus = ’1.5’

🏇 Ускоряет ли это тесты?

По-разному…

Самой большой проблемой для нас было то, что наши агенты Jenkins умирали из-за высокой загрузки ЦП и ОЗУ. Благодаря такому подходу мы решили эту проблему.

Это действительно увеличивает скорость для небольших наборов тестов. Для тех, кто в конечном итоге потребляет около 4 ГБ ОЗУ, тесты проходят медленно. Это не имеет большого значения, потому что благодаря нашей настройке Jenkins, созданной нашей командой DevOps, узлы Jenkins масштабируются автоматически. Используя конвейеры Jenkins, мы можем просто распараллелить столько тестов, сколько захотим, для разных заданий Jenkins.

🚰 Работа с утечками памяти

Чтобы повысить производительность тестов и убедиться, что они не замедляются из-за чрезмерного потребления оперативной памяти, мы не рекомендуем запускать все тесты в одной команде Jest.

Вместо этого разделите тесты на несколько выполнений. В нашем случае у нас есть монорепозиторий с более чем 10 приложениями. Мы разделили эти 10 наборов на 5 различных исполнений Jest. Перезапустив Jest, вы убедитесь, что начинаете с 0, а не с предыдущими утечками, влияющими на ваши текущие тесты.

🐌 Все еще слишком медленно?

Может случиться так, что вскоре после запуска тестов они уже будут работать медленно. Тогда весьма вероятно, что вы уже достигли максимально допустимого объема ОЗУ. Чтобы подтвердить это, запустите «docker stats». Он покажет текущее использование памяти и его предел.

Если использование достигает 100% очень быстро - если вы не обнаружите большую утечку памяти и не сумеете ее устранить, - единственное решение - увеличить ОЗУ докера: запустите набор тестов, не устанавливая никаких ограничений докера, и при мониторинге с помощью статистики докера , запишите самый высокий пик.

Если пик превышает объем ОЗУ, который вы установили для контейнера Docker, вам следует увеличить лимиты ОЗУ в контейнере Docker, но также убедитесь, что математика по-прежнему имеет смысл. Если бы нам пришлось дополнительно увеличить объем оперативной памяти для наших тестов, у нас было бы два варианта:

  1. Увеличьте оперативную память узлов с 15 ГБ до 32 ГБ.
  2. Уменьшите количество рабочих Jenkins с 4 до 3.

🏁 Выводы

Мы потратили пару недель на метод проб и ошибок, пока не нашли идеальные цифры для нашей инфраструктуры. Не следует ожидать копирования и вставки какой-либо конфигурации. Вы должны понимать свою инфраструктуру и адаптироваться к ней.

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

Хотите увидеть больше? Мы нанимаем! 🚀 Ознакомьтесь с нашими открытыми вакансиями.