Привет.
Делюсь лайфхаком по уменьшению размеров Docker-образов. Как-то нам попалась на поддержку и развитие CRM-система, написанная на Ruby. Пришли со словами: предыдущий разработчик не передал исходный код, но систему нужно развивать. Я уверен, что по условиям контракта передавали исходный код, но заказчики всегда относятся попустительски: им присылают архив на почту, а они потом стирают старое барахло, чтобы ящик почистить.
Так вот, зайдя на продакшен-сервер, я нашел развернутую платформу, да ещё и с .git папочкой. Ура, у меня были исходники с историей (она потом мне ни разу не понадобилась). Загрузил в нашу репу исходники, поизучал. В ходе контракта нужно было изменить деплой с rsync
на контейнеризацию и перетащить все на Alt Linux (или Astra, уже не помню).
Обновили Ruby-пакеты (gems), обновили под них код и написали Dockerfile. Первая сборка была удручающей: образ в 2Гб. Это нормальный размер, если ты собираешь образ с Torch и другой ML-штуковиной, но CRM - нет. В результате дальнейших действий, удалось сократить размер образа до 200Мб.
Если кратко, сделали банальные действия, чтобы сократить размеры образа:
Multistage.
Избавление от ненужный gem-ов.
Неизменение прав на файлы. Если вы вздумаете выполнить команду
RUN chmod +x /app/something
, то создастся новый слой с копией этого something, но с другими разрешениями. Это актуально для больших папок и файлов.Установка пакетов ОС и удаление кэша пакетов в этом же слое.
А также были проведены чуть менее очевидные операции:
С помощью dive (крайне рекомендую этот tool для того, чтобы посмотреть, что находится в каждом Docker-слое) выяснилось, что примерно 1 Гб занимает gem-пакет для генерации PDF. При чуть более пристальном взляде оказалось, что ребята загружают бинарники для 10 различных ОС и, даже, для Windows, а потом используют только один бинарник. Естественно, в том же слое, где ставился этот пакет, мы подчистили с помощью
rm -rf
этот ненужный хлам. Еще в пакеты любят пихать документацию и тесты, их тоже можно и нужно удалять, если вы настолько же параноидальны по поводу размеров.RUN mount - это способ монтирования данных из других stage в multistage без их копирования, а также выполнения действий в одном слое
RUN
. Если хотите, посмотрите официальную непонятную документацию https://docs.docker.com/reference/dockerfile/#run---mounttypebind, но лучше посмотрите пример ниже.
Mount
Обычно, в первом stage мы делаем компиляцию, а потом результат копируем в результирующий stage.
COPY --from=builder /app/public ./public
Периодически возникает потребность поставить пакеты в финальном слое, но инсталляторы для этого нам не нужны. Давайте приведу пример Dockerfile простого Python-проекта, тот проект на Ruby показывать не могу:
# Stage 1: Build the wheels
FROM python:3.11-alpine AS builder
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Set the working directory
WORKDIR /app
# Copy the requirements file into the container
COPY requirements.txt .
# Install the build dependencies
RUN pip install --no-cache-dir wheel
# Install the dependencies and build the wheels
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt
# Stage 2: Create the final image
FROM python:3.11-alpine
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Create a non-root user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Set the working directory
WORKDIR /app
# Install the wheels
RUN --mount=type=bind,from=builder,source=/app/wheels,target=/wheels pip install --no-cache-dir --no-index --find-links=/wheels /wheels/*
# Set file permissions and ownership
RUN chown -R appuser:appgroup /app
# Switch to the non-root user
USER appuser
# Copy the rest of the application code into the container
COPY . .
# Specify the command to run the application
CMD ["python", "app.py"]
Самое интересное здесь:
RUN --mount=type=bind,from=builder,source=/app/wheels,target=/wheels pip install --no-cache-dir --no-index --find-links=/wheels /wheels/*
Мы не копируем папку /app/wheels в /wheels, а используем как временную. Это позволяет выполнить установку пакетов и сразу же забыть о существовании папки . Конечно, можно копировать уже установленные пакеты из python3.11/site-packages, а не wheels, но это уже попахивает костылём. Затем проверяем с помощью dive - да! папки /wheels не существует ни в одном слое! Только на этой простой команде экономия сразу в 100Мб в моем случае.
Надеюсь, данные способы помогут вам в сокращении размеров Docker-образов.
PS. Это будет моя 10-я (я удалил несколько, чтобы не привлекать внимание санитаров) статья здесь, поэтому, в честь юбилея, если вам будет интересно почитать про работу CTO в небольшой компании, подписывайтесь на мой новый Telegram-канал https://t.me/cto_podsekin.
Комментарии (10)
seyko2
17.10.2024 22:30Сделать из содержимого подкаталога файл squash_fs.img несложно. А вот как подсунуть его в docker? Ведь слои то в нем и состоят из таких образов (нового вроде ничего не придумали), смонтированных с помощью overlayfs (в OpenVx всё было попонятнее как то)
vitaly_il1
17.10.2024 22:30Спасибо!
Я не уверен что понял чем mount лучше/удобнее чем "стандартное" копирование файлов при multi-stage build.WondeRu Автор
17.10.2024 22:30когда копируете что-то постоянное, чем будете пользоваться в рантайме, то
COPY --from,
а когда нужно в рантайм контейнере что-то временное получить из предыдущего этапа, тоRUN --mount
Alexufo
Как перетаскивать бинари питона собранные, чтобы не тащить зависимости для сборки?
WondeRu Автор
Не совсем понял, пакеты питона не компилируются в бинарники, в редких случаях содержат скомпилированные библиотеки. Если совсем хочется без пакетов, то нужно смотреть в сторону cyphon. Есть еще .pyc файлы - байткод питона, можно их попробовать потаскать в финальный образ, но я не пробовал и не понимаю, какие могут быть проблемы
Alexufo
Нужно собрать питон из исходников, для сборки нужна куча зависимостей. Если После сборки не понятно, как перетащить собранный питон, разбросанный своим телом по системным папкам. Разве что делать пакет deb или rpm но вот с ними возиться не очень хочется, хочется простым копированием файлов обойтись
whocoulditbe
установите собранное в отдельную директорию? https://docs.python.org/3/using/configure.html#install-options
WondeRu Автор
почему не хотите готовый образ использовать? python:3.12-slim-bookworm - этот без всякой шелухи.
WondeRu Автор
Но если у вас базовый образ, не приведи господь, какая-нибудь Астра, то идем в исходники образа python и копируем скрипты as is https://github.com/docker-library/python/blob/master/3.12/slim-bookworm/Dockerfile