О проекте
Сборщик OCI-образов, который использует altflow/altflow как backend, а repoteka в качестве инструмента проверки обновлений бинарных пакетов. Концепция работы altflow описана в документации altflow.
Проект во многом вдохновлён cloud/image-forge. Цели:
- автоматизировать процесс сборки OCI-образов для целевого
Registry; - дать широкому кругу контрибьютеров понятный алгоритм, чтобы они могли участвовать в добавлении новых
восстребованных образов в автоматизированный пайплайн
flowforge.
Полная документация проекта flowforge
Периодическая пересборка образов
Flowforge CI с заданной периодичностью сравнивает зафиксированные в репозитории версии бинарных пакетов с
актуальной информацией от repoteka.mskdc.altlinux.org. Образы, бинарные пакеты которых обнововились, отправляются
на пересборку. Образы, чьи source-образы обновились, также отправляются на пересборку.
Состояние последних успешно обработанных сборок хранится в ветках *-artefacts.
Там же хранится служебное состояние пересборок, вызванных обновлением source-образов.
Подробнее об этом написано в org/README.md.
Участие в добавлении новых образов в flowforge
Вы имеете право предложить новые образы в автоматизированный пайплайн flowforge. В первую очередь заведите новую
задачу в Issues (Задачах) репозитория с обоснованием необходимости добавления новых образов.
После появления задачи либо один из контрибьютеров flowforge может согласиться её выполнить, либо вы можете
выполнить задачу самостоятельно. В последнем случае следуйте инструкции:
- Сделайте форк текущего репозитория.
- Дополните одну или несколько организаций каталога
org/спецификациями новых образов, которые вы предлагаете добавить. Спецификация образа содержит:
- Dockerfile.template
- altflow-test
- info.yaml *
- README.md (желательно)
- Сделайте Pull Request (PR) в ветку main данного основного репозитория, запустятся базовые тесты.
- После выполенения первичных тестов мейнтейнер
flowforgeвручную должен запустить тестовую сборку образов, если вы вносили изменения вorg. Если запущенная тестовая сборка завершится красными индикаторами, изучите логи сборки в PR и поправьте ошибки в файлах спецификации. - Когда PR успешно прошёл полный prepare-deploy цикл для ревью подключаются мейнтейнеры репозитория. Поправьте ваши коммиты по их замечаниям.
- При выполнении всех условий мейнтейнер отправляет ваши коммиты в ветку main.
- CI flowforge автоматически запустит сборку образов и отправит их в целевой
Registry.
Примечание: Рекомендуем изучить корректное заполнении info.yaml и правила создания distroless-образов на базе flowforge в полной документации проекта.
Поддержка архитектур
В настоящее время поддерживаются следующие архитектуры: x86_64, aarch64, i586, loongarch64, riscv64
Данное ограничение связано с возможностями сборочницы OCI-образов altflow.
Локальная разработка
Основные положения
PR с функциональными изменениями скорее всего не будут приняты. Исключения возможны.
- Если есть предложение по улучшению, создайте Issue и опишите его.
- Если есть багрепорт, создайте Issue с описанием проблемы. Будет полезно, если вы опишете способ воспроизведения.
- [В БУДУЩЕМ] Если PR не выглядит poorly vibecoded, изменения с использованием AI могут быть приняты. AI можно использовать для PR, но будьте готовы обсуждать и защищать каждое предложенное изменение.
Ожидаемые рабочие конфигурации
Организации: alt, base, k8s-core, k8s-extra, kubevirt
Архитектуры: amd64, arm64
Ветки: sisyphus
Зависимости
python3-module-aiohttppython3-module-pydanticpython3-module-pydantic-settingspython3-module-logurupython3-module-jinja2python3-module-yaml
Использование
Переменные среды, которые использует скрипт:
DRY-> включает dry-run режимTEST-> отправляет задания в altflow без тегов. Финальный push в registry становится фиктивнымEMAIL_NOTIFICATION-> включает email-уведомления об ошибках сборкиEMAIL_TO-> список получателей email-уведомлений. Можно перечислять через запятую или пробелLOG_LEVEL-> уровень логированияALTFLOW_URL-> URL экземпляра altflowALTFLOW_AUTH-> токен авторизации для altflowSOT_REPO-> Source Of Truth репозиторий. Из этого репозитория altflow забирает спецификацию образаSOT_BRANCH-> Source Of Truth ветка. В неё рендерятся Dockerfile перед тем, как altflow их прочитаетSOT_FORCE_PUSH-> разрешает force push вSOT_BRANCHREGISTRY_URL-> адрес целевого registryREGISTRY_AUTH-> авторизация дляREGISTRY_URLSOURCE_REGISTRY-> registry, из которого берутся базовые образыREPOTEKA_URL-> URL экземпляра repotekaREPOTEKA_COMPONENT-> компонент repotekaREPOTEKA_SSL_VERIFY-> включает SSL verify для запросов в repotekaREPOTEKA_CONCURRENCY-> максимальное количество одновременных запросов к repotekaSMTP_SERVER-> SMTP-сервер для email-уведомленийSMTP_PORT-> SMTP-порт для email-уведомленийSMTP_FROM-> адрес отправителя email-уведомленийSMTP_USERNAME-> SMTP-пользователь для email-уведомленийSMTP_PASSWORD-> SMTP-пароль для email-уведомленийSMTP_SSL-> включает implicit TLS для SMTP. Для порта465включается автоматически
Подробнее в config.py
Доступные CLI-аргументы:
$ ./flowforge.py --help
Или можно посмотреть их в flowforge.py
Собрать один образ:
$ ./flowforge.py -i alt/redis
Собрать группу образов организации:
$ ./flowforge.py -o alt
Принудительно пересобрать образ, даже если сохранённые артефакты показывают, что он актуален:
$ ./flowforge.py -f -i alt/redis
Флаг -f не делает reset ветки *-artefacts, но весь выбранный набор образов проходит обычный цикл сборки
с учётом уровней source_images. Dockerfile.<branch> заново рендерится и пушится в SOT_BRANCH, а после
успешной сборки обновляется соответствующий info.*.yaml. Если в выбранном наборе есть source-образы и
зависимые от них образы, artefacts-состояние обновится для обеих групп.
Отправить тестовую сборку без публикации тегов в registry:
$ TEST=1 ./flowforge.py -i alt/redis
Все примеры предполагают, что нужные переменные среды уже экспортированы.
Source Of Truth ветка
flowforge не отправляет локальное содержимое Dockerfile напрямую в altflow. Он рендерит Dockerfile.<branch>
в SOT_BRANCH, пушит эту ветку, а затем отправляет в altflow SOT_REPO, SOT_BRANCH и путь к Dockerfile.
Если SOT_BRANCH не задана, она вычисляется как <current-branch>-artefacts.
Подробнее о роли *-artefacts, info.*.yaml и PR-веток ci/pr-<number>-artefacts написано в
org/README.md.
CI secrets
CI jobs используют secret FLOWFORGE_CI_TOKEN, чтобы пушить PR artefacts-ветки в altlinux.space.
Токену нужны права repository read/write. Для cron rebuild используется тот же FLOWFORGE_CI_TOKEN.
Repository задаётся обязательной CI variable FLOWFORGE_CI_REPO, например
altlinux.space/cloud/flowforge.git без схемы.
Доступ к registry передаётся отдельно через REGISTRY_AUTH.
Если включены email-уведомления, SMTP может работать в трёх режимах:
- plain SMTP без
STARTTLS; - SMTP с
STARTTLS, если сервер объявляет такую возможность; - implicit TLS через
SMTP_SSL=1или порт465.
Если EMAIL_TO не задан, уведомления никому не отправляются.
Если SMTP_FROM не задан, используется SMTP_USERNAME; если не задано и оно, email-уведомления отключаются.
Формат спецификаций в org
В директории образа в обязательном порядке должны находится следующие файлы:
Dockerfile.templateinfo.yamlaltflow-test
Файлы, подобные Dockerfile.sisyphus и info.sisyphus.yaml, являются автоматически сгенерированными артефактами и не должны попадать
в основную ветку репозитория.
Рабочие ветки и *-artefacts
Рабочая ветка содержит только исходную спецификацию образа:
Dockerfile.templateinfo.yamlaltflow-test- дополнительные файлы, которые нужны Dockerfile
Во время сборки flowforge переключается в SOT_BRANCH, обычно <branch>-artefacts, и создаёт там
сгенерированные файлы:
Dockerfile.<branch>info.<branch>.yaml- для versioned k8s-образов:
info.<branch>.<version>.yaml
Скрипт формирует и добавляет файлы Dockerfile.<branch> в ветку <branch>-artefacts до отправки заданий на сборку в altflow,
так как altflow ориентируется на данные файлы при сборке. Общая схема работы altflow описана в
документации altflow.
В artefacts-ветке info.*.yaml хранит зафиксированные версии бинарных пакетов, полученные из repoteka после успешной сборки.
При следующем запуске flowforge сравнивает эти версии с актуальными версиями пакетов и решает, какие образы устарели.
Из этого следует правило:
- в рабочую ветку нельзя добавлять
Dockerfile.<branch>иinfo.*.yaml; - в artefacts-ветке эти файлы должны сохраняться, потому что это source of truth для rebuild-логики;
- если меняется
info.yaml, cron rebuild подтягивает рабочую ветку в соответствующую artefacts-ветку merge'ом, чтобы сохранить старые слепки версий и обновить шаблоны.
Важно различать два сценария:
- cron rebuild должен сохранять существующие
info.*.yaml, потому что они нужны для сравнения версий пакетов; - PR-проверка новых образов постоянно пересоздаёт
ci/pr-<number>-artefacts, потому что проверяет только новые спецификации из diff.
is_versioned
Бинарная спец опция для k8s-core, которая позволяет обозначить, что есть несколько версий пакета который нужно собрать.
Версии указываются в cfg/k8s-versions.json
Возможно в дальнейшем, опция будет расширена по функционалу, например для подержки сборки нескольких версий одного пакета вне k8s-core
target_package
Обязательная опция в конфигурации всех образов, кроме перечисленных в org/base. Может быть null или названием бинарного пакета.
Предназначение
От этого пакета будет унаследована версия в тэге образа. При null будет выставлен тэг соответсвующий дате сборки.
Для образов, которые собираются через DistrolessImage, версия target_package сейчас не
используется как тег. Такие образы получают теги latest и дату запуска в формате YYYYMMDD.
binary_packages
Опция которая содержит список бинарных пакетов, зависимость от которых есть у данного образа.
Предназначение
- Слепки версий при сборке будут храниться в репозитории, для обнаружения обновлений, и устранения пересборки не устаревших образов.
- Если в
Dockerfile.templateиспользуется{{ install_packages() }}без аргументов, будут установлены пакеты изbinary_packages.
Если install_packages() вызван с явными аргументами, устанавливаются именно эти аргументы, а не перечисленные в binary_packages.
Если binary_packages указан в info.yaml, но Dockerfile.template не вызывает install_packages(),
а builder.reinstall_packages и rootfs.packages не используют макрос @binary_packages для отсылки к списку binary_packages,
список используется только для проверки версий и обновления artefacts. Это справедливо для обычных образов из org/alt,
org/k8s-extra, org/kubevirt, для K8sImage из org/k8s-core, а также для недистролессных образов из org/base.
source_images
Локальные source-образы, которые flowforge должен учитывать как зависимости сборки.
Предназначение
- Проверка, что зависимость от source-образа известна flowforge
- Расчёт уровней сборки по графу зависимостей
- Пересборка зависимого образа, если его source-образ был собран в текущем запуске
В info.*.yaml flowforge может добавить служебный флаг source_images_updated.
Он означает, что образ уже был поставлен в очередь из-за обновления source-образа, но ещё не был успешно собран.
Вручную задавать этот флаг в info.yaml не нужно.
Если образ наследуется от обычного {{ registry }}{{ branch }}/{{ alt_image }}:latest, указывается:
source_images:
- alt
Если образ наследуется от другого образа из org и этот образ тоже собирается flowforge,
указывается canonical name:
source_images:
- base/distroless-python3
Неизвестный в рамках flowforge source image считается ошибкой.
exclude_archs
Опциональный список архитектур, для которых образ не нужно отправлять в altflow.
Возможные исключения: x86_64, aarch64, i586, loongarch64, riscv64.
Для org/k8s-core рядом с архитектурой можно через запятую указать версии Kubernetes, для которых действует исключение.
Пример:
exclude_archs:
- riscv64
- loongarch64,1.33,1.34
exclude_branches
Опциональный список веток пакетной базы, для которых образ не нужно собирать. Исключение применяется до запросов к repoteka и до отправки задач в altflow.
Пример:
exclude_branches:
- p10
- c10f1
annotations
Опциональный словарь OCI annotations, который отправляется в altflow вместе с build payload.
Пример:
annotations:
org.opencontainers.image.source: 'https://github.com/kubevirt/kubevirt'
org.opencontainers.image.licenses: Apache-2.0
org.opencontainers.image.vendor: 'ALT Linux Team'
altflow_context
Опциональный раздел для образов, где контекст для сборки образа (build context или buildContextPath) лежит в другом git-репозитории. Dockerfile при этом всё равно генерируется автоматически алгоритмом flowforge на основе конфигурационных файлов и не зависит от базового репозитория с основной кодовой базой.
Пример:
altflow_context:
buildContextPath: "."
buildGitBranch: "alt"
git_url: "https://altlinux.space/cloud/cilium-envoy.git"
distroless
Опциональный раздел для образов, которые должны собираться через DistrolessImage.
В org/k8s-core этот renderer пока не используется: там один каталог разворачивается в
несколько Kubernetes-версий через K8sImage.
Основные секции:
from-> базовый образ финальной стадии. По умолчаниюscratchbuilder.reinstall_packages-> пакеты, которые должны быть установлены в builder-стадииrootfs.files-> файлы, которые надо положить в rootfs без поиска библиотекrootfs.library_files-> файлы, для которых надо добавить runtime librariesrootfs.packages-> пакеты, файлы которых надо добавить в rootfsrootfs.library_packages-> пакеты, для файлов которых надо добавить runtime librariesrootfs.timezone-> timezone, для которой будет создан/etc/localtimeenv->ENVв финальном образеuser->USERв финальном образеworkdirилиworkingdir->WORKDIRв финальном образеentrypoint-> JSONENTRYPOINTcmd-> JSONCMD
В builder.reinstall_packages и rootfs.packages указываются пакеты, которые нужны
distroless-сборке:
distroless:
builder:
reinstall_packages:
- python3
- python3-base
rootfs:
packages:
- python3
- python3-base
altflow-test
Файл altflow-test обязателен в каждом каталоге образа. Файл не должен быть пустым,
а его содержимое должно быть JSON-списком списков.
Минимально допустимый формат:
[]
Для образов, где необходимо протестировать один etrtypoint, допустим формат:
[
"/usr/bin/python3",
"--version"
]
Для тестирования нескольких etrypoints, используйте:
[
[
"/usr/bin/python3",
"--version"
],
[
"/usr/bin/pytest3",
"--version"
]
]
В случаях, когда образ собирается для нескольких архитектур и для каждой требуется уникальное тестирование, используйте формат:
{
"amd64": [
[
"/usr/lib64/ld-linux-x86-64.so.2",
"--help"
]
],
"arm64": [
[
"/usr/lib64/ld-linux-aarch64.so.1",
"--help"
]
],
"all": [
[
"/usr/lib/ld-linux.so.2",
"--help"
]
]
}
Маркер all здесь буквально говорит алгоритму протестируй следующие entrypoints для всех оставшихся не указанных явно архитектур.
Каждый вложенный список является командой для altflow test entrypoint: первый элемент - executable, остальные элементы - аргументы.
Для distroless и controller-like образов лучше проверять существование entrypoint или использовать безопасные --version/--help,
которые не требуют kubernetes cluster и не запускают бесконечный процесс.
Как создать distroless-образ в flowforge
Этот раздел описывает, как создавать distroless-образы в flowforge на базе существующих
спецификаций из org/base.
Distroless-образ в flowforge - это OCI-образ, в котором финальный rootfs собирается не через
обычную установку пакетов в финальный слой, а через промежуточный builder-образ и tar-архив
с минимальным набором файлов. В финальный образ попадает только то, что явно указано в
info.yaml и Dockerfile.template.
Общий формат спецификации образа описан в разделе
Формат спецификаций в org. Distroless добавляет к нему секцию
distroless в info.yaml и несколько helper'ов для Dockerfile.template.
В flowforge есть два способа получить distroless-подобный образ:
- использовать renderer
DistrolessImage; он включается, если вinfo.yamlесть секцияdistrolessи организация не являетсяk8s-core; - написать обычный
Dockerfile.template, который наследуется от уже собранного distroless-образа, например{{ registry }}{{ branch }}/distroless-base:latest.
В первом случае главным становится info.yaml: секция distroless передаёт renderer'у описание
rootfs, окружения, entrypoint и базового образа. Во втором случае образ остаётся обычным
AltImage или K8sImage, а distroless-поведение задаётся самим Dockerfile.
Где смотреть примеры
Актуальные примеры находятся в org/base:
distroless-static- минимальный rootfs отscratch;distroless-base- динамический runtime с glibc, timezone и базовыми библиотеками;distroless-cc- C/C++ runtime поверхdistroless-base;distroless-python3- Python runtime поверхdistroless-cc;distroless-true,distroless-toybox,distroless-gotop- небольшие прикладные примеры;distroless-devel- отладочный образ с shell и набором инструментов;distroless-busybox- пример custom Dockerfile поверх distroless-base.
Вне org/base тоже есть образы, которые используют distroless как runtime base. Например
org/k8s-extra/kube-rbac-proxy, org/k8s-extra/hubble-ui-backend, org/k8s-extra/spire-agent
и org/alt/bldr наследуются от distroless-base или distroless-static. Если в их
info.yaml добавить секцию distroless, flowforge будет рендерить их через DistrolessImage.
Если поведение документации расходится с кодом, точкой истины является core/image/distroless.py
и текущие спецификации в org.
Простейший distroless-образ
Начинать лучше с маленького образа, который запускает один бинарник. В flowforge такой образ
удобно делать в org/base, потому что там уже лежит базовая distroless-цепочка. Сам
DistrolessImage выбирается не по организации, а по секции distroless в info.yaml.
Реальный минимальный пример уже есть в репозитории: org/base/distroless-true. Он кладёт
в rootfs /bin/true из пакета coreutils и запускает его.
Каталог
org/base/distroless-true/
Dockerfile.template
altflow-test
info.yaml
info.yaml
---
is_versioned: false
binary_packages:
- coreutils
source_images:
- base/distroless-builder
- base/distroless-static
distroless:
from: "{{ registry }}{{ branch }}/distroless-static:latest"
builder:
reinstall_packages:
- coreutils
rootfs:
files:
- /bin/true
cmd:
- /bin/true
annotations:
org.opencontainers.image.revision: ""
org.opencontainers.image.source: ""
org.opencontainers.image.url: ""
org.opencontainers.image.version: ""
org.opencontainers.image.title: "distroless-true"
org.opencontainers.image.description: "True-command image for zero status returning"
org.opencontainers.image.licenses: GPLv2
org.opencontainers.image.vendor: "ALT Linux Team"
...
Разбор по смыслу:
is_versioned: false,binary_packagesиsource_images- общие поля спецификации. Их смысл описан в формате спецификаций. Дляdistroless-trueважная практическая связь такая:coreutilsотслеживается как пакет-зависимость, аbase/distroless-builderиbase/distroless-staticзадают порядок сборки.distroless.fromзадаёт финальныйFROMчерез функциюdistroless_from(). Если не указано иное, функция строит итоговый образ отscratch. В этом примере итоговый образ строится поверхdistroless-static, что указано явно.distroless.builder.reinstall_packagesперечисляет пакеты, которые надо поставить или переустановить в builder-стадии перед извлечением файлов. Здесь снова указанcoreutils, но смысл другой: это не проверка актуальности, а подготовка файла/bin/trueдля rootfs.distroless.rootfs.filesговоритdistroless-builder.py, какие конкретные файлы забрать из builder-стадии. Дляtrueдополнительные библиотеки искать не нужно.distroless.cmdзадаётCMDфинального образа.annotationsзадаёт OCI metadata. Это общее поле, но его удобно видеть рядом с примером.
coreutils повторяется намеренно. В binary_packages он описывает зависимость образа. В
distroless.builder.reinstall_packages он уже управляет сборкой: пакет должен быть установлен
в builder-стадии, иначе distroless-builder.py не сможет положить /bin/true в rootfs.
В минимальном примере это один и тот же пакет, но в более сложных образах список зависимостей
образа и список пакетов для подготовки rootfs могут отличаться.
Dockerfile.template
Минимальный шаблон обычно повторяет схему существующих distroless-образов:
FROM {{ registry }}{{ branch }}/distroless-builder:latest AS builder
WORKDIR /usr/src/distroless
{{ distroless_base_rootfs() }}
{{ distroless_reinstall() }}
{{ distroless_add() }}
FROM {{ distroless_from() }}
COPY --from=builder /rootfs/ /
{{ distroless_config() }}
LABEL org.opencontainers.image.title="distroless-true" \
org.opencontainers.image.description="True-command image for zero status returning" \
org.opencontainers.image.licenses="GPLv2" \
org.opencontainers.image.vendor="ALT Linux Team"
Шаблон короткий, потому что основная логика спрятана в helper'ах renderer'а DistrolessImage.
Строки читаются так:
- первый
FROMзапускает builder-стадию изdistroless-builder; WORKDIRвыбирает каталог, где лежитdistroless-builder.py;distroless_base_rootfs()распаковывает базовый rootfs в/basefs, чтобы потом не добавлять в новый слой файлы, которые уже есть в итоговом указанном вdistroless.fromобразе;distroless_reinstall()ставит или переустанавливает пакеты изdistroless.builder.reinstall_packages;distroless_add()вызываетdistroless-builder.py add, создаётdistroless.tar, распаковывает его в/rootfsи удаляет дубли относительно/basefs;- второй
FROMберёт финальный базовый образ изdistroless.from; COPY --from=builder /rootfs/ /переносит подготовленный минимальный rootfs в финальный образ;distroless_config()добавляетENV,USER,WORKDIR,ENTRYPOINT,CMDиз секцииdistroless;LABELдобавляет OCI metadata в сам образ.
distroless.tar
distroless.tar - временный tar-архив rootfs, который создаётся только в builder-стадии.
Его создаёт distroless-builder.py:
distroless-builder.py add ...получает правила изdistroless.rootfs: конкретные файлы, файлы из rpm-пакетов, библиотеки бинарников, списки файлов.- Скрипт сохраняет список найденных путей во внутреннем рабочем состоянии.
distroless-builder.py tar -o distroless.tarупаковывает эти пути в tar-архив.- Dockerfile распаковывает архив командой
tar -C /rootfs -xf distroless.tar. - Финальная стадия копирует
/rootfsв образ черезCOPY --from=builder /rootfs/ /.
В distroless-true архив содержит файл /bin/true, нормализованный под ALT usrmerge. На
практике это означает, что основной файл оказывается в /usr/bin/true, а путь /bin в
итоговом rootfs остаётся совместимым symlink'ом.
altflow-test
Общий формат файла описан в спецификации образов. Для distroless-образов тест должен быть безопасным: команда должна быстро завершаться и не требовать shell, сети или Kubernetes API, если shell не добавлен в образ явно.
[
[
"/bin/true"
]
]
Если в образе нет /bin/true, не используйте его в тесте. Проверяйте реальный entrypoint
или другой файл, который действительно попадает в rootfs.
Pipeline сборки distroless
Distroless pipeline в flowforge состоит из двух уровней:
- общий pipeline flowforge, описанный в разделах О проекте и Формат спецификаций в org;
- distroless renderer: преобразование секции
distrolessв команды builder-стадии и финального Dockerfile.
Как flowforge выбирает DistrolessImage
Для большинства организаций используется selector BaseImage. Он смотрит на info.yaml: если
там есть секция distroless, образ создаётся как DistrolessImage; иначе используется обычный
AltImage.
Из этого следует правило: для нового distroless-образа нужна секция:
distroless:
...
Одного имени каталога distroless-* недостаточно.
Исключение сейчас одно: org/k8s-core. Там используется K8sImage, потому что один каталог
разворачивается в несколько Kubernetes-версий из cfg/k8s-versions.json.
В config.py сейчас указано:
ORG_STATE = {
"alt": BaseImage,
"base": BaseImage,
"k8s-core": K8sImage,
"k8s-extra": BaseImage,
"kubevirt": BaseImage,
}
Если секции distroless нет, образ остаётся обычным AltImage. Он всё равно может
наследоваться от distroless runtime через custom Dockerfile.template:
FROM {{ registry }}{{ branch }}/{{ alt_image }}:latest AS prepare
{{ install_packages("kube-rbac-proxy") }}
RUN mkdir -p /rootfs/usr/bin && \
cp -aL /usr/bin/kube-rbac-proxy /rootfs/usr/bin/kube-rbac-proxy
FROM {{ registry }}{{ branch }}/distroless-base:latest
COPY --from=prepare /rootfs /
ENTRYPOINT ["/usr/bin/kube-rbac-proxy"]
При этом source_images всё равно должен указывать distroless base:
source_images:
- alt
- base/distroless-base
Так flowforge понимает порядок сборки и пересобирает зависимый образ после обновления
base/distroless-base.
Если промежуточный образ приходит из registry и не собирается flowforge, в source_images
его не добавляют. Например, cilium может делать FROM {{ registry }}{{ branch }}/cilium-envoy:latest AS envoy,
но если cilium-envoy собирается отдельно вручную, зависимость фиксируется только в
Dockerfile.template, а не в source_images.
Базовая цепочка
Текущая практическая цепочка в org/base такая:
alt
-> base/distroless-builder
-> base/distroless-static
-> base/distroless-base
-> base/distroless-cc
-> base/distroless-python3
Параллельно существуют прикладные ветки:
base/distroless-static
-> base/distroless-true
-> base/distroless-toybox
base/distroless-base
-> base/distroless-gotop
-> base/distroless-devel
-> base/distroless-busybox
source_images фиксирует эту зависимость для flowforge. Общий алгоритм очереди сборки описан
в спецификации образов.
distroless-builder
base/distroless-builder - служебный образ. Он основан на обычном alt и содержит:
python3;glibc-utils;apt-repo;- скрипт
distroless-builder.py.
Именно из него строятся остальные distroless-образы. Финальный distroless rootfs создаётся в builder-стадии, а затем копируется в финальный образ.
distroless-builder.py
Скрипт distroless-builder.py ведёт список файлов и собирает tar-архив rootfs.
Поддерживаемый CLI:
./distroless-builder.py add ...
./distroless-builder.py tar -o distroless.tar
./distroless-builder.py clean
distroless.tar появляется на шаге tar: это временный архив с файлами, которые были
выбраны предыдущими вызовами add. Сам по себе он не является базовым образом и не хранится
в репозитории. Dockerfile распаковывает его в /rootfs, а затем копирует /rootfs в
финальную стадию образа.
add умеет принимать:
-f/--files- конкретные файлы;-p/--packages- все обычные файлы из rpm-пакетов;--library-files- библиотеки, найденные черезlddдля указанных бинарников;--library-packages- rpm-пакеты, которым принадлежат библиотеки указанных бинарников;--clean- очистить старый список перед добавлением.
При упаковке distroless-builder.py учитывает ALT usrmerge:
/bin/...попадает в архив какusr/bin/...;/lib/...попадает какusr/lib/...;/lib64/...попадает какusr/lib64/...;/sbin/...попадает какusr/sbin/...;- при необходимости добавляются symlink'и
bin -> usr/bin,lib -> usr/lib,lib64 -> usr/lib64,sbin -> usr/sbin.
Это важно для distroless-образов ALT Linux: основные файлы должны жить в /usr, а legacy
пути в корне должны быть symlink'ами.
Что делает renderer DistrolessImage
DistrolessImage читает distroless из info.yaml и готовит helper'ы для Jinja-шаблона:
distroless_from();distroless_base_rootfs();distroless_reinstall();distroless_add();distroless_config().
Типовой Dockerfile сначала строит /rootfs в builder-стадии:
FROM {{ registry }}{{ branch }}/distroless-builder:latest AS builder
WORKDIR /usr/src/distroless
{{ distroless_base_rootfs() }}
{{ distroless_reinstall() }}
{{ distroless_add() }}
Потом копирует /rootfs в финальный образ:
FROM {{ distroless_from() }}
COPY --from=builder /rootfs/ /
{{ distroless_config() }}
Если distroless.from не scratch, renderer копирует базовый rootfs в /basefs и после
создания нового /rootfs удаляет из него файлы, которые совпадают с базовым образом. Это
уменьшает повторное включение одинаковых файлов в новый слой.
Если distroless.from равен scratch или не задан, distroless_base_rootfs() не нужен
для создания /basefs, так как впоследствии дедупликация не будет выполняться.
Связь с distroless.tar такая: этот архив создаёт новый rootfs-добавок, а /basefs используется
только как эталон для сравнения. Если файл из распакованного distroless.tar уже есть в
/basefs и совпадает по содержимому, renderer удаляет его из /rootfs; если файла нет или
он отличается, файл остаётся и попадёт в новый слой.
Секция distroless в info.yaml
Общие поля info.yaml описаны в разделе
Формат спецификаций в org. Здесь перечислены только поля секции
distroless, которые управляют сборкой минимального rootfs.
Наличие секции distroless включает DistrolessImage во всех организациях, кроме
org/k8s-core.
from
Финальный базовый образ:
distroless:
from: "{{ registry }}{{ branch }}/distroless-base:latest"
Если from не задан, используется scratch.
Примеры:
scratchвdistroless-static;distroless-staticвdistroless-base;distroless-baseвdistroless-cc,distroless-gotop,distroless-devel;distroless-ccвdistroless-python3.
builder.reinstall_packages
Пакеты, которые надо установить в builder-стадии перед сборкой rootfs:
distroless:
builder:
reinstall_packages:
- gotop
Renderer превращает это в apt-get install -y ....
Несмотря на имя reinstall_packages, текущий renderer вызывает apt-get install, а не
apt-get reinstall. Название поля сохраняет исторический смысл: обеспечить наличие свежих
файлов пакетов в builder-стадии.
builder.install_packages
Поле встречается в distroless-builder и distroless-busybox, но стандартный helper
distroless_reinstall() его не использует. Эти образы имеют custom Dockerfile.template,
который сам вызывает обычный helper install_packages(...).
Для нового стандартного distroless-образа обычно используйте builder.reinstall_packages.
rootfs.files
Добавляет конкретные файлы в rootfs:
rootfs:
files:
- /bin/true
Symlink'и по умолчанию разворачиваются с учётом назначения. Для библиотек бинарника это поле
не подходит: используйте library_files или full_files.
rootfs.library_files
Добавляет указанные файлы и библиотеки, найденные через ldd:
rootfs:
library_files:
- /usr/bin/vim
Если бинарник статический или ldd не может его обработать, сборка упадёт. Для таких файлов
используйте files.
rootfs.full_files
Удобное сокращение: файл попадёт и в files, и в library_files.
rootfs:
full_files:
- /usr/bin/python3
Это нужно для динамически связанных исполняемых файлов: сам бинарник попадёт в rootfs, а
его runtime libraries будут найдены через ldd.
rootfs.packages
Добавляет обычные файлы из rpm-пакетов:
rootfs:
packages:
- glibc-core
- tzdata
rootfs.packages нужен, когда нужно забрать из rpm-пакета обычные runtime-файлы целиком, а
не перечислять их вручную. Это удобно для пакетов, где важны не один-два бинарника, а набор
данных, конфигов, сертификатов, Python-модулей, timezone-файлов, metadata и т.п.
Разница такая:
rootfs.files- забрать конкретные файлы. Пример:/bin/true,/etc/localtime.rootfs.library_files- забрать указанный бинарник/файл и библиотеки, которые нужны ему поldd. Пример:/usr/bin/gotop.rootfs.packages- забрать обычные файлы из перечисленных rpm-пакетов. Это шире и грубее, зато надёжнее для runtime-пакетов со множеством связанных файлов.rootfs.full_files- короткая запись для динамического исполняемого файла: указанный путь одновременно попадает вrootfs.filesи вrootfs.library_files. В результате в rootfs попадает сам файл и библиотеки, которые нужны ему поldd.
Например, для distroless-python3 rootfs.packages оправдан: Python runtime это не только
/usr/bin/python3 и .so из ldd. Нужны стандартная библиотека, Python modules, CA/cert
files и runtime data. Перечислять это вручную через files было бы хрупко.
Для distroless-true наоборот rootfs.packages не нужен: достаточно одного /bin/true, и
библиотеки не нужны.
Практическое правило:
- один бинарник или один конфиг ->
rootfs.files; - бинарник с динамическими библиотеками ->
rootfs.library_files; - runtime-пакет с деревом файлов ->
rootfs.packages; - distroless-слой, где пакет целиком и есть смысл слоя ->
rootfs.packages.
rootfs.library_packages
Добавляет пакеты, которым принадлежат библиотеки указанных бинарников:
rootfs:
library_packages:
- /usr/bin/my-binary
Это специализированный механизм. Он вызывает ldd, затем rpm -qf для найденных библиотек.
rootfs.timezone
Создаёт /etc/localtime в builder-стадии и добавляет его в rootfs:
rootfs:
timezone: Europe/Moscow
Так сделан distroless-base.
env
Рендерится в ENV:
env:
LANG: C.UTF-8
SSL_CERT_FILE: /etc/pki/tls/certs/ca-bundle.crt
user
Рендерится в USER:
user: nonroot
Пользователь должен существовать в rootfs или базовом образе. В цепочке flowforge пользователь
nonroot создаётся в distroless-builder и попадает в distroless-static.
workdir и workingdir
Оба имени поддерживаются. Рендерятся в WORKDIR:
workdir: /home/nonroot
entrypoint
JSON ENTRYPOINT финального образа:
entrypoint:
- /usr/bin/python3
cmd
JSON CMD финального образа:
cmd:
- /bin/bash
Для distroless-образов shell-form использовать нельзя, если shell не добавлен в rootfs явно.
Примеры из org/base
Этот раздел показывает, какие паттерны уже используются в org/base.
distroless-static
distroless-static строится от scratch и задаёт самый маленький базовый rootfs:
binary_packages:
source_images:
- base/distroless-builder
distroless:
from: scratch
rootfs:
files:
- /bin
- /etc
- /usr
- /var
# в оригинальном файле список файлов намного длиннее
user: nonroot
workdir: /home/nonroot
Главная идея: rootfs задаётся минимальным набором каталогов и системных файлов. Этот образ подходит для статически связанных программ или как основа для образов, которым не нужен glibc runtime.
distroless-base
distroless-base добавляет динамический runtime:
binary_packages:
- glibc-core
- glibc-pthread
- glibc-timezones
- libselinux
- libssl3
- tzdata
- zlib
source_images:
- base/distroless-builder
- base/distroless-static
distroless:
from: "{{ registry }}{{ branch }}/distroless-static:latest"
rootfs:
packages:
- glibc-core
- glibc-pthread
- glibc-timezones
- tzdata
- zlib
timezone: Europe/Moscow
Здесь появляется важный паттерн: binary_packages может быть шире, чем rootfs.packages.
Часть пакетов нужна для отслеживания обновлений и builder-стадии, но в rootfs может попадать
только выбранный набор файлов.
distroless-cc
distroless-cc добавляет C/C++ runtime-пакеты целиком:
В этом и следующем примере используется сокращение @binary_packages; его смысл описан в
формате спецификаций.
binary_packages:
- libgcc1
- libgomp1
- libstdc++6
source_images:
- base/distroless-builder
- base/distroless-base
distroless:
from: "{{ registry }}{{ branch }}/distroless-base:latest"
builder:
reinstall_packages: "@binary_packages"
rootfs:
packages: "@binary_packages"
Это хороший шаблон для runtime-слоя, где нужно добавить несколько rpm-пакетов целиком.
distroless-python3
Python runtime строится поверх distroless-cc:
binary_packages:
- ca-certificates
- ca-trust
- python3
- python3-base
- python3-modules-curses
- python3-modules-sqlite3
- libpython3
source_images:
- base/distroless-builder
- base/distroless-cc
distroless:
from: "{{ registry }}{{ branch }}/distroless-cc:latest"
builder:
reinstall_packages: "@binary_packages"
rootfs:
full_files:
- /usr/bin/python3
library_files:
- /usr/lib64/python3*/lib-dynload/*.so
packages: "@binary_packages"
env:
LANG: C.UTF-8
SSL_CERT_FILE: /etc/pki/tls/certs/ca-bundle.crt
user: nonroot
workdir: /home/nonroot
entrypoint:
- /usr/bin/python3
Здесь используются сразу три механизма rootfs:
full_filesдля/usr/bin/python3;library_filesдля Python extension modules;packagesдля файлов из runtime-пакетов, перечисленных вbinary_packages.
Dockerfile дополнительно создаёт symlink /usr/bin/python -> python3, если его нет.
distroless-gotop
Простой пример динамического бинарника:
binary_packages:
- gotop
source_images:
- base/distroless-builder
- base/distroless-base
distroless:
from: "{{ registry }}{{ branch }}/distroless-base:latest"
builder:
reinstall_packages:
- gotop
rootfs:
full_files:
- /usr/bin/gotop
entrypoint:
- /usr/bin/gotop
Для похожих утилит это один из лучших стартовых шаблонов.
distroless-devel
distroless-devel не является минимальным runtime. Это отладочный образ:
distroless:
from: "{{ registry }}{{ branch }}/distroless-base:latest"
rootfs:
full_files:
- /bin/bash
- /usr/bin/curl
- /usr/bin/gdb
files:
- /etc/alternatives/links/*
- /usr/bin/vim
library_files:
- /usr/bin/vim
- /usr/bin/ss
packages:
- coreutils
- findutils
- glibc-utils
- iproute2
cmd:
- /bin/bash
Используйте этот образ как пример, когда нужен shell, диагностика и инструменты. Не копируйте его как базу для production runtime без необходимости.
distroless-busybox
distroless-busybox отличается от стандартных distroless-образов. В нём есть секция
distroless, но Dockerfile написан вручную:
FROM {{ registry }}{{ branch }}/{{ alt_image }}:latest AS prepare
{{ install_packages("busybox") }}
RUN mkdir -p /rootfs/usr/bin && \
cp -aL /usr/bin/busybox /rootfs/usr/bin/busybox && \
/usr/bin/busybox --list | while read -r applet; do \
[ "$applet" != busybox ] || continue; \
ln -sf busybox "/rootfs/usr/bin/$applet"; \
done
FROM {{ registry }}{{ branch }}/distroless-base:latest
COPY --from=prepare /rootfs /
CMD ["/usr/bin/sh"]
Такой подход допустим, если helper'ов DistrolessImage недостаточно. Но для новых образов
сначала попробуйте стандартный шаблон с distroless_add().
Образы вне org/base на базе distroless
Не все образы, которые используют distroless runtime, обязаны создаваться через
DistrolessImage. Например org/k8s-extra/kube-rbac-proxy может оставаться обычным
AltImage, если в info.yaml нет секции distroless, но финальная стадия его Dockerfile
наследуется от distroless-base:
FROM {{ registry }}{{ branch }}/{{ alt_image }}:latest AS prepare
{{ install_packages("kube-rbac-proxy") }}
RUN mkdir -p /rootfs/usr/bin && cp -aL /usr/bin/kube-rbac-proxy /rootfs/usr/bin/kube-rbac-proxy
FROM {{ registry }}{{ branch }}/distroless-base:latest
COPY --from=prepare /rootfs /
ENTRYPOINT ["/usr/bin/kube-rbac-proxy"]
В info.yaml это фиксируется через source_images:
target_package: kube-rbac-proxy
binary_packages:
- kube-rbac-proxy
source_images:
- alt
- base/distroless-base
Такой способ подходит, если образу достаточно вручную скопировать несколько файлов из
prepare-стадии. Если нужен автоматический сбор rootfs по rpm-пакетам, ldd, ENV, USER,
WORKDIR, ENTRYPOINT и CMD из info.yaml, добавьте секцию distroless и используйте
renderer DistrolessImage.
Проверки и типичные ошибки
altflow-test
Общий формат altflow-test описан в спецификации образов.
Для distroless-образов важно учитывать состав rootfs:
- если shell не добавлен в rootfs, нельзя использовать
/bin/sh; - если бинарник не поддерживает
--helpили--version, не используйте эти флаги; - если entrypoint запускает daemon или controller, тест не должен зависать;
- для образов на
scratchпроверяйте только реально существующие бинарники.
Команды без shell
Distroless-образ не обязан содержать /bin/sh. Поэтому такие тесты часто неверны:
[
[
"/bin/sh",
"-ec",
"test -x /usr/bin/app"
]
]
Они подходят только для образов, где shell явно добавлен, например для distroless-devel,
distroless-toybox или distroless-busybox.
Динамический бинарник не запускается
Если бинарник есть, но запуск падает из-за отсутствующей .so, проверьте:
- добавлен ли бинарник через
full_filesилиlibrary_files; - поддерживает ли
lddэтот файл; - есть ли нужные библиотеки в пакетах из
binary_packages; - не нужен ли runtime-слой
distroless-baseилиdistroless-cc.
Для динамического исполняемого файла обычно нужен один из вариантов:
rootfs:
full_files:
- /usr/bin/app
или:
rootfs:
files:
- /usr/bin/app
library_files:
- /usr/bin/app
Когда нужен custom Dockerfile
Стандартный distroless-шаблон подходит для большинства runtime-образов. Custom Dockerfile нужен, если требуется нестандартная подготовка rootfs:
- создать symlink'и вручную;
- скопировать applet'ы busybox;
- выполнить дополнительные команды после
distroless_add(); - проверить или изменить файлы в
/rootfs.
Даже в custom Dockerfile секция distroless может быть полезна: она включает DistrolessImage
и даёт доступ к helper'ам renderer'а.