Привет.
Делюсь лайфхаком по уменьшению размеров 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.
ссылка на оригинал статьи https://habr.com/ru/articles/851384/
Добавить комментарий