Distroless и минимизация размера

Уровень: expert ~45 мин Практика: Beszel (MIT)

Что нужно знать перед уроком

  • M3.05 — Stapel-импорты — нужна базовая модель builder+import из M3, здесь она доводится до продакшен-уровня
  • M8.05 — werf cleanup глубоко — продолжение серии продвинутых тем сборки

Что нужно знать перед уроком

M3 урок 5 — базовая модель импорта файлов между образами. Этот урок доводит паттерн builder+import до продакшен-уровня — полностью минимального финального образа без пакетного менеджера и shell.

Теория

Что такое distroless и зачем он нужен

Distroless-образ — это финальный образ контейнера, содержащий только сам бинарник приложения и минимально необходимый рантайм (например, CA-сертификаты для HTTPS-запросов, часовые пояса), но без пакетного менеджера (apt, apk), без shell (/bin/sh, /bin/bash), без стандартных Unix-утилит (ls, cat, curl) и вообще без дистрибутива Linux в привычном смысле. Классический пример базового образа для этого — gcr.io/distroless/* или scratch (полностью пустой образ).

Паттерн builder+import, доведённый до продакшен-уровня

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
project: beszel
configVersion: 1
---
image: builder
from: golang:1.26-alpine
git:
  - add: /
    to: /app
shell:
  install:
    - cd /app && go build -o /beszel-agent ./cmd/agent
---
image: agent
from: gcr.io/distroless/static-debian13
import:
  - image: builder
    add: /beszel-agent
    to: /beszel-agent
    after: install
imageSpec:
  config:
    entrypoint: ["/beszel-agent"]

Обратите внимание: раньше метаданные образа (ENTRYPOINT/CMD/EXPOSE и т.д.) в Stapel задавались через директиву docker: — она устарела и будет удалена в werf v3, актуальный способ — imageSpec.config (тот же механизм, что используется для expose/healthcheck в werf.yaml сайта курса, см. M3 урок 2).

Ключевое отличие от промежуточного (не полностью distroless) варианта из M3 урока 5: базовый образ финального agent — не alpine/debian-slim (у которых есть shell и минимальный набор утилит), а gcr.io/distroless/static-debian13 — образ, который физически не содержит shell и пакетного менеджера. Поскольку Beszel — единственный статически слинкованный Go-бинарник без внешних runtime-зависимостей (в отличие от, например, Python-приложений, требующих интерпретатор), это идеальный кандидат для полностью distroless-финала: единственное, что нужно скопировать в финальный образ — сам бинарник.

Trade-off’ы: сложность отладки vs размер и площадь атаки

АспектОбычный образ (alpine/debian)Distroless
Размер финального образаДесятки-сотни МБЕдиницы-десятки МБ (только бинарник + минимальный рантайм)
kubectl exec -it ... sh для отладки внутри контейнераРаботаетНе работает — нет shell
Площадь атаки (attack surface)Больше (пакетный менеджер, shell, утилиты — потенциальные векторы)Минимальна
Наличие известных CVE в базовом образеВыше (больше установленных пакетов = больше потенциальных уязвимостей)Ниже
Удобство отладки в продакшенеВысокоеНизкое — требует альтернативных методов (эфемерные debug-контейнеры через kubectl debug)

Ключевой практический trade-off: distroless-образ нельзя отладить привычным kubectl exec -it <pod> -- sh, потому что в контейнере физически нет shell. Альтернатива в современном Kubernetes — эфемерные debug-контейнеры (kubectl debug -it <pod> --image=busybox --target=<container>), которые подключаются к тому же namespace процессов пода, не изменяя сам оригинальный distroless-контейнер.

Когда distroless оправдан, а когда нет

Distroless имеет смысл для скомпилированных бинарников без интерпретируемого рантайма (Go, Rust, статически слинкованный C/C++) — именно такие приложения не нуждаются в shell/пакетном менеджере внутри финального контейнера вообще. Для приложений на интерпретируемых языках (Python, PHP, Node.js без bundling в единый бинарник) distroless либо невозможен напрямую, либо требует значительно более сложной сборки (например, PyInstaller для Python) — в таких случаях минимизация обычно ограничивается использованием -slim/alpine вариантов базовых образов, а не полным distroless.

Частые ошибки и подводные камни

  • Пытаться применить distroless к приложению, требующему интерпретатор или shell в рантайме. Например, приложение, вызывающее sh -c "..." внутри своей логики (например, для выполнения shell-хуков), сломается в distroless-образе — нет /bin/sh для выполнения команды.
  • Забывать про CA-сертификаты для HTTPS-запросов. Полностью пустой scratch-образ не содержит системных CA-сертификатов — если приложение делает исходящие HTTPS-запросы, нужен gcr.io/distroless/static (с сертификатами) вместо scratch, либо явное копирование /etc/ssl/certs/ca-certificates.crt из builder-образа.
  • Не иметь плана отладки production-инцидентов в distroless-контейнерах. Команда должна заранее знать про kubectl debug с эфемерными контейнерами — попытка “просто зайти в под” по привычке в момент инцидента только потеряет время.

Практика в кластере

🧩 Практика урока: Beszel · лицензия MIT
1
2
d8 k exec -it deploy/beszel-agent -n course-m8-beszel -- sh   # ожидаемая ошибка: no such file or directory
d8 k debug -it deploy/beszel-agent -n course-m8-beszel --image=busybox --target=agent

Убедитесь на практике, что обычный exec в distroless-контейнер невозможен, и освойте альтернативный способ отладки через эфемерный debug-контейнер.

Практика разработки

Соберите werf.yaml для Beszel по паттерну builder+distroless из теории и сравните размер итогового образа с промежуточным вариантом на alpine:

1
2
werf build --repo $CI_REGISTRY_IMAGE
docker images | grep beszel   # сравните размеры agent (distroless) и builder (alpine)

Шпаргалка команд урока

1
kubectl debug -it <pod> --image=busybox --target=<container>   # отладка distroless без shell в самом контейнере

Полная сводная таблица — 📋 werf .

Вопросы для самопроверки

Можно ли выполнить kubectl exec -it <pod> -- sh в distroless-контейнере?

Это ключевой практический trade-off distroless-подхода.

Источник: Рекомендуемая литература

Какой альтернативный метод отладки существует для distroless-контейнеров в современном Kubernetes?

Debug-контейнер подключается к namespace процессов пода без изменения оригинального контейнера.

Источник: Рекомендуемая литература

Для каких приложений distroless наиболее оправдан?

Такие приложения не нуждаются в shell/пакетном менеджере внутри финального контейнера вообще.

Источник: Рекомендуемая литература

Что нужно учесть при использовании полностью пустого scratch-образа для приложения, делающего HTTPS-запросы?

Без CA-сертификатов TLS-рукопожатие для внешних HTTPS-запросов не сможет проверить сертификат сервера.

Источник: Рекомендуемая литература

Рекомендуемая литература

Официальная документация

Статьи и блоги

Книги

  • Nigel Poulton. Docker Deep Dive. 2023. ISBN 978-1916585206.

Связанные материалы