Простейший 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:

  1. distroless-builder.py add ... получает правила из distroless.rootfs: конкретные файлы, файлы из rpm-пакетов, библиотеки бинарников, списки файлов.
  2. Скрипт сохраняет список найденных путей во внутреннем рабочем состоянии.
  3. distroless-builder.py tar -o distroless.tar упаковывает эти пути в tar-архив.
  4. Dockerfile распаковывает архив командой tar -C /rootfs -xf distroless.tar.
  5. Финальная стадия копирует /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.