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; если файла нет или
он отличается, файл остаётся и попадёт в новый слой.