Освоение Distroless: руководство по созданию безопасных и эффективных образов Docker

Освоение Distroless: руководство по созданию безопасных и эффективных образов Docker

21 апреля 2023 г.

С появлением Docker перед инженерами встала новая задача: оптимизировать сборку для достижения наименьшего возможного размера образа.

Доступно несколько вариантов:

  • Многоэтапные сборки. Dockerfile может состоять из нескольких шагов, каждый из которых имеет свой базовый образ Docker. Каждый шаг может копировать файлы с любого из предыдущих шагов сборки. Только последний получит тег; остальные останутся без тегов.

Этот подход разделяет один или несколько шагов сборки и шаг запуска. Для JVM это означает, что первый этап включает компиляцию и упаковку на основе JDK, а второй этап — запуск на основе JRE.

* Выбор наименьшего размера базового изображения: чем меньше базовое изображение, тем меньше результирующее изображение.

В этом посте я сосредоточусь на втором пункте — выборе наименьшего базового размера изображения

Минимум базовых изображений

Для базовых изображений доступны три подхода:

С нуля

Вы можете использовать зарезервированный минимальный образ Docker, scratch, в качестве отправной точки для создания контейнеров. Использование «образа» scratch сигнализирует процессу сборки, что вы хотите, чтобы следующая команда в Dockerfile была первым уровнем файловой системы в вашем образе.

Хотя scratch появляется в репозитории Docker на концентраторе, вы не можете извлечь его, запустить или пометить любое изображение с именем scratch. Вместо этого вы можете сослаться на него в своем Dockerfile. Например, чтобы создать минимальный контейнер с помощью нуля:

-- Создание простого родительского образа с помощью нуля

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

  1. Изображение для прикрепления. Поскольку нам нужна оболочка, мы используем bash
  2. Имя контейнера для присоединения
  3. Почему-то я не понимаю, надо повторить

Результат именно тот, который мы ожидаем:

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 г.

:::


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE