Сборочный процесс werf для образов, описанных в werf.yaml, подразумевает последовательную сборку стадий для описанных образов.
Несмотря на то, что конвейеры стадий для Dockerfile-образа, Stapel-образа и Stapel-артефакта отличаются, каждая стадия подчиняется общим правилам выборки из хранилища, сохранения, а также работы кеша и блокировок в параллельных запусках.
Сборка стадии Dockerfile-образа
Для сборки Dockerfile-образа werf создает единственную стадию — dockerfile
.
В настоящий момент, при сборке стадии werf использует стандартные команды встроенного в Docker клиента (это аналогично выполнению команды docker build
), а также аргументы, которые пользователь описывает в werf.yaml
. Кэш, создаваемый при сборке, используется, как и при обычной сборке, без помощи werf.
Интересной особенностью werf является то, что в качестве сборочного контекста используются файлы не из директории проекта, а из git-репозитория. Все файлы, которые содержатся в директории, заданной директивой context
(по умолчанию это директория проекта), берутся из текущего коммита репозитория проекта (подробнее про гитерминизм можно почитать в отдельной статье).
В итоге сборку стадии dockerfile
можно представить следующим образом:
docker build --file=Dockerfile - < ~/.werf/service/tmp/context/4b9d6bc2-a549-42f9-86b8-4032c146f888
Подробнее о файле конфигурации сборки werf.yaml
в соответствующем разделе.
Сборка стадии Stapel-образа и Stapel-артефакта
При сборке стадии предполагается, что инструкции стадии будут запускаться в контейнере, основанном на предыдущей собранной стадии или на базовом образе. Такой контейнер будет упоминаться далее как сборочный контейнер.
Перед запуском сборочного контейнера werf подготавливает набор инструкций, который зависит от типа стадии и содержит как служебные команды werf, так и пользовательские, указанные в конфигурации werf.yaml
. Например, среди служебных команд может быть добавление файлов, наложение патчей, запуск ansible заданий и т.п.
Stapel-сборщик использует свой набор инструментов и библиотек и никак не зависит от базового образа. При запуске сборочного контейнера werf монтирует всё необходимое из специального служебного образа registry.werf.io/werf/stapel
. Подробнее об образе можно прочитать в соответствующей статье.
В сборочный контейнер пробрасывается сокет ssh-агента с хоста, а также могут использоваться пользовательские маунты.
Также стоит отметить, что при сборке werf игнорирует некоторые параметры манифеста базового образа, перетирая их определёнными значениями:
--user=0:0
;--workdir=/
;--entrypoint=/.werf/stapel/embedded/bin/bash
.
В итоге запуск сборочного контейнера произвольной стадии можно представить следующим образом:
docker run \
--volume=/tmp/ssh-ln8yCMlFLZob/agent.17554:/.werf/tmp/ssh-auth-sock \
--volumes-from=stapel_0.6.1 \
--env=SSH_AUTH_SOCK=/.werf/tmp/ssh-auth-sock \
--user=0:0 \
--workdir=/ \
--entrypoint=/.werf/stapel/embedded/bin/bash \
sha256:d6e46aa2470df1d32034c6707c8041158b652f38d2a9ae3d7ad7e7532d22ebe0 \
-ec eval $(echo c2V0IC14 | /.werf/stapel/embedded/bin/base64 --decode)
Подробнее о файле конфигурации сборки werf.yaml
в соответствующем разделе.
Как Stapel-сборщик работает с CMD и ENTRYPOINT
Для сборки стадии werf запускает контейнер со служебными значениями CMD
и ENTRYPOINT
а затем, заменяет их значениями базового образа. Если в базовом образе эти значения не установлены, werf сбрасывает их следующим образом:
[]
дляCMD
;[""]
дляENTRYPOINT
.
Также werf сбрасывает (использует специальные пустые значения) значение ENTRYPOINT
базового образа, если указано значение CMD
в конфигурации (docker.CMD
).
В противном случае поведение werf аналогично поведению Docker.
Как использовать Stapel-сборщик в закрытом контуре
При запуске сборочного контейнера werf монтирует инструментарий из специального образа, имя и тег которого зашиты в коде.
Для сборки со Stapel в закрытом контуре (без доступа к служебному образу) необходимо загрузить образ в доступный container registry и переопределить имя с помощью переменных окружения WERF_STAPEL_IMAGE_NAME
и WERF_STAPEL_IMAGE_VERSION
(например, WERF_STAPEL_IMAGE_NAME=localhost:5000/stapel
и WERF_STAPEL_IMAGE_VERSION=0.6.2
).
Выборка стадий
Алгоритм выборки стадии в werf можно представить следующим образом:
- Рассчитывается дайджест стадии.
- Выбираются все стадии, подходящие под дайджест, т.к. с одним дайджестом может быть связанно несколько стадий в хранилище.
- Выбирается старейший по времени
TIMESTAMP_MILLISEC
(подробнее про именование стадий здесь).
Дополнение для Stapel-образов и Stapel-артефактов
К основному алгоритму добавляется проверка родства git-коммитов. После шага 2 выполняется дополнительный отсев с использованием истории git: если текущая стадия связана с git (стадия git-архив, пользовательская стадия с git-патчами или стадия git latest patch), тогда выбираются только те стадии, которые связаны с коммитами, являющимися предками текущего коммита. Таким образом, коммиты соседних веток будут отброшены.
Возможна ситуация когда существует несколько собранных образов с одинаковым дайджестом. Более того, стадии для разных git-веток могут иметь одинаковую дайджест. Однако werf гарантированно предотвращает переиспользование кеша между несвязанными ветками. Кеш в разных ветках может быть переиспользован только если этот кеш относится к коммиту, который является базовым, как для одной ветки, так и для другой.
Сохранение стадий в хранилище
Множество процессов werf (на одном хосте или на нескольких хостах) могут инициировать сборку одной и той же стадии в один момент времени, потому что этой стадии еще нет в хранилище.
werf использует алгоритм оптимистичных блокировок в процессе сохранения свежесобранного образа в хранилище. Когда сборка нового образа закончена, werf блокирует хранилище на любые операции с целевым дайджестом:
- Если за время сборки подходящего образа не появилось в хранилище, то werf сохраняет новый образ, сгенерировав гарантированно уникальный идентификатор
TIMESTAMP_MILLISEC
. - Если за время сборки подходящий образ появился в хранилище, то werf отбрасывает свежесобранный образ, а вместо него использует появившийся в хранилище образ.
Другими словами: первый процесс, который закончит сборку новой стадии (самый быстрый процесс) получит шанс сохранить собранный образ в хранилище. Медленный процесс сборки не будет блокировать более быстрые процессы в параллельной и распределенной среде.
В процессе выборки и сохранения новых стадий в хранилище werf использует менеджер блокировок для координации работы нескольких процессов werf.
Параллельная сборка
Параллельная сборка в werf регулируется двумя параметрами -p, --parallel
и --parallel-tasks-limit
. По умолчанию параллельная сборка включена и собирается не более 5 образов одновременно.
После построение дерева зависимостей образов, werf разбивает сборку на этапы. Каждый этап содержит набор независимых образов, которые могут собираться параллельно.
┌ Concurrent builds plan (no more than 5 images at the same time)
│ Set #0:
│ - ⛵ image common-base
│ - 🛸 artifact jq
│ - 🛸 artifact libjq
│ - 🛸 artifact kcov
│
│ Set #1:
│ - ⛵ image base-for-go
│
│ Set #2:
│ - 🛸 artifact terraform-provider-vsphere
│ - 🛸 artifact terraform-provider-gcp
│ - 🛸 artifact candictl
│ - ⛵ image candictl-tests
│ - 🛸 artifact helm
│ - 🛸 artifact controller
│
│ Set #3:
│ - ⛵ image base
│
│ Set #4:
│ - ⛵ image tests
│ - ⛵ image app
└ Concurrent builds plan (no more than 5 images at the same time)
Buildah
werf поддерживает использование Buildah в экспериментальном режиме для сборки образов. В этом режиме пока поддерживаются только Dockerfile-образы (планируется поддержка Stapel).
При этом процесс сборки не изменился, перемены коснулись только бэкенда контейнеров и хранилища (сервер Docker был заменен на Buildah).
Более подробную информацию о Buildah можно получить в разделе Режим сборки с использованием Buildah.