Как создать образ контейнера Docker “FROM scratch”, который содержит только программу на Python и базовый интерпретатор Python?

Вопрос или проблема

Без Alpine, Ubuntu, Debian, CentOS, yum, apt, apk, pip и т.д. – только самое необходимое для запуска программы на python. Конечно, у нее, вероятно, будут зависимости, которые также нужно установить из файла requirements.txt.

Для ясности, многоэтапная сборка подойдет, если в конечном образе есть программное обеспечение на python для запуска, интерпретатор python и все необходимые библиотеки и дополнительные требования python.

На самом деле возможно (по крайней мере для статических бинарных файлов GoLang) иметь только бинарное приложение, и с точки зрения безопасности это вполне разумно.
Если вы хотите сделать то же самое с python, вам нужно скомпилировать код python в бинарный файл, но помните, что glibc имеет только обратную совместимость.
Таким образом, если вы решите скомпилировать свой код python с помощью cpython или pyinstaller – вам нужно знать версию операционной системы хоста контейнера и компилировать на ней (компиляция на последней версии Fedora не будет работать на RHEL 6 ;)).

После того как у вас есть код python в виде бинарного файла, вы можете использовать Dockerfile для копирования вашего бинарного файла и запуска его внутри контейнера. Затем сборка контейнера происходит как обычно.

Вот пример, как создать ваш минимальный контейнер с помощью buildah.

Ближе всего к Python вам будет проект distroless от Google, который создает образы docker с минимально необходимым для запуска конкретного интерпретатора. Тем не менее, я бы поставил под сомнение эту цель, потому что да, у злоумышленников может не быть оболочки или установщика пакетов, но у них все равно будет полноценный интерпретатор (в данном случае python) для использования в их эксплуатации.

Поймите, что без оболочки вы не сможете использовать синтаксис строки для RUN, и python не сможет вызвать оболочку на хосте, так что некоторые вещи могут также не сработать с этим подходом.

Если вы перейдете на компилируемый язык, который может упаковать результат в один статический бинарный файл (C, C++ и Go, среди других), у вас будет гораздо более защищенная среда, так как единственным инструментом внутри контейнера для использования злоумышленниками будет само приложение. Еще лучше, если вы сможете запустить этот бинарный файл с корневой файловой системой в контейнере, установленной только для чтения, и любыми подключенными томами, установленными на noexec, устраняя возможность для злоумышленников запускать их собственные бинарные файлы внутри контейнера.

Я хотел создать очень маленький образ для своих приложений на Python, но это сложно сделать с scratch, потому что вы не можете построить статически связанное приложение на Python, поэтому вам нужно включать необходимые библиотеки (например, glibc, libffi и т.д.).

Я посмотрел на проект distroless, но затем решил создать свое собственное изображение на основе изображений busybox. Busybox очень мал, но удобно иметь некоторые инструменты, такие как sh, ls, df и т.д. в вашем изображении.

Вот результат: Winand/python-base-images. Я копирую установку Python из официального образа Python, очищаю ее, затем копирую сертификаты и некоторые основные библиотеки.

Размер составляет около 35,0MB для версии glibc (python:3.12-slim – 123.6MB) и 31.3MB для версии musl (python:3.12-alpine – 48.3MB).

Я также удаляю pip, поэтому вам нужно будет подключить uv инструмент, чтобы иметь возможность установить пакеты. Этот пример использует файл pyproject.toml и uv.lock, но он также может устанавливать пакеты из requirements.txt.

FROM ghcr.io/astral-sh/uv:0.5.14 AS uv
COPY pyproject.toml uv.lock /

FROM python:3.12-musl
# https://docs.astral.sh/uv/concepts/python-versions/#disabling-automatic-python-downloads
ENV UV_PYTHON_DOWNLOADS=never
# for uv sync https://docs.astral.sh/uv/concepts/projects/config/#project-environment-path
ENV UV_PROJECT_ENVIRONMENT=/usr/local

USER root
RUN --mount=from=uv,source=/uv,target=/bin/uv \
    --mount=from=uv,source=/pyproject.toml,target=/pyproject.toml \
    --mount=from=uv,source=/uv.lock,target=/uv.lock \
  # Установить зависимости из существующего uv.lock
  uv sync --frozen --no-cache --no-dev --no-install-project
USER app
COPY --chown=app:app ./application /application

Ответ или решение

Создание Docker-контейнера "FROM scratch", содержащего только Python-программу и минимальный Python-интерпретатор, является сложной, но выполнимой задачей. Эта концепция предполагает создание контейнера без использования готовых образов операционных систем, таких как Alpine, Ubuntu или CentOS, а также без пакетных менеджеров вроде yum, apt или pip. Давайте разберем, как можно этого достичь.

Шаг 1: Компиляция Python кода в бинарный файл

Чтобы создать минималистичный Docker-образ, вам потребуется скомпилировать ваш Python-код в бинарный файл. Это возможно с помощью инструментов, таких как PyInstaller или cPython. Обратите внимание, что:

  • Совместимость системных библиотек: Компиляция должна выполняться на том же дистрибутиве и версии ОС, что и целевая среда для контейнера. Например, если контейнер будет работать на RHEL 6, компиляцию нужно проводить в той же среде, чтобы избежать проблем несовместимости с glibc.
  • Зависимости: Не забудьте включить все необходимые библиотеки, которые требуются вашему приложению.

Шаг 2: Создание Dockerfile

Dockerfile для вашего контейнера может выглядеть так:

# Использование multistage сборки
FROM python:3.12-slim AS builder
WORKDIR /app

# Копируем потребности и приложение
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Компиляция Python-кода в бинарник
RUN pyinstaller --onefile main.py

# Финальная сборка FROM scratch
FROM scratch
COPY --from=builder /app/dist/main /main
ENTRYPOINT ["/main"]

Шаг 3: Сборка и запуск Docker-контейнера

Соберите и запустите Docker-контейнер с помощью следующих команд:

docker build -t my-python-app .
docker run --rm my-python-app

Соображения безопасности

  • Уязвимости интерпретатора: Несмотря на минимальную среду, интерпретатор Python останется доступным, что может представлять угрозу в случае эксплуатации. Рассмотрите возможность использования более защищенных языков и статических бинарников.
  • Ограничения файловой системы: Установите файловую систему контейнера в режим только чтения и используйте параметры монтирования, такие как noexec, чтобы ограничить возможность злоумышленников запускать свои собственные исполняемые файлы.

Заключение

Создание контейнера "FROM scratch" с Python может показаться сложной задачей из-за динамической природы Python и его зависимостей. Однако, используя правильные подходы к компиляции и минимизации зависимостей, это вполне достижимо. Экспериментируйте с различными инструментами и методами, чтобы создать наиболее оптимизированный и безопасный контейнер для ваших потребностей.

Оцените материал
Добавить комментарий

Капча загружается...