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

Несмотря на то, что конвейеры стадий для Dockerfile-образа, Stapel-образа и Stapel-артефакта отличаются, каждая стадия подчиняется общим правилам выборки из хранилища, сохранения, а также работы кеша и блокировок в параллельных запусках.

Сборка стадии Dockerfile-образа

Для сборки Dockerfile-образа werf создает единственную стадиюdockerfile.

В настоящий момент, при сборке стадии werf использует стандартные команды встроенного в Docker клиента (это аналогично выполнению команды docker build), а также аргументы, которые пользователь описывает в werf.yaml. Кэш, создаваемый при сборке, используется, как и при обычной сборке, без помощи werf.

Подробнее о файле конфигурации сборки werf.yaml смотри в соответствующем разделе.

Сборка стадии Stapel-образа и Stapel-артефакта

При сборке стадии предполагается, что инструкции стадии будут запускаться в контейнере, основанном на предыдущей собранной стадии. Такой контейнер будет упоминаться далее как сборочный контейнер.

Перед запуском сборочного контейнера werf подготавливает набор инструкций, который зависит от типа стадии и содержит как внутренние команды werf, так и пользовательские команды, указанные в конфигурации werf.yaml. Например, werf может добавлять в список инструкций команды применения патча, измененных примонтированных файлов (такие патчи werf делает с помощью CLI команд git).

При запуске сборочного контейнера пробрасывается сокет ssh-агента с хоста, а также могут использоваться пользовательские маунты.

Для сборки Stapel-сборщик использует свой набор инструментов и библиотек, никак не зависит от базового образа. При запуске сборочного контейнера werf монтирует специальный сервисный образ flant/werf-stapel. Подробнее об образе можно прочитать в соответствующей статье.

Также стоит отметить, что werf игнорирует значения манифеста базового образа при сборке:

  • --user=0:0;
  • --workdir=/;
  • --entrypoint=/.werf/stapel/embedded/bin/bash.

В итоге, запуск сборочного контейнера выглядит следующим образом:

docker run \
  --name=werf.build.offtur4fay \
  --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 \
  --env=COLUMNS=146 \
  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.

Выборка стадий

Алгоритм выборки стадии в werf можно представить следующим образом:

  1. Рассчитывается дайджест стадии.
  2. Выбираются все стадии, подходящие под дайджест, т.к. c одним дайджестом может быть связанно несколько стадий в хранилище.
  3. Выбирается старейший по времени TIMESTAMP_MILLISEC (подробнее про именование стадий здесь).

Дополнение для Stapel-образов и Stapel-артефактов

К основному алгоритму добавляется проверка родства git-коммитов. После шага 2 выполняется дополнительный отсев с использованием истории git: если текущая стадия связана с git (стадия git-архив, пользовательская стадия с git-патчами или стадия git latest patch), тогда выбираются только те стадии, которые связаны с коммитами, являющимися предками текущего коммита. Таким образом, коммиты соседних веток будут отброшены.

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

Сохранение стадий в хранилище

Множество процессов werf (на одном хосте или на нескольких хостах) могут инициировать сборку одной и той же стадии в один момент времени, потому что этой стадии еще нет в хранилище.

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

  • Если за время сборки подходящего образа не появилось в хранилище, то werf сохраняет новый образ, сгенерировав гарантированно уникальный идентификатор TIMESTAMP_MILLISEC.
  • Если за время сборки подходящий образ появился в хранилище, то werf отбрасывает свежесобранный образ, а вместо него использует появившийся в хранилище образ.

Другими словами: первый процесс, который закончит сборку новой стадии (самый быстрый процесс) получит шанс сохранить собранный образ в хранилище. Медленный процесс сборки не будет блокировать более быстрые процессы в параллельной и распределенной среде.

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