Pull to refresh
375.49
FirstVDS
Виртуальные серверы в ДЦ в Москве и Амстердаме

Оптимизация Dockerfile для уменьшения размера и быстрой сборки образов

Reading time6 min
Views16K

У каждого образа Docker есть свой размер, который он занимает на жёстком диске. Порой бывает так, что контейнер с запущенным приложением на языке программирования Go, который содержит в себе всего лишь одну строчку с выводом фразы «Hello, world!» может занимать сотни Мб, в то время как существуют образы содержащие легковесные ОС весом всего лишь 5 Мб (alpine).

В этой статье будут подробно рассмотрены способы оптимизации файла Dockerfile с целью уменьшения размера готового образа и ускорения его сборки.

Способ 1. Использование правильного базового образа

Базовый образ (его также называют родительским образом) – это образ, из которого собирается текущий (новый) образ. Все последующие слои «накладываются» поверх родительского образа, тем самым образуя подобие слоеного пирога. Базовый образ всегда задается в самой первой инструкции 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 и других. При использовании специфического образа отпадает необходимость в установке дополнительных пакетов и, как следствие, образ не заполняется ненужными пакетами, библиотеками и файлами, которые приводят к увеличению размера образа.

Способ 2. Использование образов slim и alpine

Отдельно стоит выделить, что существуют 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, необходимо тщательно протестировать ваши приложения на этих образах, чтобы убедиться, что написанная программа работает без сбоев.

Способ 3. Использование файла dockerignore

Часто происходит так, что при сборке образа в него не должны попадать определенные файлы. Это могут быть файлы артефактов, библиотек, ключей либо другие файлы, которые по какой-либо причине не должны попадать в создаваемый образ.

Для решения этой задачи необходимо использовать специальный файл под названием .dockerignore (имя файла начинается с символа точки, так как этот файл является скрытым). В данном файле можно указывать, какие файлы и директории не нужно включать в итоговую сборку образа. Файл .dockerignore является аналогом файла .gitignore, который используется в git. Так же, как и gitignore, он представляет собой специальный тип файла — в нем перечисляются те файлы, которые должны быть исключены из сборки образа.

Рассмотрим несколько примеров.

# игнорировать директории .git и .cache
.git
.cache
 
# игнорировать все файлы, которые имеют разрешение class *.class во всех директориях, включая корневой каталог сборки
**/*.class
 
# игнорировать все файлы в формате markdown (md) кроме всех файлов README*.md, исключая README-secret.md
*.md
!README*.md
README-secret.md

Файл .dockerignore помещается в корневую папку с проектом и располагается там же, где и файл Dockerfile.

Способ 4. Минимизация количества слоев образа

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

Чтобы уменьшить количество слоев, необходимо объединять (комбинировать) команды в цепочки для того, чтобы исключить проблемы, связанные с неправильным использованием кэша. Рассмотрим эти рекомендации на конкретных примерах. Предположим, нам необходимо выполнить следующие 2 команды:

RUN apt update
RUN apt -y install tree

Если вы используете apt, необходимо комбинировать в одной инструкции RUN команды apt update и apt install. Команды выше необходимо скомбинировать в одну команду следующим методом:

RUN apt update && apt -y install tree

В результате вместо двух слоев будет создан один слой, и как итог будет уменьшен размер финального образа. Кроме того, следует объединять в одну инструкцию команды установки пакетов. Перечислять пакеты необходимо на нескольких строках, разделяя список символами \. Выглядеть это может так:

RUN apt update && apt install -y \
	htop \
	tree \
	mc

Этот метод также позволяет сократить число слоёв, которые должны быть добавлены в образ, и помогает поддерживать код файла в читаемом виде.

Способ 5. Удаление кэшей и временных файлов

При использовании пакетных менеджеров, таких как apt, apk, yum/dnf, они кэшируют загружаемые данные с целью снижения нагрузки на сеть, и, как следствие, уменьшается время, требуемое для установки программ. Данный кэш необходимо удалять, чтобы размер итогового образа не разрастался до больших объемов.

Для удаления кэша в конец команды по установке (например, apt install) необходимо добавить одну из нижеперечисленных строк — в зависимости от используемого пакетного менеджера:

APT: ... && rm -rf /var/cache/apt
APK: ... && rm -rf /etc/apk/cache
YUM: ... && rm -rf /var/cache/yum
DNF: ... && rm -rf /var/cache/dnf

Способ 6. Использование многоэтапных (multi-stage) сборок

В версии Docker 17.05 был добавлен функционал многоэтапных сборок или как ее часто называют multi-stage сборка. Для чего нужна многоэтапная сборка? Предположим, что в нашем проекте есть такие файлы, как инструменты разработки, файлы библиотек и т. д., которые необходимы для создания образа, но они не нужны в финальном образе. Если включить эти файлы в финальную сборку, то это приведет к увеличению размера образа. Для решения данной проблемы необходимо отделить стадию сборки от стадии выполнения или, говоря простыми словами, исключить лишние зависимости сборки из образа, при этом оставив их доступными во время процесса сборки образа.

Мulti stage сборка позволяет использовать несколько инструкций FROM, и как итог используется сразу 2 базовых образа. При использовании muti-stage сборки появляется главное преимущество — возможность копирования необходимых артефактов из одной стадии в другую.

Разберем этот способ на конкретном примере. В качестве теста возьмем простое приложение, написанное на языке программирования Go и представляющее собой HTTP-сервер, который выводит фразу «Hello, world!». Код приложения указан ниже:

package main;
import (
	"fmt"
	"log"
    "net/http"
)
func main() {
    http.HandleFunc("/helloworld", func(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "Hello, World!")
	})
    fmt.Printf("Server running (port=8080), route: http://localhost:8080/helloworld\n")
	if err := http.ListenAndServe(":8080", nil); err != nil {
    	log.Fatal(err)
	}
}

Содержимое Dockerfile выглядит следующим образом:

FROM golang:1.16-buster AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY *.go ./
RUN go build -o /hello_go_http
EXPOSE 8080
ENTRYPOINT ["/hello_go_http"]

Соберем образ:

docker build -t hello_go_http .

Далее проверим размер созданного образа, выполнив команду:

docker images

Образ занимает 869 Мб. Для вывода одной фразы “Hello World” это довольно много. В данном случае стоит воспользоваться multi-stage сборкой и собрать образ только из исполняемых файлов. Переделаем вышеописанный Dockerfile под multi-stage сборку:

FROM golang:1.16-buster AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY *.go ./
RUN go build -o /hello_go_http
 
FROM gcr.io/distroless/base-debian10
WORKDIR /
COPY --from=builder /hello_go_http /hello_go_http
EXPOSE 8080
ENTRYPOINT ["/hello_go_http"]

Соберем образ еще раз:

docker build -t hello_go_http_multistage .

И проверяем размер образа, созданного при помощи многоэтапной сборки:

docker images

Как можно увидеть, размер существенно сократился – 25.4 Мб вместо прошлых 869 Мб!

Способ 7. Использование пустых образов

Существуют такие приложения, которым для запуска не требуется дополнительная среда разработки, (в данном случае под термином дополнительная среда разработки подразумевается базовый образ).

В таком случае можно отказаться от использования базового образа. Но инструкция FROM является обязательным условием и должна присутствовать в Dockerfile, без базового образа не получится собрать итоговый образ.

Для решения проблемы можно использовать специальный образ — scratch. Он представляет собой пустой образ, в котором нет никаких файлов. Scratch часто используют для построения других образов, например, для Debian или Alpine. После сборки образа при помощи scratch, в готовом образе не создается дополнительный слой, и как итог не увеличивается объем образа.

Итог

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


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Tags:
Hubs:
Total votes 27: ↑23 and ↓4+23
Comments27

Articles

Information

Website
firstvds.ru
Registered
Founded
Employees
51–100 employees
Location
Россия
Representative
FirstJohn