Container optimization
Last updated
Last updated
https://habr.com/ru/companies/first/articles/702168/
У каждого образа Docker есть свой размер, который он занимает на жёстком диске. Порой бывает так, что контейнер с запущенным приложением на языке программирования Go, который содержит в себе всего лишь одну строчку с выводом фразы «Hello, world!» может занимать сотни Мб, в то время как существуют образы содержащие легковесные ОС весом всего лишь 5 Мб (alpine).
В этой статье будут подробно рассмотрены способы оптимизации файла Dockerfile с целью уменьшения размера готового образа и ускорения его сборки.
Базовый образ (его также называют родительским образом) – это образ, из которого собирается текущий (новый) образ. Все последующие слои «накладываются» поверх родительского образа, тем самым образуя подобие слоеного пирога. Базовый образ всегда задается в самой первой инструкции Dockerfile в инструкции – FROM.
В реестре Docker Hub присутствуют базовые образы операционных систем (Ubuntu, Debian, CentOS, Fedora), однако необходимо использовать конкретные образы, которые специально собраны под конкретные задачи.
Приведем пример. Предположим, у нас есть 3 приложения, написанные на 3 разных языках программирования – JavaScript (с использованием платформы Node.js), Python и Java. Для каждого из этих языков программирования существует свой базовый образ, который содержит все самое необходимое для запуска написанных программ – среду разработки, интерпретатор, компилятор и т. д. Для Node.js образ называется node, для Python —python, для Java —openjdk.
Такие образы существуют не только для языков программирования, но также и для различных инфраструктурных компонентов, таких как Nginx, MySQL, Apache Tomcat, HAProxy и других. При использовании специфического образа отпадает необходимость в установке дополнительных пакетов и, как следствие, образ не заполняется ненужными пакетами, библиотеками и файлами, которые приводят к увеличению размера образа.
Отдельно стоит выделить, что существуют slim и alpine образы, которые занимают еще меньше места на жестком диске. На скриншоте ниже приведен пример образа, содержащего OpenJDK 8 версии, который представляет собой среду разработки Java. Как можно заметить, базовый образ openjdk 8 занимает больше всего места на жестком диске – 526 Мб, в то время как образы slim и alpine занимают, соответственно, 194 Мб и 84.9 мб:
Slim образы – это образы, в которых присутствует минимальное количество пакетов и, в первую очередь, такие образы предназначены для запуска написанных программ. В качестве примера можно привести образ node:slim, в котором отсутствует компилятор.
Alpine образ содержит в себе одноименную операционную систему, разработанную специально для запуска внутри контейнера. Легковесность Alpine объясняется тем, что в данном дистрибутиве не используются привычные функции, которые доступны в других дистрибутивах Linux, такие как пакетные менеджеры apt/yum/dnf, система инициализации systemd, а также существенно сокращён список используемых стандартных утилит.
Прежде чем использовать образы с тегом slim и alpine, необходимо тщательно протестировать ваши приложения на этих образах, чтобы убедиться, что написанная программа работает без сбоев.
Часто происходит так, что при сборке образа в него не должны попадать определенные файлы. Это могут быть файлы артефактов, библиотек, ключей либо другие файлы, которые по какой-либо причине не должны попадать в создаваемый образ.
Для решения этой задачи необходимо использовать специальный файл под названием .dockerignore (имя файла начинается с символа точки, так как этот файл является скрытым). В данном файле можно указывать, какие файлы и директории не нужно включать в итоговую сборку образа. Файл .dockerignore является аналогом файла .gitignore, который используется в git. Так же, как и gitignore, он представляет собой специальный тип файла — в нем перечисляются те файлы, которые должны быть исключены из сборки образа.
Рассмотрим несколько примеров.
Файл .dockerignore помещается в корневую папку с проектом и располагается там же, где и файл Dockerfile.
При использовании таких команд, как RUN, COPY, ADD Docker создает слои. Каждый слой увеличивает размер образа, так как слои кэшируются.
Чтобы уменьшить количество слоев, необходимо объединять (комбинировать) команды в цепочки для того, чтобы исключить проблемы, связанные с неправильным использованием кэша. Рассмотрим эти рекомендации на конкретных примерах. Предположим, нам необходимо выполнить следующие 2 команды:
Если вы используете apt, необходимо комбинировать в одной инструкции RUN команды apt update и apt install. Команды выше необходимо скомбинировать в одну команду следующим методом:
В результате вместо двух слоев будет создан один слой, и как итог будет уменьшен размер финального образа. Кроме того, следует объединять в одну инструкцию команды установки пакетов. Перечислять пакеты необходимо на нескольких строках, разделяя список символами . Выглядеть это может так:
Этот метод также позволяет сократить число слоёв, которые должны быть добавлены в образ, и помогает поддерживать код файла в читаемом виде.
При использовании пакетных менеджеров, таких как apt, apk, yum/dnf, они кэшируют загружаемые данные с целью снижения нагрузки на сеть, и, как следствие, уменьшается время, требуемое для установки программ. Данный кэш необходимо удалять, чтобы размер итогового образа не разрастался до больших объемов.
Для удаления кэша в конец команды по установке (например, apt install) необходимо добавить одну из нижеперечисленных строк — в зависимости от используемого пакетного менеджера:
В версии Docker 17.05 был добавлен функционал многоэтапных сборок или как ее часто называют multi-stage сборка. Для чего нужна многоэтапная сборка? Предположим, что в нашем проекте есть такие файлы, как инструменты разработки, файлы библиотек и т. д., которые необходимы для создания образа, но они не нужны в финальном образе. Если включить эти файлы в финальную сборку, то это приведет к увеличению размера образа. Для решения данной проблемы необходимо отделить стадию сборки от стадии выполнения или, говоря простыми словами, исключить лишние зависимости сборки из образа, при этом оставив их доступными во время процесса сборки образа.
Мulti stage сборка позволяет использовать несколько инструкций FROM, и как итог используется сразу 2 базовых образа. При использовании muti-stage сборки появляется главное преимущество — возможность копирования необходимых артефактов из одной стадии в другую.
Разберем этот способ на конкретном примере. В качестве теста возьмем простое приложение, написанное на языке программирования Go и представляющее собой HTTP-сервер, который выводит фразу «Hello, world!». Код приложения указан ниже:
Содержимое Dockerfile выглядит следующим образом:
Соберем образ:
Далее проверим размер созданного образа, выполнив команду:
Образ занимает 869 Мб. Для вывода одной фразы “Hello World” это довольно много. В данном случае стоит воспользоваться multi-stage сборкой и собрать образ только из исполняемых файлов. Переделаем вышеописанный Dockerfile под multi-stage сборку:
Соберем образ еще раз:
И проверяем размер образа, созданного при помощи многоэтапной сборки:
Как можно увидеть, размер существенно сократился – 25.4 Мб вместо прошлых 869 Мб!
Существуют такие приложения, которым для запуска не требуется дополнительная среда разработки, (в данном случае под термином дополнительная среда разработки подразумевается базовый образ).
В таком случае можно отказаться от использования базового образа. Но инструкция FROM является обязательным условием и должна присутствовать в Dockerfile, без базового образа не получится собрать итоговый образ.
Для решения проблемы можно использовать специальный образ — scratch. Он представляет собой пустой образ, в котором нет никаких файлов. Scratch часто используют для построения других образов, например, для Debian или Alpine. После сборки образа при помощи scratch, в готовом образе не создается дополнительный слой, и как итог не увеличивается объем образа.
Создание Dockerfile может показаться достаточно легким процессом, однако стоит учитывать некоторые особенности при создании образов. Соблюдая рекомендации выше, вы сможете быстрее собирать образы и более рационально использовать пространство хранилища, тем самым оптимизируя работу с Dockerfile, что особенно важно для production среды.