Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров9.1K

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

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

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

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

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

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

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

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

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

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

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

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

FROM ubuntu
RUN touch file1
RUN touch file2
RUN mkdir
RUN touch dir1/inner_file

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

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

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

FROM ubuntu
COPY package.deb
RUN dpkg -i package.deb
RUN rm package.deb

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

При возможности старайтесь использовать меньше слоев. Одинаковые или однородные инструкции можно писать в одном слое, что сделает ваш image чуть меньше. Важно учитывать то, что слои могут кэшироваться и их стоит писать раздельно. То есть не нужно объединять слой, который не может быть закэширован (зачастую взаимодействие с локальной файловой системой), с подходящим для этого слоем.

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

FROM ubuntu
RUN apt-get update && apt-get install -y nginx

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

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

FROM ubuntu
RUN apt-get update && apt-get install -y nginx && \
    rm -f /var/lib/apt/lists/*

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

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

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

.dockerignore

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

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

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

Кэширование

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

FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && \
    apt-get upgrade -y && \ 
    apt-get install -y vim net-tools dnsutils
COPY . .
FROM ubuntu
COPY . .
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && \
    apt-get upgrade -y && \         
    apt-get install -y vim net-tools dnsutils

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

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

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

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

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

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

FROM maven:3.8.5-openjdk-17 AS build
COPY /src /src
COPY pom.xml /
RUN -f /pom.xml clean package

FROM openjdk:17-jdk-slim
COPY --from=build /target/*.jar application.jar
EXPOSE 8083
ENTRYPOINT ["java", "-jar", "application.jar"] 

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

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

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

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

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

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

Теги:
Хабы:
Всего голосов 9: ↑5 и ↓4+1
Комментарии8

Публикации

Истории

Работа

DevOps инженер
47 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
11 сентября
Митап по BigData от Честного ЗНАКа
Санкт-ПетербургОнлайн
14 сентября
Конференция Practical ML Conf
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
20 – 22 сентября
BCI Hack Moscow
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн