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

Стадии

Стадии — это этапы сборочного процесса, кирпичи, из которых в итоге собирается конечный образ. Стадия собирается из группы сборочных инструкций, указанных в конфигурации. Причем группировка этих инструкций не случайна, имеет определенную логику и учитывает условия и правила сборки. С каждой стадией связан конкретный Docker-образ.

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

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

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

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

Сигнатура стадии — это контрольная сумма от:

  • контрольной суммы зависимостей стадии.
  • сигнатуры предыдущей стадии;
  • идентификатора git коммита связанного с предыдущей стадией (если эта стадия связана с git).

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

В случае отсутствия у стадии зависимостей стадии, она пропускается, и, соответственно, конвейер стадий уменьшается на одну стадию. Таким образом конвейер стадий может уменьшаться на несколько стадий, вплоть до единственной стадии from.

Зависимости стадии

Зависимости стадии — это данные, которые напрямую связаны и влияют на сигнатуру стадии. К зависимостям стадии относятся:

  • файлы (и их содержимое) из git-репозиториев;
  • инструкции сборки стадии из файла werf.yaml;
  • произвольные строки указанные пользователем в werf.yaml
  • и т.п.

Большинство зависимостей стадии определяется в файле конфигурации werf.yaml, остальные — во время запуска.

Следующая таблица иллюстрирует зависимости в Dockerfile-образе, Stapel-образе и Stapel-артефакте. Каждая строка таблицы описывает зависимости для определенной стадии. Левая колонка содержит краткое описание зависимостей, правая содержит соответствующую часть werf.yaml и ссылки на разделы с более подробной информацией.

stage dockerfile

target dockerfile instructions
hashsum of files related with ADD and COPY dockerfile instructions
args used in target dockerfile instructions
addHost
image: <image name... || ~>
dockerfile: <relative path>
context: <relative path>
target: <docker stage name>
args:
  <build arg name>: <value>
addHost:
- <host:ip>

Подробнее:

stage from

from
or from image stages-digest
or from artifact stages-digest
actual digest from registry (if fromLatest: true)
fromCacheVersion
mounts
from: <image[:<tag>]>
fromLatest: <bool>
fromCacheVersion: <arbitrary string>
fromImage: <image name>
fromArtifact: <artifact name>
mount:
- from: build_dir
  to: <absolute or relative path>
- from: tmp_dir
  to: <absolute path>
- fromPath: <absolute or relative path>
  to: <absolute path>

stage beforeInstall

beforeInstall bash commands or ansible tasks
cacheVersion
beforeInstallCacheVersion
shell:
  beforeInstall:
  - <bash command>
  cacheVersion: <arbitrary string>
  beforeInstallCacheVersion: <arbitrary string>

or

ansible:
  beforeInstall:
  - <task>
  cacheVersion: <arbitrary string>
  beforeInstallCacheVersion: <arbitrary string>

stage importsBeforeInstall

imports before install
import:
- artifact: <artifact name>
  before: install
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

stage gitArchive

git mappings
git:
- add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>
- url: <git repo url>
  branch: <branch name>
  commit: <commit>
  tag: <tag>
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

stage install

install bash commands or ansible tasks
installCacheVersion
git files hashsum by install stageDependency
git:
- stageDependencies:
    install:
    - <relative path or glob>

shell:
  install:
  - <bash command>
  installCacheVersion: <arbitrary string>

or

ansible:
  install:
  - <task>
  installCacheVersion: <arbitrary string>

stage importsAfterInstall

imports after install
import:
- artifact: <artifact name>
  after: install
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

stage beforeSetup

beforeSetup bash commands or ansible tasks
beforeSetupCacheVersion
git files hashsum by beforeSetup stageDependency
git:
- stageDependencies:
    beforeSetup:
    - <relative path or glob>

shell:
  beforeSetup:
  - <bash command>
  beforeSetupCacheVersion: <arbitrary string>

or

ansible:
  beforeSetup:
  - <task>
  beforeSetupCacheVersion: <arbitrary string>

stage importsBeforeSetup

imports before setup
import:
- artifact: <artifact name>
  before: setup
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

stage setup

setup bash commands or ansible tasks
setupCacheVersion
git files hashsum by setup stageDependency
git:
- stageDependencies:
    setup:
    - <relative path or glob>

shell:
  setup:
  - <bash command>
  setupCacheVersion: <arbitrary string>

or

ansible:
  setup:
  - <task>
  setupCacheVersion: <arbitrary string>

stage gitCache

size of git diff between last used commit and actual

stage importsAfterSetup

imports after setup
import:
- artifact: <artifact name>
  after: setup
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

stage gitLatestPatch

presence of git diff changes between last used commit and actual

stage dockerInstructions

docker instructions
docker:
  VOLUME:
  - <volume>
  EXPOSE:
  - <expose>
  ENV:
    <env name>: <env value>
  LABEL:
    <label name>: <label value>
  ENTRYPOINT: <entrypoint>
  CMD: <cmd>
  WORKDIR: <workdir>
  USER: <user>
  HEALTHCHECK: <healthcheck>

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

Хранилище стадий содержит стадии проекта. Стадии могут храниться локально на хост-машине, либо в Docker Repo.

Большинство команд werf используют стадии. Такие команды требуют указания места размещения хранилища стадий с помощью ключа --repo или переменной окружения WERF_REPO.

Существует 2 типа хранилища стадий:

  1. Локальное хранилище стадий. Использует локальный docker-server для хранения docker-образов соответствующих стадий. Включается опцией --stages-storage=:local. Данный режим был единственным поддерживаемым до версии v1.1.10.
  2. Удалённое хранилище стадий. Использует docker registry для хранения стадий. Включается опцией --stages-storage=DOCKER_REPO_DOMAIN, например --stages-storage=registry.mycompany.com/web/frontend/stages. ЗАМЕЧАНИЕ Каждый проект должен использовать в качестве хранилища стадий уникальный адрес docker repo, который используется только этим проектом.

Стадии будут именоваться по-разному в зависимости от типа используемого хранилища стадий.

При использовании docker registry для хранения стадий, локальный docker-server на всех хостах, где запускают werf, используется как кеш. Этот кеш может быть очищен автоматически самим werf-ом, либо удалён с помощью других инструментов (например docker rmi).

Рекомендуется использовать docker registry в качестве хранилища стадий. Werf по умолчанию использует этот режим при работе в CI/CD системах.

Требования к хостам при использовании удалённого хранилища стадий:

  • Доступ к docker registry.
  • Доступ к кластеру Kubernetes (используется для синхронизации нескольких процессов сборки, публикации и выката с разных хостов).

Заметим, что все команды werf, которые требуют доступа к стадиям должны использовать одно и то же хранилище стадий. Поэтому при использовании локального хранилища стадий все команды werf должны запускаться с одного и того же хоста. При использовании удалённого хранилища стадий не важно с какого хоста запускается werf если для этих вызовов указан одинако (касается таких команд как build, publish, cleanup, deploy и т.д.)

Именование стадий

Стадии в локальном хранилище стадий именуются согласно следующей схемы: werf-stages-storage/PROJECT_NAME:SIGNATURE-TIMESTAMP_MILLISEC. Например:

werf-stages-storage/myproject                   9f3a82975136d66d04ebcb9ce90b14428077099417b6c170e2ef2fef-1589786063772   274bd7e41dd9        16 seconds ago      65.4MB
werf-stages-storage/myproject                   7a29ff1ba40e2f601d1f9ead88214d4429835c43a0efd440e052e068-1589786061907   e455d998a06e        18 seconds ago      65.4MB
werf-stages-storage/myproject                   878f70c2034f41558e2e13f9d4e7d3c6127cdbee515812a44fef61b6-1589786056879   771f2c139561        23 seconds ago      65.4MB
werf-stages-storage/myproject                   5e4cb0dcd255ac2963ec0905df3c8c8a9be64bbdfa57467aabeaeb91-1589786050923   699770c600e6        29 seconds ago      65.4MB
werf-stages-storage/myproject                   14df0fe44a98f492b7b085055f6bc82ffc7a4fb55cd97d30331f0a93-1589786048987   54d5e60e052e        31 seconds ago      64.2MB

Стадии в удалённом хранилище стадий именуются согласно следующей схемы: DOCKER_REPO_ADDRESS:SIGNATURE-TIMESTAMP_MILLISEC. Например:

localhost:5000/myproject-stages                 d4bf3e71015d1e757a8481536eeabda98f51f1891d68b539cc50753a-1589714365467   7c834f0ff026        20 hours ago        66.7MB
localhost:5000/myproject-stages                 e6073b8f03231e122fa3b7d3294ff69a5060c332c4395e7d0b3231e3-1589714362300   2fc39536332d        20 hours ago        66.7MB
localhost:5000/myproject-stages                 20dcf519ff499da126ada17dbc1e09f98dd1d9aecb85a7fd917ccc96-1589714359522   f9815cec0867        20 hours ago        65.4MB
localhost:5000/myproject-stages                 1dbdae9cc1c9d5d8d3721e32be5ed5542199def38ff6e28270581cdc-1589714352200   6a37070d1b46        20 hours ago        65.4MB
localhost:5000/myproject-stages                 f88cb5a1c353a8aed65d7ad797859b39d357b49a802a671d881bd3b6-1589714347985   5295f82d8796        20 hours ago        65.4MB
localhost:5000/myproject-stages                 796e905d0cc975e718b3f8b3ea0199ea4d52668ecc12c4dbf85a136d-1589714344546   a02ec3540da5        20 hours ago        64.2MB
  • PROJECT_NAME — имя проекта;
  • STAGE_SIGNATURE — сигнатура стадии. Сигнатура является идентификатором содержимого стадии и также зависит от истории правок в git репозитории, которые привели к такому содержимому.
  • TIMESTAMP_MILLISEC — уникальный идентификатор, который генерируется в процессе процедуры сохранения стадии после того как стадия была собрана.

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

Алгоритм выборки стадий в werf основан на проверке родства git-коммитов:

  1. Рассчитывается сигнатуры некоторой стадии.
  2. Возможно существование нескольких стадий в хранилище стадий с одной и той же сигнатурой, поэтому на данном этапе происходит выборка всех стадий, подходящих по целевой сигнатуре.
  3. Если текущая стадия связана с git (стадия git-архив, пользовательская стадия с git-патчами или стадия git latest patch), тогда выбираются только те стадии, которые связаны с коммитами, являющимися предками текущего коммита. Таким образом коммиты соседних веток будут отброшены.
  4. Среди оставшихся образов выбирается старейший по времени TIMESTAMP_MILLISEC.

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

Сборка и сохранение стадий

Если в процессе выборки стадий не было найдено подходящей стадии по целевой сигнатуре, тогда werf инициирует сборку нового образа для данной стадии.

Следует отметить, что множество процессов werf (на одном хосте или на нескольких хостах) могут инициировать сборку одной и той же стадии в один момент времени, потому что этой стадии еще нету в хранилище стадий. Werf использует алгоритм оптимистичных блокировок в процессе сохранения свежесобранного образа в хранилище стадий: когда сборка нового образа закончена werf блокирует хранилище стадий на любые операции с целевой сигнатурой и сохраняет свежесобранный образ в хранилище стадий, но только при условии, что в момент сохранения в нем уже нету подходящих стадий по нашей сигнатуре. Новые стадии могли появится в процессе сборки. В случае если уже существующий образ был найден, свежесобранный образ будет отброшен, вместо него в качестве кеша будет взят уже существующий образ. Если же за время сборки подходящих образов в хранилище стадий не появилось, то werf сохранит новый образ, сгенерировав гарантированно уникальный идентификатор TIMESTAMP_MILLISEC.

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

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

Сигнатура стадий образа

Сигнатура стадий образа — это такая сигнатура, которая идентифицирует содержимое всего образа и зависит от истории правок в git репозитории, которые привели к такому содержимому.

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

  • сигнатуры последней не пустой стадии образа;
  • идентификатора git коммита связанного с последней не пустой стадией образа (если эта стадия связана с git).

Данная сигнатура используется при тегировании образов по содержимому и для импорта файлов из артефактов и образов (сигнатура стадий артефакта или образа влияет на сигнатуру стадии импортов целевого образа, куда происходит импорт).

Образы

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

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

werf создает образы используя хранилище стадий. На текущий момент, образы создаются только в процессе публикации (publish) и хранятся в репозитории образов.

Конфигурация образов должна быть описана в файле конфигурации werf.yaml.

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

Синхронизация: блокировки и кеш хранилища стадий

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

  1. Кеш хранилища стадий — это внутренний служебный кеш werf, который существенно повышает производительность фазы рассчёта стадий в случае, если эти стадии уже есть в хранилище. Кеш хранилища стадий содержит соответствие существующих в хранилище стадий с сигнатурой (или другими словами: содержит предварительно расчитанный шаг алгоритма выборки стадий по сигнатуре). Данный кеш является когерентным и werf автоматически сбрасывает его, если будет замечена несостыковка между хранилищем стадий и кешом хранилища стадий.
  2. Менеджер блокировок. Блокировки требуются для корректной публикации новых стадий в хранилище, публикации новых образов в репозиторий образов и для организации параллельных процессов выката в Kubernetes (блокируется имя релиза).

Все команды, использующие параметры хралищища стадий (--stages-storage=...) и репозитория образов (--images-repo=...) также требуют указания адреса менеджера блокировок, который задается опцией --synchronization=... или переменной окружения WERF_SYNCHRONIZATION=.... На данный момент поддерживается только локальный менеджер блокировок (соответствущее значение опции: --synchronization=:local).

Существует 2 типа наборов компонентов для синхронизации:

  1. Локальный. Включается опцией --synchronization=:local.
    • Локальный кеш хранилища стадий располагается по умолчанию в файлах ~/.werf/shared_context/storage/stages_storage_cache/1/PROJECT_NAME/SIGNATURE, каждый из которых хранит соответствие существующих в хранилище стадий по некоторой сигнатуре.
    • Локальный менеджер блокировок использует файловые блокировки, предоставляемые операционной системой.
  2. Kubernetes. Включается опцией --synchronization=kubernetes://NAMESPACE[:CONTEXT][@(base64:CONFIG_DATA)|CONFIG_PATH].
    • Кеш хранилища стадий в Kubernetes использует для каждого проекта отдельный ConfigMap cm/PROJECT_NAME, который создается в указанном NAMESPACE.
    • Менеджер блокировок в Kubernetes использует ConfigMap по имени проекта cm/PROJECT_NAME (тот же самый что и для кеша хранилища стадий) для хранения распределённых блокировок в аннотациях этого ConfigMap. Werf использует библиотека lockgate, которая реализует распределённые блокировки с помощью обновления аннотаций в ресурсах Kubernetes.
  3. Http. Включается опцией --synchronization=http[s]://DOMAIN.
    • Есть публичный сервер синхронизации доступный по домену https://synchronization.werf.io.
    • Собственный http сервер синхронизации может быть запущен командой werf synchronization.

Werf использует --synchronization=:local (локальный кеш хранилища стадий и локальный менеджер блокировок) по умолчанию, если используется локальное хранилище стадий (--stages-storage=:local).

Werf использует --synchronization=https://synchronization.werf.io по умолчанию, если используется удалённое хранилище стадий (--stages-storage=DOCKER_REPO_ADDRESS).

Пользователь может принудительно указать произвольный адрес компонентов для синхронизации, если это необходимо, с помощью явного указания опции --synchronization=:local|(kubernetes://NAMESPACE[:CONTEXT][@(base64:CONFIG_DATA)|CONFIG_PATH])|(http[s]://DOMAIN).

ЗАМЕЧАНИЕ: Множество процессов werf, работающих с одним и тем же проектом обязаны использовать одинаковое хранилище стадий и адрес набора компонентов синхронизации.

Дальнейшее руководство

Больше информации о процессе сборки для stapel и Dockerfile сборщиков доступно в статье.