Mount — ещё один способ уменьшения размера Docker-образа

от автора

Привет.

Делюсь лайфхаком по уменьшению размеров Docker-образов. Как-то нам попалась на поддержку и развитие CRM-система, написанная на Ruby. Пришли со словами: предыдущий разработчик не передал исходный код, но систему нужно развивать. Я уверен, что по условиям контракта передавали исходный код, но заказчики всегда относятся попустительски: им присылают архив на почту, а они потом стирают старое барахло, чтобы ящик почистить.

Так вот, зайдя на продакшен-сервер, я нашел развернутую платформу, да ещё и с .git папочкой. Ура, у меня были исходники с историей (она потом мне ни разу не понадобилась). Загрузил в нашу репу исходники, поизучал. В ходе контракта нужно было изменить деплой с rsync на контейнеризацию и перетащить все на Alt Linux (или Astra, уже не помню).

Обновили Ruby-пакеты (gems), обновили под них код и написали Dockerfile. Первая сборка была удручающей: образ в 2Гб. Это нормальный размер, если ты собираешь образ с Torch и другой ML-штуковиной, но CRM — нет. В результате дальнейших действий, удалось сократить размер образа до 200Мб.

Если кратко, сделали банальные действия, чтобы сократить размеры образа:

  1. Multistage.

  2. Избавление от ненужный gem-ов.

  3. Неизменение прав на файлы. Если вы вздумаете выполнить команду RUN chmod +x /app/something, то создастся новый слой с копией этого something, но с другими разрешениями. Это актуально для больших папок и файлов.

  4. Установка пакетов ОС и удаление кэша пакетов в этом же слое.

А также были проведены чуть менее очевидные операции:

  1. С помощью dive (крайне рекомендую этот tool для того, чтобы посмотреть, что находится в каждом Docker-слое) выяснилось, что примерно 1 Гб занимает gem-пакет для генерации PDF. При чуть более пристальном взляде оказалось, что ребята загружают бинарники для 10 различных ОС и, даже, для Windows, а потом используют только один бинарник. Естественно, в том же слое, где ставился этот пакет, мы подчистили с помощью rm -rf этот ненужный хлам. Еще в пакеты любят пихать документацию и тесты, их тоже можно и нужно удалять, если вы настолько же параноидальны по поводу размеров.

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


ссылка на оригинал статьи https://habr.com/ru/articles/851384/