Вы когда-нибудь задумывались, почему вашему конвейеру Azure требуется так много времени, чтобы завершить то, что он должен сделать? Можно ли это как-то улучшить?
В этой статье я постараюсь указать на очевидную причину медлительности наших конвейеров — выполнение скриптов на основе экосистемы npm, опишу, какое решение предлагает Azure Pipelines для решения этой проблемы и как оно работает, а также как выглядит реализация.
Конвейеры в Azure
Конвейеры Azure состоят из этапов, каждый из которых выполняет задания, разделенные на шаги, которые запускают ваши скрипты или задачи. Эти задания выполняются поверх агента, который, по сути, является вычислительной инфраструктурой (можно представить его как образ с предустановленным программным обеспечением, необходимым для выполнения ваших сценариев).
Эти агенты разворачиваются в идеальное состояние. Да, это основы предсказуемой среды CI. Проблема в том, что ваш скрипт каждый раз делает все с нуля, что, вероятно, является большой тратой времени. Возможно, мы можем сэкономить несколько секунд или больше.
Экосистема Npm
В нашей экосистеме npm мы полагаемся на основы модулей node в качестве наших зависимостей. В нашем процессе CI мы будем устанавливать зависимости предсказуемым образом, что дает нам большую уверенность в том, что среда CI всегда будет одинаковой для заданных входных данных (в основном, нашего файла блокировки), поэтому мы можем тщательно протестировать ее и, наконец, безопасно развернуть или опубликовать наш артефакт.
Этот процесс установки, наряду с процессом сборки, может занимать значительное количество времени по мере роста и развития нашего приложения. Поэтому наш конвейер может занять несколько минут. Почему? — Проблема кроется в природе экосистемы CI; этот процесс повторяется снова и снова при каждом запуске нашего конвейера. К счастью, системы CI и, в частности, Azure Pipelines позволяют нам потенциально улучшить этот аспект, используя механизм кэширования.
Кэширование зависимостей с помощью задачи Cache
Следующий пример демонстрирует, как использовать задачу Azure Pipeline Cache для кэширования зависимостей yarn.
Процесс кэширования зависимостей npm такой же, как и при использовании папки package-lock.json и .npm cache.
Вот краткое описание того, как работает процесс кэширования:
- Использование задачи Yarn для установки папки кэша Yarn в выбранное нами место.
- task: Yarn@3
inputs:
arguments: 'config set cache-folder $(yarnCacheFolder)'
displayName: Set Yarn cache folder
- Использование задачи cache для создания кэша с ключом на основе статического описания ‘yarn’, идентификатора операционной системы агента и содержимого файла блокировки yarn. Azure Pipelines фактически создает хэш на основе содержимого этого файла, и при запуске конвейера он будет проверять попадание в кэш с этим ключом. Во время выполнения, если ваш агент работает под управлением Windows, это будет выглядеть примерно так — yarn|»Windows_NT»|FupyH86xxxxxxxtBrhv+2fiXTRb/6Dew= .
- task: Cache@2
inputs:
key: 'yarn | "$(Agent.OS)" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
yarn
path: $(yarnCacheFolder)
displayName: Cache dependencies
- При первом запуске происходит пропуск кэша с нашим ключом и кэш создается в первый раз.
Getting a pipeline cache artifact with one of the following fingerprints:
Fingerprint: `yarn|"Windows_NT"|FupyH86xxxxxxxxxtBrhv+2fiXTRb/6Dew=`
Fingerprint: `yarn|"Windows_NT"|**`
Fingerprint: `yarn|**`
There is a cache miss.
На этом этапе вы можете заметить, что дополнительный шаг Post-Job был динамически добавлен к конвейеру. Этот шаг используется для фактического создания элемента кэша. Процесс печатается в журнале конвейера:
Creating a pipeline cache artifact with the following fingerprint: `yarn|"Windows_NT"|FupyH86xxxxxxxxxtBrhv+2fiXTRb/6Dew=`
Cache item created.
- При втором запуске, при условии отсутствия изменений в файле блокировки и конфигурации нашего агента, произойдет попадание в кэш, и зависимости будут извлечены из ранее созданного кэша вместо того, чтобы разрешить их из реестра. Это не означает, что это произойдет мгновенно, но это должно занять меньше времени, чем выполнение всего процесса с нуля. В журнале конвейера можно увидеть, что кэш загружается из хранилища Azure до того, как он будет использован.
There is a cache hit: `yarn|"Windows_NT"|FupyH86xxxxxxxxxtBrhv+2fiXTRb/6Dew=`
Used scope: 30;7375ab34-xxxx-xxxx-8ef7-382c644dca33;refs/heads/xxxxx;
Entry found at fingerprint: `yarn|"Windows_NT"|FupyH86xxxxxxxxxtBrhv+2fiXTRb/6Dew=`
Expected size to be downloaded: 100.0 MB
Downloaded 0.0 MB out of 100.0 MB (0%).
7-Zip 21.07 (x64) : Copyright (c) 1999-2021 Igor Pavlov : 2021-12-26
Extracting archive:
Downloaded 12 MB out of 100.0 MB (12%).
...
Если кэш пропущен, он попытается вернуться к одному из ключей восстановления.
Вот полный пример нашего файла .yml:
variables:
npmRegistry: 'xxxx-xxxx-xxxx-xxxx' #Your custom registry ID
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
steps:
- task: Yarn@3
inputs:
arguments: 'config set cache-folder $(yarnCacheFolder)'
displayName: Set Yarn cache folder
- task: Cache@2
inputs:
key: 'yarn | "$(Agent.OS)" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
yarn
path: $(yarnCacheFolder)
displayName: Cache dependencies
- task: Yarn@3
displayName: 'Install dependencies'
inputs:
arguments: '--frozen-lockfile'
customRegistry: useFeed
customFeed: '$(npmRegistry)'
Заключение
Использование функции Azure Pipeline Caching может сэкономить нам много времени. Вот пример производительности конвейера до применения задачи кэширования. Видно, что среднее время выполнения, мягко говоря, не очень хорошее, и колеблется в районе 10 минут, а иногда и больше — в зависимости от доступности агента, других аспектов работы сети, CI и экосистемы npm. Очевидно, что здесь должны были быть возможности для улучшения.
А вот сводка по двум запускам после применения кэширования.
Первый запуск (2-й в списке) дольше из-за того, что шаг Post-Job создает новый элемент кэша, а зависимости npm устанавливаются из npm, как обычно.
Второй запуск (1-й в списке) действительно демонстрирует новое улучшение производительности, поскольку происходит попадание в кэш, зависимости npm извлекаются из кэша — время работы нашего конвейера теперь разумно!