- PVSM.RU - https://www.pvsm.ru -
В этой части перевода серии материалов, которая посвящена Docker, мы поговорим о том, как оптимизировать размеры образов и ускорить их сборку. В прошлых материалах мы сравнивали образы Docker с пиццей, термины с пончиками, а инструкции файлов Dockerfile с бубликами. Сегодня же не будет никакой выпечки. Пришло время посидеть на диете.
→ Часть 1: основы [1]
→ Часть 2: термины и концепции [2]
→ Часть 3: файлы Dockerfile [3]
→ Часть 4: уменьшение размеров образов и ускорение их сборки [4]
Для того чтобы разобраться с тем, о чём мы будем тут говорить, вам будет полезно освежить в памяти то, о чём шла речь в третьей части [5] этой серии материалов. А именно, там мы говорили об инструкциях файлов Dockerfile. Знание этих инструкций и тех особенностей Docker, которые мы обсудим сегодня, поможет вам оптимизировать файлы образов Docker.
Одной из сильных сторон Docker является кэширование. Благодаря этому механизму ускоряется сборка образов.
При сборке образа Docker проходится по инструкциям файла Dockerfile, выполняя их по порядку. В процессе анализа инструкций Docker проверяет собственный кэш на наличие в нём образов, представляющих собой то, что получается на промежуточных этапах сборки других образов. Если подобные образы удаётся найти, то система может ими воспользоваться, не тратя время на их повторное создание.
Если кэш признан недействительным, то инструкция, в ходе выполнения которой это произошло, выполняется, создавая новый слой без использования кэша. То же самое происходит и при выполнении инструкций, которые следуют за ней.
В результате, если в ходе выполнения инструкций из Dockerfile оказывается, что базовый образ имеется в кэше, то используется именно этот образ из кэша. Это называется «попаданием кэша». Если же базового образа в кэше нет, то весь процесс сборки образа будет происходить без использования кэша.
Затем следующая инструкция сопоставляется со всеми образами из кэша, в основе которых лежит тот же самый базовый образ, который уже обнаружен в кэше. Каждый кэшированный промежуточный образ проверяется на предмет того, имеется ли в нём то, что было создано такой же инструкцией. Если совпадения найти не удаётся, это называется «промахом кэша» и кэш считается недействительным. То же самое происходит до тех пор, пока не будет обработан весь файл Dockerfile.
Большинство новых инструкций просто сравниваются с тем, что уже есть в промежуточных образах. Если системе удаётся найти совпадение, то при сборке используется то, что уже есть в кэше.
Использование кэша способно ускорить сборку образов, но тут есть одна проблема. Например, если в Dockerfile обнаруживается инструкция RUN pip install -r requirements.txt
, то Docker выполняет поиск такой же инструкции в своём локальном кэше промежуточных образов. При этом содержимое старой и новой версий файла requirements.txt
не сравнивается.
Подобное может приводить к проблемам в том случае, если в requirements.txt
были добавлены сведения о новых пакетах, после чего, при сборке обновлённого образа, для того, чтобы установить новый набор пакетов, нужно снова выполнить инструкцию RUN pip install
. Совсем скоро мы поговорим о том, как бороться с этой проблемой.
В отличие от других инструкций Docker, при выполнении инструкций ADD
и COPY
от Docker требуется проверка содержимого файла или файлов для определения того, можно ли, при формировании образа, воспользоваться кэшем. А именно, контрольная сумма файлов, упомянутых в этих инструкциях, сравнивается с контрольной суммой файлов, которые имеются в промежуточных образах, которые уже есть в кэше. Если изменилось содержимое файлов или их метаданные, тогда кэш признаётся недействительным.
Вот несколько советов, касающихся эффективного использования кэша Docker:
--no-cache=True
команде docker build
.RUN apt-get update
и apt-get install
в цепочки для того, чтобы исключить проблемы, связанные с неправильным использованием кэша.pip
, с файлом requirements.txt
, тогда придерживайтесь нижеприведённой схемы работы для того, чтобы исключить использование устаревших промежуточных образов из кэша, содержащих набор пакетов, перечисленных в старой версии файла requirements.txt
. Вот как это выглядит:
COPY requirements.txt /tmp/
RUN pip install -r /tmp/requirements.txt
COPY . /tmp/
Если вам известны другие способы борьбы с «проблемой requirements.txt» — можете рассказать о них в комментариях.
Образы Docker могут быть довольно большими. Это противоречит вполне обоснованному стремлению того, кто их создаёт, к тому, чтобы сделать их как можно более компактными, что облегчит их загрузку из удалённого репозитория и благотворно скажется на объёме свободного места на компьютере, на который они загружаются. Поговорим о том, как уменьшать их размеры.
Вместо бубликов и пончиков мы теперь будем есть зелень
Одним из способов уменьшения размеров образов является тщательный подбор базовых образов и их последующая настройка.
Так, например, базовый образ Alpine представляет собой полноценный дистрибутив Linux-подобной ОС, содержащий минимум дополнительных пакетов. Его размер — примерно 5 мегабайт. Однако сборка собственного образа на основе Alpine потребует потратить достаточно много времени на то, чтобы оснастить его всем необходимым для обеспечения работы некоего приложения.
Существуют и специализированные варианты базового образа Alpine. Например, соответствующий образ из репозитория python, в который упакован скрипт print("hello world")
весит около 78.5 Мб. Вот Dockerfile для сборки такого образа:
FROM python:3.7.2-alpine3.8
COPY . /app
ENTRYPOINT ["python", "./app/my_script.py", "my_var"]
При этом на Docker Hub сказано, что этот базовый образ имеет размер 29 Мб. Размер образа, основанного на этом базовом образе, увеличивается за счёт загрузки и установки Python.
Помимо использования базовых образов, основанных на Alpine, уменьшить размеры образов можно благодаря использованию технологии многоступенчатой сборки.
В Dockerfile, описывающем многоступенчатую сборку образа, используется несколько инструкций FROM
. Создатель такого образа может настроить выборочное копирование файлов, называемых артефактами сборки, из одной ступени сборки в другую ступень. При этом появляется возможность избавиться от всего того, что в готовом образе не понадобится. Благодаря этому методу можно уменьшить размер готового образа.
Вот как работает каждая инструкция FROM
:
Вот модифицированный пример файла Dockerfile из документации [6] Docker, описывающего многоступенчатую сборку.
FROM golang:1.7.3 AS build
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=build /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
Обратите внимание на то, что мы дали имя первой ступени сборки, указав его после инструкции FROM
. К именованному этапу сборки мы обращаемся в инструкции COPY --from=
ниже в Dockerfile.
Применение процесса многоступенчатой сборки образов имеет смысл в некоторых случаях, когда приходится создавать множество контейнеров для продакшн-окружения. Многоступенчатая сборка позволяет максимально сократить размеры готовых образов. Но иногда такой подход приводит к усложнению поддержки образов. Поэтому вы, вероятно, не будете пользоваться многоступенчатой сборкой образов в тех случаях, в которых без неё можно обойтись. Об особенностях этой технологии можно почитать здесь [7] и здесь [8].
Как видите, многоступенчатая сборка — технология интересная, но подходит она далеко не для всех случаев. Тот же способ уменьшения размера образов, который мы обсудим ниже, можно порекомендовать абсолютно всем.
О файлах .dockerignore
нужно знать абсолютно всем, кто хочет освоить Docker. Эти файлы похожи на файлы .gitignore
. Они содержат список файлов и папок, в виде имён или шаблонов, которые Docker должен игнорировать в ходе сборки образа.
Этот файл размещают там же, где находится файл Dockerfile, и всё остальное, входящее в контекст сборки образа.
При запуске команды docker build
, инициирующей сборку образа, Docker проверяет папку на наличие в ней файла .dockerignore
. Если такой файл найти удаётся, тогда этот файл разбирается, при этом при определении списка файлов, которые нужно игнорировать, используются правила [9] функции Match()
из пакета filepath
Go и некоторые собственные правила [10] Docker.
Так, например, если в файле .dockerignore
встретится шаблон вида *.jpg
, то при создании образа проигнорированы будут файлы с любым именем и с расширением .jpg
. Если в файле встретится строка videos
, то система проигнорирует папку videos
и всё её содержимое.
При составлении файла .dockerignore
его можно снабжать комментариями, используя символ #
.
Вот что даёт тому, кто занимается созданием образов Docker, применение файлов .dockerignore
:
Подробности о файле .dockerignore
можно почитать в документации [10] к Docker.
Поговорим о том, как, пользуясь средствами командной строки, узнавать размеры образов и контейнеров Docker.
docker container ls -s
.docker image ls
выводит размеры образов.docker image history my_image:my_tag
.docker image inspect my_image:tag
позволяет узнать подробные сведения об образе, в том числе — размер каждого его слоя. Слои немного отличаются от промежуточных образов, из которых состоит готовый образ, но, в большинстве случаев их можно рассматривать как одинаковые сущности. Вот [11] хороший материал, который посвящён подробностям внутреннего устройства образов Docker.Теперь, когда мы обсудили возможности по уменьшению размеров образов, предлагаю вашему вниманию восемь рекомендаций, касающихся уменьшения размеров образов и ускорения процесса их сборки.
apt
, комбинируйте в одной инструкции RUN
команды apt-get update
и apt-get install
. Кроме того, объединяйте в одну инструкцию команды установки пакетов. Перечисляйте пакеты в алфавитном порядке на нескольких строках, разделяя список символами
. Например, это может выглядеть так:
RUN apt-get update && apt-get install -y
package-one
package-two
package-three
&& rm -rf /var/lib/apt/lists/*
Этот метод позволяет сократить число слоёв, которые должны быть добавлены в образ, и помогает поддерживать код файла в приличном виде.
&& rm -rf /var/lib/apt/lists/*
в конец инструкции RUN
, используемой для установки пакетов. Это позволит очистить кэш apt
и приведёт к тому, что он не будет сохраняться в слое, сформированном командой RUN
. Подробности об этом можно почитать в документации [13]..dockerignore
.dive
— отличный инструмент для исследования образов Docker, который помогает в деле уменьшения их размеров.Теперь вы знаете о том, как сделать так, чтобы образы Docker быстро собирались бы, быстро загружались бы из репозиториев и не занимали бы слишком много места на компьютере. В следующий раз мы поговорим о командах Docker.
Уважаемые читатели! Сталкивались ли вы при сборке образов Docker с проблемами, связанными с неправильным использованием механизмов кэширования?
Автор: ru_vds
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/309390
Ссылки в тексте:
[1] Часть 1: основы: https://habr.com/post/438796/
[2] Часть 2: термины и концепции: https://habr.com/post/439978/
[3] Часть 3: файлы Dockerfile: https://habr.com/post/439980/
[4] Часть 4: уменьшение размеров образов и ускорение их сборки: https://habr.com/post/440658/
[5] третьей части: https://habr.com/ru/company/ruvds/blog/439980/
[6] документации: https://docs.docker.com/develop/develop-images/multistage-build/
[7] здесь: https://blog.realkinetic.com/building-minimal-docker-containers-for-python-applications-37d0272c52f3
[8] здесь: https://medium.com/@tonistiigi/advanced-multi-stage-build-patterns-6f741b852fae
[9] правила: https://golang.org/pkg/path/filepath/#Match
[10] правила: https://docs.docker.com/v17.09/engine/reference/builder/#dockerignore-file
[11] Вот: https://windsock.io/explaining-docker-image-ids/
[12] dive: https://github.com/wagoodman/dive
[13] документации: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
[14] Источник: https://habr.com/ru/post/440658/?utm_source=habrahabr&utm_medium=rss&utm_campaign=440658
Нажмите здесь для печати.