Фото від frank mckenna на Unsplash
У цьому посібнику ми розглянемо стратегії зменшення розміру Docker-образів, зосередивши увагу на порядку шарів, багатоступеневих збірках та використанні образів на основі Alpine. В кінцевому підсумку наша мета — зменшити розмір вашого Docker-контейнера для Python на 90% та зменшити час збірки більше ніж на 80%.
Dockerfile
У цьому посібнику ми зосередимося на Docker-образі для Django-додатка.
Перш за все, давайте подивимося, як виглядає «поганий» Dockerfile:
FROM python:3.10
# Встановити робочий каталог
WORKDIR /app
# Скопіювати всі файли проекту
COPY . .
# Встановити Python-залежності
RUN pip install -r requirements.txt
# Встановити операційні залежності
RUN apt-get update && apt-get install -y vim
# Відкрити порт додатка
EXPOSE 8000
# Запустити Django-додаток
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
На перший погляд це може не виглядати проблемним, але насправді є кілька моментів, які ми могли б покращити.
Хеш шару
Важливо розуміти, що Docker-образи складаються з шарів, кожен з яких створюється на основі попереднього, починаючи з верхньої частини Dockerfile і рухаючись вниз. Це означає, що ми повинні розміщувати операції, які малоймовірно змінюватимуться, ближче до верху або початку файлу.
У нашому прикладі будь-яка зміна коду буде включена в команду COPY . .
, що змінить її хеш. Це, у свою чергу, анулює всі шари, які йдуть після неї. Результат? Установка пакунків виконуватиметься знову, навіть якщо жодна з залежностей не змінилася.
# Встановити Python-залежності
RUN pip install -r requirements.txt
# Встановити операційні залежності
RUN apt-get update && apt-get install -y vim
...
=> => передача контексту: 45.45MB 1.0s
=> [2/5] WORKDIR /app 0.2s
=> [3/5] COPY . . 0.5s
=> [4/5] RUN pip install -r requirements.txt 3.2s
=> [5/5] RUN apt-get update && apt-get install -y vim 3.1s
=> експорт в образ 0.3s
=> => експорт шарів
...
Виправлення №1— Час збірки
Для встановлення залежностей для Python-проекту нам потрібен лише файл requirements.txt
.
Щоб покращити процес збірки, ми можемо спочатку скопіювати лише цей файл і виконати команду pip install
, перш ніж копіювати решту коду додатка. Це допоможе уникнути анулювання шару.
Django==5.0.0
djangorestframework==3.15.0
FROM python:3.10
# Скопіювати тільки файл requirements.txt
COPY requirements.txt requirements.txt # <-- тут
# Встановити Python-залежності
RUN pip install -r requirements.txt # <-- тут
# Встановити робочий каталог
WORKDIR /app
# Скопіювати всі файли проекту
COPY . .
# Встановити операційні залежності
RUN apt-get update && apt-get install -y vim
# Відкрити порт додатка
EXPOSE 8000
# Запустити Django-додаток
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Тепер, якщо ми побудуємо образ і внесемо зміни в код (але не в requirements.txt
), ми зможемо скористатися кешем Docker.
=> => передача контексту: 1.20MB 0.4s
=> КЕШУЄТЬСЯ [2/6] COPY requirements.txt requirements.txt 0.0s
=> КЕШУЄТЬСЯ [3/6] RUN pip install -r requirements.txt 0.0s
=> КЕШУЄТЬСЯ [4/6] WORKDIR /app 0.0s
=> [5/6] COPY . . 0.8s
=> [6/6] RUN apt-get update && apt-get install -y vim 8.2s
=> експорт в образ 0.3s
=> => експорт шарів
Те ж саме слід зробити для операційних залежностей. Перемістимо команду RUN apt-get
перед кроком COPY requirements.txt
.
FROM python:3.10
# Встановити операційні залежності
RUN apt-get update && apt-get install -y vim # <-- тут
# Скопіювати тільки файл requirements.txt
COPY requirements.txt requirements.txt
# Встановити Python-залежності
RUN pip install -r requirements.txt
# Встановити робочий каталог
WORKDIR /app
# Скопіювати всі файли проекту
COPY . .
# Відкрити порт додатка
EXPOSE 8000
# Запустити Django-додаток
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Якщо подивитися на час повторної збірки (час, необхідний для повторної збірки образу після внесення змін у код додатка):
- За замовчуванням: в середньому 19 секунд
- Оптимізовано: в середньому 3.5 секунди
Це на 15 секунд або майже 82% швидше!!
Виправлення №2 — Зменшення розміру образу
Є кілька способів зменшити розмір Docker-образу без втрати його функціональності. Менші розміри означають швидші завантаження і вивантаження, а це в свою чергу сприяє більш швидким процесам CI/CD або іншими скриптами.
Наш Docker-образ наразі має 1.16GB, давайте зменшимо його.
➜ ~ docker images | grep docker-opt
docker-opt-tutorial latest a171d964a545 10 seconds ago 1.16GB
Базовий образ
Добре почати з перегляду нашого базового образу. За замовчуванням Python-образи використовують дистрибутиви Debian, але є більш легкі альтернативи на Docker Hub.
Кращим вибором буде використання Alpine-версії образу, наприклад python:3.10-alpine
, який значно менший і безпечніший, оскільки використовує Linux Alpine.
Перед тим як продовжити, потрібно переконатися, що встановлювані операційні бібліотеки сумісні з цим дистрибутивом.
Замінимо визначення базового образу відповідно.
FROM python:3.10-alpine
Alpine використовує apk
замість apt-get
.
# Встановити операційні залежності
RUN apk add --no-cache vim
Результати
Порівняння розміру образів
➜ ~ docker images | grep docker-opt
docker-opt-tutorial-debian latest 2a6ed70991ea 57 seconds ago 1.16GB
docker-opt-tutorial-alpine latest ebfd35b6314f 32 seconds ago 177MB
1.16GB → 177MB — це майже на 85% менше! Але давайте не зупинятися на цьому.
Етапи збірки
Етапи збірки Docker, також відомі як багатоступеневі збірки, розділяють процес збірки на окремі етапи, наприклад, етап «збірки» для компіляції або встановлення залежностей та етап «runtime» для остаточного, оптимізованого образу. Такий підхід гарантує, що в кінцевий образ включено лише необхідні файли для виконання, що зменшує розмір і покращує продуктивність.
Розділимо нашу збірку на два етапи.
# Етап 1: Етап збірки
FROM python:3.10-alpine as builder
# Встановити залежності для збірки
RUN apk add --no-cache vim
# Скопіювати лише файл requirements.txt
COPY requirements.txt requirements.txt
# Встановити Python-залежності в каталог
RUN pip install --user --no-cache-dir -r requirements.txt
# --------------------------------------------------------
# Етап 2: Остаточний етап
FROM python:3.10-alpine as main
# Встановити робочий каталог для додатка
WORKDIR /app
# Скопіювати Python-залежності з етапу збірки
COPY --from=builder /root/.local /root/.local
# Забезпечити доступність Python-середовища
ENV PATH="/root/.local/bin:$PATH"
# Скопіювати тільки код додатка
COPY . .
# Відкрити порт додатка
EXPOSE 8000
# Запустити Django-додаток
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Отже, що саме ми змінили:
- Додали
as builder
до базової команди образу:
Це дозволяє нам посилатися на цей етап в інших частинах Dockerfile. - Продовжили встановлення залежностей на етапі збірки:
На етапі збірки ми встановлюємо залежності. Хочаvim
— це лише приклад, зазвичай встановлюються додаткові бібліотеки, які потрібні лише для встановлення залежностей і можуть не бути необхідними під час виконання. - Додали другий
FROM python:3.10-alpine
команду:
Це позначає новий етап у Dockerfile. На цьому етапі ми пропускаємо повторну установку залежностей.
4.
ВикористаноCOPY --from=builder /root/.local /root/.local
:
Це копіює/root/.local
з етапу збірки в етап виконання, забезпечуючи, що лише необхідні файли потраплять в кінцевий образ. - Додано
ENV PATH="/root/.local/bin:$PATH"
:
Це забезпечує доступність інструментів, скопійованих з етапу збірки, на етапі виконання.
Інша частина файлу залишилась без змін.
Результати
➜ ~ docker images | grep docker-opt
docker-opt-tutorial-multistage latest 2dfef0712422 7 minutes ago 131MB
docker-opt-tutorial-debian latest 2a6ed70991ea 20 minutes ago 1.16GB
docker-opt-tutorial-alpine latest ebfd35b6314f 21 minutes ago 177MB
Ми зменшили розмір образу ще на 46 МБ або приблизно 26%!!
Виключення файлів зі збірки
Docker дозволяє створити файл .dockerignore
, щоб забезпечити, що певні файли не потрапляють в образ помилково, такі як локальна база даних розробки, файли змінних середовища або директорія .venv
, коли виконується команда COPY . .
Ось приклад простого файлу .dockerignore
, який потрібно додати в корінь вашого проєкту:
# .dockerignore
.venv
Результати
➜ ~ docker images | grep docker-opt
docker-opt-tutorial-multistage-ignore latest b4b3427f99d3 87MB
docker-opt-tutorial-multistage latest 2dfef0712422 131MB
docker-opt-tutorial-alpine latest ebfd35b6314f 177MB
docker-opt-tutorial-debian latest 2a6ed70991ea 1.16GB
Виключив .venv
, що використовувалась локально, ми заощадили ще 44 МБ або приблизно 34%!
Висновок
У цьому підручнику ви дізналися, як правильно структурувати Dockerfile, використовувати кешування шарів, зменшувати розмір образу та виключати непотрібні файли.
Нам вдалося зменшити початковий образ розміром 1.16GB до всього 87MB, досягнувши зменшення розміру на 92.50%, зберігши ту ж саму функціональність і зменшивши час повторної збірки в середньому на 20 секунд.
Не забудьте перевірити мої інші статті!
Дякую за прочитане!
Перекладено з: How to Reduce the Size of Your Docker Image and Build Time by 90%?