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

Хранилище стадий

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

Есть два вида хранилища стадий: локальное и репозиторий (container registry).

  • Локальное хранилище стадий может быть использовано только для локальной разработки и на данный момент только для сборки образов. В качестве локального хранилища выступает Docker server. Локальное хранилище стадий будет задействовано, например, если вызвать команду werf build без аргумента --repo.
  • Во всех остальных случаях (т.е. кроме локальной разработки) в качестве хранилища стадий всегда выступает репозиторий в container registry. Если запустить сборку с хранилищем стадий в репозитории (например, werf build с параметром --repo ghcr.io/example/myrepo), то werf сначала проверит наличие требуемых стадий в локальном хранилище и скопирует оттуда подходящие стадии, чтобы не пересобирать эти стадии заново.

Для дальнейших подробностей введём также понятие инсталляции проекта — это Git-репозиторий проекта и связанные с ним хранилища стадий в репозитории. Во всех вызовах werf предполагается использование одного и того же Git-репозитория и одних и тех же хранилищ стадий (может быть указано одно или несколько — подробности см. далее).

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

Первичный репозиторий

  • Задаётся параметром --repo (или переменной окружения WERF_REPO).
  • У проекта всегда есть только один такой репозиторий.

Данное хранилище является основным и объединяет в себе несколько функций:

  1. История проекта.
    • Хранилище метаданных, связанных с Git-репозиторием, для собираемых образов и стадий.
    • История проекта позволяет реализовать продвинутый алгоритм безопасной очистки старых стадий на основе истории Git-репозитория (команда werf cleanup).
  2. Синхронизация сборки в распределённом окружении.
    • Данное хранилище позволяет запускать сборку в распределённом окружении и эффективно переиспользовать собираемые стадии в таком окружении. Это возможно за счёт механизмов распределённой синхронизации, встроенных в werf. Эти механизмы работают исключительно с первичным репозиторием.
    • Как только стадия попадает в первичный репозиторий, эта стадия может быть переиспользована другими сборочными процессами, которые могут быть запущены с произвольного хоста.
  3. Сборочный кэш ранее собранных стадий.
    • Ранее собранные стадии по возможности переиспользуются вместо того, чтобы собираться заново.
    • Данная функция схожа с привычным локальным сборочным кэшом в Docker server, однако в случае werf этот кэш находится в container registry.
  4. Хранилище финальных образов, используемых при запуске приложения в Kubernetes.
    • Kubernetes будет скачивать образы из этого репозитория для запуска контейнеров приложения.
    • В качестве финальных образов напрямую используются те же стадии, из которых состоит собранный образ.

Очистка (ВАЖНО!). Для корректной и полноценной работы werf первичный репозиторий должен быть надёжным (persistent) хранилищем, образы из которого удаляются лишь специальной командой очистки werf cleanup. Первичный репозиторий — это не просто сборочный кэш проекта, но и хранилище его истории, похожее на историю коммитов в Git-репозитории.

Помимо первичного репозитория в werf есть другие типы репозиторив, которые могут выполнять часть его функций. Они будут рассмотрены далее. Стоит помнить, что без первичного репозитория werf не работает, а все другие репозитории — дополнительные; они были созданы, чтобы снять часть функций первичного репозитория в вашей инсталляции проекта.

Вторичный репозиторий

  • Задаётся параметром --secondary-repo (или переменной окружения WERF_SECONDARY_REPO_<NAME>).
  • Может быть указан один или несколько таких репозиториев.

Данное хранилище — это репозиторий только для чтения (read-only), выполняющий функцию сборочного кэша ранее собранных стадий (см. функции первичного репозитория).

Как правило, в качестве secondary-repo может быть указан уже существующий первичный репозиторий от другой инсталляции проекта. Отсутствующие стадии по мере необходимости будут скопированы из вторичного репозитория в первичный вместо сборки с нуля. Вновь собранные же стадии будут помещены только в первичный репозиторий, потому что вторичный репозиторий всегда read-only, т.е. мы не имеем права в него записывать новые данные.

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

Кэширующий репозиторий

  • Задаётся параметром --cache-repo (или переменной окружения WERF_SECONDARY_REPO_<NAME>).
  • Может быть указан один или несколько таких репозиториев.

Выполняет функцию сборочного кэша ранее собранных стадий (см. функции первичного репозитория). Кэширующий репозиторий отличается от вторичного тем, что он read-write, т.е. используется как для записи новых стадий в кэш, так и для использования собранных стадий из этого кэша.

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

Стадии, которые используются в качестве базовых для сборки новых стадий:

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

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

Кэширующий репозиторий никогда не используется при запуске приложения в Kubernetes.

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

Финальный репозиторий

  • Задаётся параметром --final-repo (или переменной окружения WERF_FINAL_REPO).
  • Может быть указан только один такой репозиторий.

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

  • Вновь собираемые образы попадают сначала в первичный репозиторий, и затем копируются в финальный.
  • Финальный репозиторий никогда не хранит промежуточные стадии образов, в нем есть лишь последняя стадия образа.
  • Финальный репозиторий никогда не хранит промежуточные образы (артефакты), нужные для сборки конечных образов. Сюда попадают только конечные образы, используемые в Kubernetes.

Очистка данного репозитория выполняется командой werf cleanup в паре с первичным репозиторием (необходимо указывать 2 параметра команде werf cleanup: --repo и --final-repo).

Примеры организации хранилища стадий

Рассмотрим варианты использования и комбинации описанных репозиториев. Стоит отметить, что любой из описанных вариантов требует указания первичного репозитория.

1. Стандарт

  • Первичный репозиторий доступен со всех сборочных хостов, а также из кластера Kubernetes.
  • При сборке образов сборочный кэш будет скачиваться и загружаться в этот репозиторий. При выкате Kubernetes будет скачивать финальные образы приложения из данного репозитория.
  • В большинстве случаев следует использовать именно эту схему. Также данная схема подходит, если нет уверенности в том, что требуется для проекта.

2. Дополнительный кэш в локальной сети

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

3. Полная оптимизация

  • Первичный репозиторий располагается в локальной сети с быстрым доступом и доступен со всех сборочных хостов. Доступ к данному репозиторию из кластера Kubernetes не требуется. В отличие от кэширующего репозитория, первичный не может быть полностью очищен или удалён без последствий для работы werf, поэтому требуется обеспечить персистивность и надёжность данного хранилища.
  • Опционально могут быть подняты дополнительные кэширующие репозитории в локальной сети с быстрым доступом.
  • Финальный репозиторий располагается близко к кластеру Kubernetes для того, чтобы запускаемое приложение быстро скачивало образы приложения (скачивание финальных образов со стороны Kubernetes будет происходить чаще, чем их загрузка со сборочных хостов). Также к финальному репозиторию требуется доступ на запись со всех сборочных хостов. В финальный репозиторий будут загружены лишь конечные образы приложения (без промежуточных образов артефактов, которые нужны для сборки других образов).