Stapel-builder глубоко
Что нужно знать перед уроком
- M3.02 — werf.yaml и два билдера — нужно понимать общую структуру werf.yaml и выбор билдера
Что нужно знать перед уроком
M3 урок 2 — общая структура werf.yaml, беглое знакомство со Stapel. Здесь разбираем Stapel-builder полностью: 4 стадии, git.stageDependencies, ansible, cacheVersion.
Теория
4 user-стадии в строгом порядке: beforeInstall → install → beforeSetup → setup
Stapel-builder делит сборку на четыре именованные пользовательские стадии, выполняемые строго последовательно в этом порядке. Официальная конвенция того, что класть на какую стадию:
| Стадия | Что кладём | Пример |
|---|---|---|
beforeInstall | системные пакеты, редко меняющиеся | apt update -q && apt install -y libmysqlclient-dev g++ |
install | зависимости приложения (по манифестам зависимостей) | bundle install, npm install, go mod download |
beforeSetup | конфигурация системы, сборка ассетов | bundle exec rails assets:precompile, webpack build |
setup | конфигурация приложения, финальная генерация | rake generate:configs |
| |
Порядок неслучаен: он воспроизводит логику зависимостей «от общего к специфичному». Системные пакеты (beforeInstall) меняются реже всего — обычно только при обновлении базового образа. Зависимости приложения (install) меняются при обновлении манифеста зависимостей. Конфигурация системы/ассеты (beforeSetup) — при изменении статических ресурсов. Конфигурация приложения (setup) — самая часто меняющаяся часть. Разделение на 4 стадии с независимым кэшем каждой означает: если поменялся только код в config/templates/, пересобирается только setup, всё до неё берётся из кэша.
git.stageDependencies — точный контроль инвалидации кэша
Ключевое отличие от обычного COPY в Dockerfile: stageDependencies привязывает маски файлов (не весь git.add) к конкретной стадии. Если изменился только package-lock.json — пересобирается стадия install (и всё после неё), но не beforeInstall. Если изменился файл в app/webpack/ — пересобирается beforeSetup (и setup), но install берётся из кэша нетронутым. Это невозможно выразить чистым Dockerfile без явного разнесения COPY по нескольким инструкциям с ручным контролем порядка — Stapel делает это декларативно и заточено именно под git-историю проекта.
shell vs ansible — два взаимоисключающих способа описать инструкции
| |
shell и ansible — взаимоисключающие top-level директивы одного image:-блока (нельзя использовать оба одновременно в одном образе). ansible-инструкции (debug, file, copy, apk, apt, yum, template и другие Ansible-модули) предпочтительны, когда команда уже владеет Ansible-экосистемой и хочет идемпотентные, декларативные шаги вместо императивных shell-команд (например, apt: state=present не переустановит пакет, если он уже там, в отличие от повторного apt-get install, который просто отработает без ошибки, но не даёт декларативной гарантии).
cacheVersion — принудительный сброс кэша стадии
| |
Иногда нужно форсировать пересборку стадии без изменения фактических файлов зависимостей — например, при обновлении версии системного пакета в базовом образе, о котором Git не «знает». cacheVersion (для всех стадий) и <stage>CacheVersion (beforeInstallCacheVersion/installCacheVersion/beforeSetupCacheVersion/setupCacheVersion, для конкретной) — произвольная строка, изменение которой инвалидирует кэш соответствующей стадии независимо от изменений в файлах.
Частые ошибки и подводные камни
- Класть установку зависимостей приложения в
beforeInstallвместоinstall. Формально работать будет, но вы теряете смысл разделения стадий —git.stageDependenciesдляinstallне сработает так, как задумано, если реальная установка идёт на другой стадии. - Пытаться использовать и
shell, иansibleв одномimage:-блоке. Это взаимоисключающие директивы; нужно выбрать одну для конкретного образа (но разные образы одногоwerf.yamlмогут использовать разные подходы). - Забыть про
git.stageDependenciesи получить пересборку всего образа при любом изменении любого файла. Без явных масок по стадиям Stapel по умолчанию привязывает изменения Git к максимально ранней стадии, где встречаетсяgit.add— можно потерять весь смысл тонкого кэша.
Практика в кластере
Практики в кластере в этом уроке нет — фокус на механике сборки, деплой Vikunja начнётся с M4.
Практика разработки
- Перепишите backend-образ Vikunja из M3.02 через Stapel вместо Dockerfile-builder, с явным разделением стадий:
| |
- Соберите и замерьте время первой сборки, затем измените только один
.go-файл (например, добавьте комментарий) и соберите снова:
| |
- Затем измените
go.mod(например, добавьте пустую строку) и соберите ещё раз — теперь должна пересобраться и стадияinstall, подтверждая, чтоstageDependenciesработает именно так, как задумано.
Шпаргалка команд урока
| |
Шпаргалка команд: 📋 werf .
Вопросы для самопроверки
Строгий порядок: beforeInstall (системные пакеты) → install (зависимости приложения) → beforeSetup (конфигурация системы/ассеты) → setup (конфигурация приложения).
Источник: Werf — документация
stageDependencies даёт точечный контроль инвалидации кэша по маскам файлов для install/beforeSetup/setup.
Источник: Werf — документация
shell и ansible взаимоисключающие директивы одного image:-блока; выбор делается per-образ, а не глобально для файла.
Источник: Werf — документация
Изменение значения cacheVersion/<stage>CacheVersion инвалидирует кэш соответствующей стадии независимо от изменений в файлах Git.
Источник: Werf — документация
install — стадия для зависимостей приложения по манифестам зависимостей; beforeInstall — для системных пакетов.
Источник: Werf — документация — пример Stapel shell instructions
Рекомендуемая литература
Официальная документация
Статьи и блоги
- werf v1.2 — стабильный релиз утилиты для доставки приложений в Kubernetes (Stapel) — Flant на Habr.
- Первые шаги с werf: собираем и деплоим простое приложение — Flant на Habr.
- GitLab + K8s + Werf — Habr.
Книги
- Nigel Poulton. Docker Deep Dive. 2023. ISBN 978-1916585206.
Связанные материалы
- Предыдущий урок: M3.02 — werf.yaml и два билдера.
- Следующий урок: M3.04 — Линковка образов.
- Шпаргалка: 📋 werf .
✓ Урок пройден — все вопросы самопроверки отвечены верно