Stapel-импорт между вспомогательными образами
Что нужно знать перед уроком
- M3.04 — Линковка образов — нужно понимать dependencies/imports через targetBuildArg, чтобы сравнить со Stapel-импортом
Что нужно знать перед уроком
M3 урок 4 — линковка образов через dependencies/imports+targetBuildArg (передача метаданных: имя/тег/дайджест). Здесь — другой механизм, специфичный именно для Stapel: прямое копирование файлов из одного образа в другой без прохождения через build-аргументы Dockerfile.
Теория
import: — прямое копирование файлов между образами Stapel
| |
import (в отличие от dependencies/imports из прошлого урока) — механизм именно Stapel-модели (обязателен after/before — стадия целевого Stapel-образа, куда встраивается импорт) и копирует непосредственно файлы/директории, а не передаёт метаданные через build-аргумент. Обязательные поля: image (источник), add (абсолютный путь в источнике), to (абсолютный путь назначения, по умолчанию равен add, если не указан явно), after/before (install или setup).
Источником (image:) может быть не только другой image:-блок текущего werf.yaml, но и произвольный внешний образ по полному имени с тегом или дайджестом (node:24-alpine, nginx:1.30@sha256:...) — он вообще не обязан быть объявлен где-либо в конфигурации проекта:
| |
Это удобно, когда нужный готовый артефакт уже существует в чужом опубликованном образе (например, взять статическую страницу-заглушку из стороннего образа) и заводить для него отдельный image:-блок ради одного import избыточно.
Почему это лучше, чем «поставить-собрать-удалить» в одном слое
Классическая антипаттерн-альтернатива без импорта — установить компилятор, собрать бинарник и удалить компилятор в одном и том же образе:
| |
Проблема — из-за copy-on-write (M3 урок 1) удаление пакета не уменьшает физический размер слоя: файлы компилятора всё равно физически присутствовали в промежуточном слое (или в том же слое, если все команды в одном shell-блоке дают один слой) и были лишь помечены как удалённые. Итоговый образ всё равно «толстый». Кроме того, такой слой невозможно эффективно кэшировать по отдельности: изменение кода приложения форсирует пересборку всей цепочки «поставить-собрать-удалить» заново.
import решает обе проблемы: builder-образ с компилятором собирается отдельно (со своим независимым кэшем по стадиям, изученным в M3 уроке 3), а в целевой app-образ копируется только конечный артефакт (add: /app/build/app) — сам компилятор физически никогда не попадает ни в один слой app-образа.
Дополнительные возможности import: маски, владелец, несколько источников
| |
Можно импортировать из нескольких разных вспомогательных образов в один целевой (backend-бинарник из одного builder’а, frontend-статика из другого) — именно этот случай прямо применим к Vikunja: два builder-образа (Go и Node), один финальный runtime-образ. includePaths/excludePaths фильтруют, какие файлы из директории попадут в целевой образ, owner/group — задают владельца скопированных файлов в целевом образе (актуально для непривилегированных runtime-образов, где процесс должен работать не от root).
Частые ошибки и подводные камни
- Пытаться использовать
importдля встраивания в образ, собранный через Dockerfile. Целевым (принимающимimport) может быть только Stapel-образ —after/beforeзавязаны на стадииinstall/setup, которых у Dockerfile-builder просто нет; для линковки с Dockerfile-образами нуженdependencies+COPY --from(M3 урок 4). Источником при этом может быть любой образ — хоть Stapel, хоть внешний по имени/тегу. - Забыть про
after/before, из-за чего импортированные файлы «встраиваются» на неожидаемой стадии. Импорт применяется к определённой стадии целевого образа — если импортированные файлы нужны доsetup(например, для генерации конфигурации на основе бинарника), а указаноafter: setup, финальная стадия не увидит импортированные файлы. - Импортировать избыточно много (всю директорию сборки) вместо конкретного артефакта. Без
includePaths/точногоadd:в целевой образ может попасть больше, чем нужно (промежуточные объектные файлы, кэш пакетного менеджера сборочного окружения).
Практика в кластере
Практики в кластере в этом уроке нет — фокус на механике сборки.
Практика разработки
- Перепишите
werf.yamlVikunja так, чтобы backend и frontend собирались как отдельные вспомогательные Stapel-образы, а финальный runtime-образ собирал их черезimport(без компилятора Go иnode_modulesв финальном слое):
| |
- Соберите итоговый образ и сравните его размер с гипотетическим вариантом «всё в одном образе с компилятором и npm»:
| |
Убедитесь, что ни go, ни npm не присутствуют в финальном runtime-образе — только бинарник и статика.
Шпаргалка команд урока
| |
Вопросы для самопроверки
import — прямое копирование файлов в целевой Stapel-образ; dependencies/imports через targetBuildArg — передача метаданных об образе для использования внутри Dockerfile.
Источник: [Werf — документация](https://ru.werf.io/docs/v2/usage/build/stapel/imports.html vs usage/build/images.html)
При установке-сборке-удалении в одном образе файлы всё равно физически присутствуют в слоях (copy-on-write); builder+import гарантирует, что компилятор физически никогда не попадёт в целевой образ.
after/before указывают точку встраивания импортированных файлов в конвейер стадий целевого образа — install или setup.
Источник: Werf — документация
import: — список, каждый элемент которого независимо указывает source image и пути — типичный случай для приложений с backend+frontend builder'ами.
Источник: Werf — документация
import поддерживает как импорт из образов текущего проекта, так и из внешних образов по image_name:tag/image_name@digest. Ограничение в другом: целевым (принимающим import) может быть только Stapel-образ, а не Dockerfile-образ — для линковки с Dockerfile нужен dependencies/imports+COPY --from.
Рекомендуемая литература
Официальная документация
Статьи и блоги
- werf v1.2 — стабильный релиз утилиты для доставки приложений в Kubernetes (Stapel) — Flant на Habr.
- Первые шаги с werf: собираем и деплоим простое приложение — Flant на Habr.
- GitLab + K8s + Werf — Habr.
Книги
- Nigel Poulton. Docker Deep Dive. 2023. ISBN 978-1916585206.
Связанные материалы
- Предыдущий урок: M3.04 — Линковка образов.
- Следующий урок: M3.06 — Секреты сборки и giterminism.
- Продакшен-уровень этого же паттерна: M8 урок 6 — Distroless и минимизация размера.
✓ Урок пройден — все вопросы самопроверки отвечены верно