с нашим главным инженером Джейми Макдональд.

В рамках нашей регулярной серии Tech Talks Джейми описывает использование Nxдля сокращения времени сборки и тестирования CI.

Перебазируйте, подождите, повторите

По мере того, как команда инженеров в Phlo расширяется, увеличивается и количество Pull Requests (PR), которые мы открываем. Нам нравится использовать PR, чтобы дать нашим инженерам возможность обратиться за отзывами, для тестирования в развернутой среде или даже для тестирования наших последних изменений React Native. Растущее количество PR в сочетании с растущей платформой привело к увеличению объема работы, которую должны выполнить наши конвейеры непрерывной интеграции (CI). Поскольку на сборку регулярно уходит 15 минут, это может разочаровать разработчиков, которые хотят объединить свои изменения и перейти к следующей задаче.

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

Что такое Нх?

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

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

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

Nx продвигает кэширование на шаг вперед, позволяя вам размещать общий кэш в облаке (распределенное кэширование). Это означает, что когда код был создан на машине одного человека, его больше не нужно создавать на машине другого — при условии той же версии кода и той же конфигурации машины. Это позволяет вам значительно сэкономить время на вашем CI-сервере и в вашей команде.

Начальная конфигурация

Проблема, на которой мы хотели сосредоточиться изначально, заключалась в сокращении времени сборки. Мы хотели, чтобы это было чем-то, что мы могли бы получить как локально на машине каждого инженера, так и в наших конвейерах CI. Мы начали с запуска удобной команды Nx для добавления в существующий монорепозиторий. Это включало простой запуск команды commandnpx cra-to-nx, которая добавляла все необходимые зависимости в наш файл package.json. Эта команда также создала файл nx.json в корне проекта, этот файл используется для объявления некоторой конфигурации для Nx. См. скриншот ниже для фрагмента нашей конфигурации.

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

С файлом project.json для каждого из наших пакетов наша начальная конфигурация была завершена.

Улучшение времени сборки

После того, как наша конфигурация была убрана, нашим следующим шагом был запуск сборки с использованием Nx. Поскольку мы хотели запустить команду сборки для всех наших пакетов, мы использовали команду nx run-many. Для нас команда выглядела так: yarn nx run-many — target=build — all. Первоначальный запуск использовался для создания кеша, что заняло примерно столько же времени, что и наш существующий шаг сборки. Однако во второй сборке этап сборки был почти незамедлительным. Nx не обнаружил никаких изменений, поэтому просто использовал уже существующую кешированную сборку.

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

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

Улучшения времени тестирования

Теперь, когда время сборки улучшилось за счет кэширования, мы хотели расширить это, чтобы также сократить время запуска нашего набора тестов. Это было достигнуто аналогично нашим улучшениям времени сборки. Во-первых, мы должны были убедиться, что у каждого из пакетов есть запись в файле package.json с подробным описанием того, какую команду запускать для своих тестов. Как только это было сделано, нам просто нужно было расширить файлы project.json, которые мы создали ранее, чтобы включить запись для тестов. Вы можете увидеть пример ниже.

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

Улучшение времени конвейера CI

Теперь у нас была сборка и тестовые прогоны с локальным использованием кеша, и мы хотели использовать его на наших конвейерах. Используемые нами конвейеры — это конвейеры Microsoft Hosted в Azure DevOps. Это означает, что у нас не может быть постоянного кеша между запусками, поскольку каждый раз, когда запускаются конвейеры, используется чистая виртуальная машина. Распределенное кэширование Nx стало ответом на эту проблему.

Поскольку мы уже используем Google Cloud Platform для размещения нашей инфраструктуры, а также используем Google Cloud Storage (GCS), мы решили попробовать использовать плагин для Nx, который позволяет вам использовать корзину GCS для размещения вашего кеша. В итоге мы использовали пакет nx-gcs-remote-cache, это позволяет нам просто указать нашей конфигурации Nx использование пользовательского запуска, что вы можете видеть на снимке экрана ниже.

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

Успех

Теперь, когда все готово, мы хотели протестировать наши изменения в CI Pipelines. Мы создали PR с изменениями Nx и позволили завершить начальный запуск, чтобы мы могли создать кеш. Как и ожидалось, это заняло около наших обычных ~ 15 минут. Затем мы снова приступили к этапам сборки и тестирования. Без изменений и с полным использованием кеша Nx мы обнаружили, что наши прогоны CI теперь занимают ‹5 минут. Это огромная экономия времени для нас, а это означает, что вместо нашего текущего процесса, при котором 1 PR можно было построить и объединить в течение 15 минут, теперь мы могли создать и объединить 3 PR за то же время. Это означает, что когда инженеры размещают PR, они знают, что их PR будут объединены гораздо раньше, и вместо этого они могут сосредоточиться на выполнении следующей интересной работы.

Следующие шаги

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

Пробовали ли вы перейти на дистрибутив или нашли другие способы сократить время конвейера CI?