Освоение Distroless: руководство по созданию безопасных и эффективных образов Docker
21 апреля 2023 г.С появлением Docker перед инженерами встала новая задача: оптимизировать сборку для достижения наименьшего возможного размера образа.
Доступно несколько вариантов:
- Многоэтапные сборки.
Dockerfile
может состоять из нескольких шагов, каждый из которых имеет свой базовый образ Docker. Каждый шаг может копировать файлы с любого из предыдущих шагов сборки. Только последний получит тег; остальные останутся без тегов.
Этот подход разделяет один или несколько шагов сборки и шаг запуска. Для JVM это означает, что первый этап включает компиляцию и упаковку на основе JDK, а второй этап — запуск на основе JRE.
* Выбор наименьшего размера базового изображения: чем меньше базовое изображение, тем меньше результирующее изображение.
В этом посте я сосредоточусь на втором пункте — выборе наименьшего базового размера изображения
Минимум базовых изображений
Для базовых изображений доступны три подхода:
С нуля
Вы можете использовать зарезервированный минимальный образ Docker, scratch
, в качестве отправной точки для создания контейнеров. Использование «образа» scratch
сигнализирует процессу сборки, что вы хотите, чтобы следующая команда в Dockerfile
была первым уровнем файловой системы в вашем образе.
Хотя scratch
появляется в репозитории Docker на концентраторе, вы не можете извлечь его, запустить или пометить любое изображение с именем scratch
. Вместо этого вы можете сослаться на него в своем Dockerfile
. Например, чтобы создать минимальный контейнер с помощью нуля:
-- Создание простого родительского образа с помощью нуля а>
FROM scratch
COPY hello /
CMD ["/hello"]
scratch
— наименьшее возможное исходное изображение. Это хорошо работает, если конечное изображение не зависит от какого-либо системного инструмента.
Аплайн
Alpine Linux — это крошечный дистрибутив, основанный на musl
, BusyBox
и OpenRC
. Он разработан, чтобы быть безопасным и небольшим. Например, образ Docker 3.17 весит всего 3,22 МБ.
С другой стороны, я уже сталкивался с проблемами из-за того, что Alpine использует musl
вместо более распространенного glibc
. Буквально на прошлой неделе я узнал об Alpaquita Linux, предназначенном для решения именно этой проблемы. Тег stream-glibc-230404
занимает 8,4 МБ. Он в два раза больше, чем Alpine, но по-прежнему очень респектабелен по сравнению с обычными дистрибутивами Linux, например, Red Hat с 75,41 МБ.
Без дистрибутива
Последний, но не менее важный пункт — Distroless.
Поскольку этот пост посвящен Distroless, я расскажу об этом в специальном разделе.
Без дистрибутива
Впервые я узнал о Distroless, потому что это был вариант по умолчанию в Google Jib. Jib — это плагин Maven для создания контейнеров Docker без зависимости от Docker. Обратите внимание, что теперь значение по умолчанию изменилось.
У Distroless есть собственный проект на GitHub:
<цитата>Образы Distroless содержат только ваше приложение и его зависимости во время выполнения. Они не содержат менеджеров пакетов, оболочек или любых других программ, которые вы ожидаете найти в стандартном дистрибутиве Linux.
[...]
Ограничение того, что находится в вашем контейнере среды выполнения, только тем, что необходимо для вашего приложения, является передовой практикой, используемой Google и другими технологическими гигантами, которые используют контейнеры в производстве в течение многих лет. Это улучшает соотношение сигнал-шум сканеров (например, CVE) и снижает нагрузку на установление происхождения до нужного вам уровня.
-- Образы контейнеров Distroless
Утверждение выше намекает на то, что такое Distroless и почему вы должны его использовать. Как и термин бессерверный, термин "бессистемный" вводит в заблуждение. Самый важный факт заключается в том, что Distroless не предоставляет ни менеджера пакетов, ни оболочку. По этой причине размер образа Distroless ограничен.
Кроме того, образы Distroless считаются более безопасными: поверхность атаки меньше по сравнению с другими обычными образами, и в них отсутствуют менеджеры пакетов и оболочки — распространенные векторы атак. Обратите внимание, что некоторые статьи оспаривают это выгода.
Образы Distroless поставляются с четырьмя стандартными тегами:
последняя
без полномочий root
: образ не запускается с правамиroot
, поэтому он более безопасенdebug
: изображение содержит оболочку для целей отладкиотладка без полномочий root
Отладка без дистрибутива
Мне нравится идея Distroless, но у нее есть большая проблема. Что-то происходит во время разработки, а иногда и во время производства, и нужно войти в контейнер, чтобы понять проблему. Как правило, для запуска оболочки используется docker exec
или kubect exec
: тогда можно запускать команды интерактивно из работающего контейнера. Однако образы Distroless не предлагают оболочку по задумке. Следовательно, нужно запускать каждую команду извне; это может быть удобнее для разработчиков.
Во время разработки можно переключить базовый образ на debug
. Затем вы перестраиваете и запускаете снова, а затем решаете проблему. Тем не менее, вы должны не забыть вернуться к базовому образу без debug
. Чем больше проблем вы столкнетесь, тем больше шансов, что вы, наконец, отправите образ debug
в рабочую среду.
Хуже того, вы вообще не можете проделать тот же трюк в продакшене.
Kubernetes спешит на помощь
На последней конференции JavaLand я присутствовал на выступлении моего хорошего друга Маттиаса Хойсслера. В своем выступлении он рассказал мне о команде kubectl debug
, появившейся в Kubernetes 1.25:
Эфемерные контейнеры полезны для интерактивного устранения неполадок, когда kubectl exec
недостаточно, потому что произошел сбой контейнера или образ контейнера не включает утилиты отладки, например, с образами без дистрибутива.
Вы можете использовать команду отладки kubectl
, чтобы добавить временные контейнеры в работающий модуль.
-- Отладка с помощью эфемерного контейнера отладки
Давайте посмотрим, как это работает, запустив контейнер Distroless:
kubectl run node --image=gcr.io/distroless/nodejs18-debian11:latest --command -- /nodejs/bin/node -e "while(true) { console.log('hello') }"
Контейнер запускает бесконечный цикл NodeJS. Мы можем проверить журналы с ожидаемыми результатами:
kubectl logs node
hello
hello
hello
hello
Представьте, что нам нужно проверить, что происходит внутри контейнера.
kubectl exec -it node -- sh
Поскольку у контейнера нет оболочки, возникает следующая ошибка:
OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown
command terminated with exit code 126
Мы можем использовать магию kubectl debug
, чтобы добиться этого в любом случае:
kubectl debug -it
--image=bash #1
--target=node #2
node #3
- Изображение для прикрепления. Поскольку нам нужна оболочка, мы используем
bash
- Имя контейнера для присоединения
- Почему-то я не понимаю, надо повторить
Результат именно тот, который мы ожидаем:
Targeting container "node". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-tkkdf.
If you don't see a command prompt, try pressing enter.
bash-5.2#
Теперь мы можем использовать оболочку для ввода любой команды:
ps
Результат подтверждает, что мы "совместно используем" один и тот же контейнер:
PID USER TIME COMMAND
1 root 12:18 /nodejs/bin/node -e while(true) { console.log('hello') }
27 root 0:00 bash
33 root 0:00 ps
После завершения сеанса мы можем повторно подключить его к контейнеру, следуя инструкциям:
bash-5.2# Session ended, the ephemeral container will not be restarted but may be reattached using 'kubectl attach node -c debugger-tkkdf -i -t' if it is still running
Заключение
Образы без дистрибутива – это отличное решение для уменьшения размера образа и повышения его безопасности. Они достигают этих преимуществ, не предоставляя ни менеджера пакетов, ни оболочки. Отсутствие оболочки — огромная проблема, когда нужно отлаживать то, что происходит внутри контейнера.
Новая команда kubectl debug
предлагает простой способ решить эту проблему, подключив внешний контейнер, который использует тот же контекст, что и исходный. Danke nochmal dafür, Матиас !
:::информация Первоначально опубликовано на сайте A Java Geek 16 апреля 2023 г.
:::
Оригинал