Docker для новичков — #4 Оптимизация Dockerfile

Эта публикация — текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).

Привет, сегодня я расскажу о том как оптимизировать размер и скорость сборки image и писать Dockerfile эффективнее.

Разрабатывая приложения и помещая их в контейнеры, можно заметить большой размер итогового image. Это сотни мегабайт либо даже гигабайты, которые необходимо спулить из какого-нибудь registry и запустить.

Например, размер image Spring Boot приложения в среднем составляет от двухсот мб.

642258599c1ffed38a7fbc7c6fa5dcb9.png

Размер image Node.js приложения может составлять и более гигабайта.

Выбирайте правильный базовый image.

Обращаясь к Dockerhub мы часто видим множество тегов и не всегда понятно какой из них использовать. Множество популярных образов имеют тег slim или alpine. Эти теги обозначают версию, которая имеет минимальный вес image.

Если ваше приложение может работать с чуть ограниченной версией базового image, то используйте именно ее.

Например, image postgres:12.17-alpine весит около 90 мб, а обычная версия postgres:12.17 уже около 140 мб. Экономия в 35% уже только на одном этапе.

f7b8e505f2121d38f9ce3243ae3c690f.png41c852cd44f45dd05dd26c2a03eedafe.png

Файловая система и слои

Во втором видео, рассказывая об инструкциях Dockerfile я упомянул, что команды RUN, COPY и ADD добавляют слои в итоговый image.

Эти инструкции работают с файловой системой Docker — Overlay FS. Докер работает не с целой файловой системой, а со слоями. Каждый инструкция, модифицирующая файловую систему, добавляет слой.

2b5f014c562d9d9211120f981febef62.png

На данном примере поверх базового image ubuntu выполняется 4 команды RUN, в которой создаются файлы либо папки. Каждая из них добавляет новый слой.

Каждый следующий слой содержит результат предыдущего слоя. Слои неизменяемые, поэтому в этом примере они дозаписываются.

А что, если есть удаление или редактирование файлов?

67a44c2dda71f5e6183ea57d77edd0b5.png

В этом примере на пятой строке удаляется файл, который был добавлен во втором слое на третьей строке. Однако слои неизменяемые, второй и третий слой содержат этот файл, но четвертый уже нет. Он помечается как удаленный.

Старайтесь использовать меньше слоев. Одинаковые или однородные инструкции можно писать в одном слое, что сделает ваш image чуть меньше.

Например, установка нескольких пакетов в Linux в одной инструкции RUN сделает ваш Docker image чуть меньше.

075b03cc786d2a6bd79fe947efc86896.png

Удаление кэша

Устанавливая пакеты, в той же команде стоит очищать кэш, это может уменьшить размер image на несколько сотен мегабайт. К предыдущей команде дописываем удаление директории с кэшем.

92e57896c294c4b996ba70d5543729a4.png

Так как слой представляет собой результат изменений одной инструкции, то если вы удалите ненужные файлы до конца этой команды, то они не попадут в слой — объединение команд в одну инструкцию помогают оптимизировать это.

Вы можете использовать экспериментальную функциональность docker build — флаг –squash, этот флаг позволяет сжать несколько слоев в один, что уменьшит размер image.

Также есть реализация подобной команды на python, где вы можете сжать последние N слоев image.

.dockerignore

Если вы копируете файлы, то можете случайно скопировать ненужные файлы в контейнер. Кроме того, что это несет угрозу безопасности приложения, так и занимает место внутри файловой системы контейнера.

Создав .dockerignore файл в директории с Dockerfile, вы можете ограничить build context -, а это то, какие файлы попадут вовнутрь контейнера.

Копируйте в контейнер только то, без чего он не сможет нормально работать.

Кэширование

Используйте кэш при сборке image, это сократит время на сборку и запуск image. Docker хранит кэш каждого слоя на случай, если он пригодится в дальнейшем. Поэтому следует устанавливать зависимости до того, как вы используется команду COPY или ADD. Docker сможет закэшировать нужные слои с установленными зависимостями и переиспользовать их.

ba65a74646d2660313a9cc415b458d55.png

В этом примере первый Dockerfile лучше, чем второй, потому что позволяет Docker закэшировать слой с установкой зависимостей.

Многошаговая сборка

Dockerfile позволяет использовать несколько шагов для сборки конечного image. Следует использовать эту возможность, чтобы уменьшить размер итогового image.

Для работы вашего приложения зачастую требуется установить зависимости или пакеты. Это делается при помощи пакетных менеджеров как maven, npm или pip. Однако для работы приложения они не требуются, а требуется зачастую лишь среда исполнения языка.

Поэтому сборка итогового image будет состоять из двух шагов — установка зависимостей и копирование исполняемого файла.

На примере Java-приложения Dockerfile выглядит следующим образом.

1adef900955ef5fbe4b51186d2696bec.png

Image собирается из двух шагов. На первом шаге собирается приложение и устанавливаются зависимости, а на втором шаге уже с другим базовым image это приложение запускается. Соответственно все те файлы, что были добавлены на первом шаге не попадут в конечный image.

А есть ли что-то еще?

Вы можете попробовать оптимизировать свой image и другими способами. Я рассказал не обо всех существующих вариантах, так как под каждое приложение есть отдельные способы оптимизации.

Я бы хотел выделить еще один совет, который поможет вам сократить размер image — оптимизируйте свое приложение. Уменьшая размер image вы можете бороться с проблемой, но не с ее причиной.

Подумайте о том эффективно ли ваше приложение использует зависимости, вдруг вы не нуждаетесь в каких-то и забыли их удалить?

Docker лишь позволяет вам запускать ваше приложение в любой среде и он гарантирует эффективную работу контейнера, а вы сами ответственны за то, что в этом контейнере будет запущено.

Заключение

Спасибо, что прочитали эту статью.

В следующей статье мы рассмотрим Docker CLI и то, как использовать консоль для работы с Docker.

© Habrahabr.ru