Оптимизация образов

Оптимизация пространства и времени

Сборка образа может занимать заметное время, а готовый образ может быть довольно объёмным: он потребует много места на диске и будет долго передаваться по сети. Дополнительное место на диске займёт и билд-кеш образа: хранилище слоёв создаваемого образа.
Следовательно, есть практический смысл в том, чтобы оптимизировать процесс сборки образа и сэкономить на этом пространство и время.

Оптимизация сборки образов

Каждая инструкция в Dockerfile — это отдельный образ, его называют «слой». Чем меньше промежуточных слоев в докерфайле — тем быстрее происходит сборка образа.
При сборке слои кешируются: сохраняются в так называемом build-cache. Чем меньше слоёв — тем меньше объем билд-кеша.

Объединение команд

Вот несложный докерфайл: при сборке образа обновится APT, а затем установятся сервер, БД и интерпретатор Python
Скопировать кодDOCKER
RUN apt update RUN apt upgrade RUN apt install nignx RUN apt install postgres RUN apt install python3
Но образ состоит из пяти слоев при том, что можно обойтись всего одним: эти пакеты устанавливаются однократно и не будут изменяться. Пусть они сохранятся в билд-кеш в виде одного образа, а не пяти отдельных:
Скопировать кодDOCKER
RUN apt upgrade && apt update && \ apt install nginx postgres python3
Команды в инструкции RUN объединяются двойным амперсандом: &&. Для читаемости строки в докерфайле можно переносить, обозначая перенос обратным слешом \.

Кеширование при сборке

Докер при сборке образа использует общий кеш для всех образов. При создании образа Yamdb первые инструкции в докерфайле были такие: установить базовым слоем образ python и создать пустую директорию code:
Скопировать кодDOCKER
FROM python:3.8.5 RUN mkdir /code ...
Эти два слоя (как и последующие) были сохранены в билд-кеше.
Если при создании нового образа вы укажете в докерфайле те же команды — докер не будет собирать первые два слоя, а возьмёт из кеша готовые слои, те, что были созданы при сборке образа Yamdb.
Но если порядок слоёв изменён, добавлен новый слой или изменено содержимое слоя — докер будет собирать образ без кеша: ведь изменённый слой влияет на все последующие.
Скопировать кодDOCKER
FROM python:3.8.5 ENV secret_key mysecretkey # этот и последующие слои будут создаваться без применения кеша RUN mkdir /code ...
Продолжая описывать образ в докерфайле, придётся выбирать, в каком порядке выполнять инструкции.
Что рациональнее: сперва установить nginx и postgres, и только после этого копировать в образ код приложения, COPY . /code?
Скопировать кодDOCKER
FROM python:3.8.5 ENV secret_key mysecretkey RUN mkdir /code RUN apt upgrade && apt update && \ apt install nginx postgres ... COPY . /code CMD python hello.py
Или наоборот, сначала добавить в образ код приложения, и лишь затем установить сервер и базу данных?
Скопировать кодDOCKER
FROM python:3.8.5 ENV secret_key mysecretkey RUN mkdir /code COPY . /code ... RUN apt upgrade && apt update && \ apt install nginx postgres CMD python hello.py
Какой вариант правильный?
Порядок команд важен.
Если поменяется код приложения, то весь следующий за COPY кеш слоев окажется бесполезен и не будет применяться. С кешированием сборка образа проходит быстрее, в работе это важно, и отказываться от кеша не хочется.
При выполнении инструкции RUN докер проверяет, не изменена ли команда, указанная в инструкции. И если команда не изменена — докер считает, что слой не изменён и можно использовать закешированный слой.
Команда apt update кешируется и не проверяет обновления при каждой сборке.
При выполнении инструкции COPY докер сравнивает хеш-сумму данных, которые надо скопировать, с хеш-суммой закешированного слоя. Любое изменение содержимого приведёт к несовпадению хешей, и закешированный слой не будет использован в сборке. Также не будут использоваться кеши и всех последующих слоёв.
Поэтому инструкции COPY лучше размещать как можно ближе к концу файла: так они повлияют на меньшее количество слоёв.
Можно принудительно перестроить кеш командой --no-cache: docker build —-no-cache. При выполнении этой команды сборка будет проведена без использования кеша, а закешированные слои будут перезаписаны.

Хеш-сумма

Хеш-сумма (hash, иначе её называют «контрольная сумма» или просто «хеш») — это уникальная последовательность символов, которая по определённому алгоритму создаётся на основе какого-нибудь блока данных (файла или строки).
Преобразовать хеш-сумму обратно в исходные данные невозможно.
Есть несколько алгоритмов хеширования. При хешировании одних и тех же данных одним и тем же алгоритмом постоянно будет получаться один и тот же результат.
Скопировать код
Хеш — полезная штука # хешируем строку по алгоритму MD5 и получаем хеш: 9fb96ea96cdd996900cd70c498416330
Если же изменить в файле хотя бы один байт — хеш получится другой.
Скопировать код
# Меняем тире на дефис, пустяковое изменение Хеш - полезная штука # хешируем по алгоритму MD5 и получаем совершенно иной хеш: f456e093616f44a7825f58add84691ed
Таким образом можно отслеживать, изменились ли данные с момента последнего хеширования.

Облегчить образ

Для уменьшения объёма образа (и, как следствие — контейнера) есть несколько очевидных решений.
Выбирайте минимальные по объёму базовые образы. Например, образ alpine — это «облегченная» Linux, разработчики убрали из неё всё то, что не потребуется в контейнере. Этот образ выложен на DockerHub: https://hub.docker.com/_/alpine . Помимо «чистого» alpine существуют образы alpine + python, их применение упростит сборку.
Не включайте в образ ненужные пакеты, как на уровне окружения, так и на уровне проекта.

Что в итоге?

  1. Чтобы оптимизировать число слоев — объединяйте команды по установке в одной инструкции RUN.
  2. Порядок инструкций влияет на кеширование, кеширование влияет на скорость сборки образа. Изменяемые слои (например, COPY) лучше помещать ближе к концу докерфайла. Сначала должны идти слои, наименее подверженные изменениям.
  3. Выбирайте лёгкий базовый слой, следите, чтобы в образ не попадали неиспользуемые пакеты.