werf — наша GitOps CLI-утилита с открытым кодом для сборки и доставки приложений в Kubernetes. В релизе v1.1 была представлена новая возможность в сборщике образов: тегирование образов по содержимому или content-based tagging. До сих пор типичная схема тегирования в werf предполагала тегирование Docker-образов по Git-тегу, Git-ветке или Git-коммиту. Но у всех этих схем есть недостатки, которые полностью решаются новой стратегией тегирования. Подробности о ней и чем она так хороша — под катом.
Выкат набора микросервисов из одного Git-репозитория
Часто встречается ситуация, когда приложение разбито на множество более-менее независимых сервисов. Релизы этих сервисов могут происходить независимо: за один раз может релизиться один или несколько сервисов, остальные при этом должны продолжать работать без каких-либо изменений. Но с точки зрения хранения кода и управления проектом удобнее держать такие сервисы приложения в едином репозитории.
Бывают ситуации, когда сервисы действительно независимы и не связаны с одним приложением. В таком случае они будут расположены в отдельных проектах и их релиз будет осуществляться через отдельные процессы CI/CD в каждом из проектов.
Однако в реальности разработчики зачастую разбивают единое приложение на несколько микросервисов, но заводить для каждого отдельный репозиторий и проект… — явный overkill. Именно про эту ситуацию и пойдет далее речь: несколько таких микросервисов лежат в едином репозитории проекта и релизы происходят через единый процесс в CI/CD.
Тегирование по Git-ветке и Git-тегу
Допустим, используется самая распространенная стратегия тегирования — tag-or-branch. Для Git-веток образы тегируются названием ветки, для одной ветки в один момент времени существует только один опубликованный образ по имени этой ветки. Для Git-тегов образы тегируются соответственно именем тега.
При создании нового Git-тега — например, при выходе новой версии — для всех образов проекта в Docker Registry будет создан новый Docker-тег:
-
myregistry.org/myproject/frontend:v1.1.10
-
myregistry.org/myproject/myservice1:v1.1.10
-
myregistry.org/myproject/myservice2:v1.1.10
-
myregistry.org/myproject/myservice3:v1.1.10
-
myregistry.org/myproject/myservice4:v1.1.10
-
myregistry.org/myproject/myservice5:v1.1.10
-
myregistry.org/myproject/database:v1.1.10
Эти новые имена образов попадают через Helm-шаблоны в конфигурацию Kubernetes. При запуске деплоя командой werf deploy
происходит обновление поля image
в манифестах ресурсов Kubernetes и перезапуск соответствующих ресурсов из-за изменившегося имени образа.
Проблема: в случае, когда реально с предыдущего выката (Git-тега) не изменилось содержимое образа, а лишь его Docker-тег, происходит лишний перезапуск этого приложения и, соответственно, возможен некоторый простой. Хотя не было никаких реальных причин производить этот перезапуск.
Как следствие, при текущей схеме тегирования приходится городить несколько отдельных Git-репозиториев и встает проблема организации выката этих нескольких репозиториев. В общем и целом такая схема получается перегруженной и сложной. Лучше объединять много сервисов в единый репозиторий и создавать такие Docker-теги, чтобы лишних перезапусков не было.
Тегирование по Git-коммиту
В werf также присутствует стратегия тегирования, связанная с Git-коммитами.
Git-commit является идентификатором содержимого Git-репозитория и зависит от истории правок файлов в Git-репозитории, поэтому кажется логичным использовать его для тегирования образов в Docker Registry.
Однако тегирование по Git-коммиту имеет те же недостатки, что и по Git-веткам или Git-тегам:
- Мог быть создан пустой коммит, который не меняет файлов, а Docker-тег образа будет изменен.
- Мог быть создан merge-коммит, который не меняет файлов, а Docker-тег образа будет изменен.
- Мог быть создан коммит, который меняет те файлы в Git, которые не импортируются в образ, а Docker-тег образа снова будет изменен.
Тегирование по имени Git-ветки не отражает версию образа
Есть и еще одна проблема, связанная со стратегией тегирования по Git-веткам.
Тегирование по имени ветки работает до тех пор, пока коммиты этой ветки собирают последовательно в хронологическом порядке.
Если в текущей схеме пользователь запустит пересборку старого коммита, связанного с некоторой веткой, то werf перетрет образ по соответствующему Docker-тегу вновь собранной версией образа для старого коммита. Использующие этот тег Deployment'ы с этого момента рискуют во время перезапуска pod'ов сделать pull другой версии образа, в результате чего наше приложение потеряет связь с CI-системой, рассинхронизируется.
Кроме того, при последовательных push’ах в одну ветку с малым промежутком времени между ними старый коммит может собраться позже, чем более новый: старая версия образа перетрет новую по тегу Git-ветки. Такие проблемы может решать CI/CD-система (например, в GitLab CI для серии коммитов запускается pipeline последнего). Однако это поддерживают не все системы и должен быть более надежный способ предотвращения столь фундаментальной проблемы.
Что такое content-based tagging?
Итак, что же такое content-based tagging — тегирование образов по содержимому.
Для создания Docker-тегов используются не примитивы Git'а (Git-ветка, Git-тег…), а контрольная сумма, связанная с:
- содержимым образа. Идентификатор-тег образа отражает его содержимое. При сборке новой версии этот идентификатор не поменяется, если в образе не изменились файлы;
- историей создания этого образа в Git. Образы, связанные с разными Git-ветками и разной историей сборки через werf, будут иметь разные теги-идентификаторы.
В качестве такого тега-идентификатора выступает так называемая сигнатура стадий образа.
Каждый образ состоит из набора стадий: from
, before-install
, git-archive
, install
, imports-after-install
, before-setup
,… git-latest-patch
и т.д. У каждой стадии есть идентификатор, отражающий ее содержимое, — сигнатура стадии (stage signature).
Финальный же образ, состоящий из этих стадий, тегируется так называемой сигнатурой набора этих стадий — stages signature, — которая является обобщающей для всех стадий образа.
У каждого образа из конфигурации werf.yaml
в общем случае будет своя такая сигнатура и, соответственно, Docker-тег.
Сигнатура стадий решает все указанные проблемы:
- Устойчива к пустым Git-коммитам.
- Устойчива к Git-коммитам, которые меняют файлы, не являющиеся релевантными для образа.
- Не приводит к проблеме с перетиранием актуальной версии образа при перезапуске сборок для старых Git-коммитов ветки.
Теперь это рекомендуемая стратегия тегирования и используется по умолчанию в werf для всех CI-систем.
Как включить и использовать в werf
Соответствующая опция появилась у команды werf publish
: --tag-by-stages-signature=true|false
В CI-системе стратегия тегирования задается командой werf ci-env
. Ранее для нее определялся параметр werf ci-env --tagging-strategy=tag-or-branch
. Теперь, если указать werf ci-env --tagging-strategy=stages-signature
или не указывать эту опцию, werf по умолчанию будет использовать стратегию тегирования stages-signature
. Команда werf ci-env
автоматически выставит нужные флаги для команды werf build-and-publish
(или werf publish
), поэтому никаких дополнительных опций для этих команд указывать не нужно.
Например, команда:
werf publish --stages-storage :local --images-repo registry.hello.com/web/core/system --tag-by-stages-signature
… может создать следующие образы:
-
registry.hello.com/web/core/system/backend:4ef339f84ca22247f01fb335bb19f46c4434014d8daa3d5d6f0e386d
-
registry.hello.com/web/core/system/frontend:f44206457e0a4c8a54655543f749799d10a9fe945896dab1c16996c6
Здесь 4ef339f84ca22247f01fb335bb19f46c4434014d8daa3d5d6f0e386d
— это сигнатура стадий образа backend
, а f44206457e0a4c8a54655543f749799d10a9fe945896dab1c16996c6
— сигнатура стадий образа frontend
.
При использовании специальных функций werf_container_image
и werf_container_env
в шаблонах Helm ничего менять не требуется: эти функции будут автоматически генерировать верные имена образов.
Пример конфигурации в CI-системе:
type multiwerf && source <(multiwerf use 1.1 beta)
type werf && source <(werf ci-env gitlab)
werf build-and-publish|deploy
Больше информации по настройке доступно в документации:
- Справочник → Публикация (publish);
- Работа с CI/CD → Общие сведения → stages-signature;
- Интеграция с GitLab CI/CD → .gitlab-ci.yml.
Итого
- Новая опция
werf publish --tag-by-stages-signature=true|false
. - Новое значение опции
werf ci-env --tagging-strategy=stages-signature|tag-or-branch
(если не указать, то по умолчанию будетstages-signature
). - Если до этого использовались опции тегирования по Git-коммитам (
WERF_TAG_GIT_COMMIT
или опцияwerf publish --tag-git-commit COMMIT
), то обязательно переключать на стратегию тегирования stages-signature. - Новые проекты лучше сразу переключать на новую схему тегирования.
- Старые проекты при переводе на werf 1.1 желательно переключать на новую схему тегирования, однако старая tag-or-branch по-прежнему поддерживается.
Content-based tagging решает все освещенные в статье проблемы:
- Устойчивость имени Docker-тега к пустым Git-коммитам.
- Устойчивость имени Docker-тега к Git-коммитам, которые меняют нерелевантные для образа файлы.
- Не приводит к проблеме с перетиранием актуальной версии образа при перезапуске сборок для старых Git-коммитов для Git-веток.
Пользуйтесь! И не забывайте заглядывать к нам на GitHub, чтобы создать issue или найти уже существующий, поставить плюс, создать PR или просто понаблюдать за развитием проекта.
P.S.
Читайте также в нашем блоге:
- «Релиз werf 1.1: улучшения в сборщике сегодня и планы на будущее»
- «Представляем werf 1.0 stable: при чём тут GitOps, статус и планы»
- «werf — наш инструмент для CI/CD в Kubernetes (обзор и видео доклада)»;
- Цикл заметок о нововведениях в werf:
Автор: Timofey Kirillov