Поиск по сайту:

Как ускорить сборку Docker и оптимизировать кэширование с помощью «COPY –link»


COPY --link — это новая функция BuildKit, которая может значительно ускорить сборку образа Docker. Он работает путем копирования файлов в независимые слои изображений, которые не полагаются на наличие своих предшественников. Вы можете добавлять новый контент к изображениям даже без базового изображения, существующего в вашей системе.

Эта возможность была добавлена в составе Buildx v0.8 в марте 2022 года. Она включена в версию 20.10.14 интерфейса командной строки Docker, поэтому у вас уже должен быть доступ, если вы используете последнюю версию.

В этой статье мы покажем, что делает --link, и объясним, как это работает. Мы также рассмотрим некоторые ситуации, в которых его не следует использовать.

Что такое «–ссылка»?

--link — это новый необязательный аргумент для существующей инструкции Dockerfile COPY. Он меняет способ работы копий, создавая новый слой снимка каждый раз, когда вы его используете.

Обычные операторы COPY добавляют файлы на уровень, предшествующий им в Dockerfile. Содержимое этого слоя должно существовать на вашем диске, чтобы новое содержимое можно было объединить:

FROM alpine
COPY my-file /my-file
COPY another-file /another-file

Приведенный выше Dockerfile копирует my-file в слой, созданный предыдущей командой. После инструкции FROM изображение состоит из содержимого Alpine:

bin/
dev/
etc/
...

Первая инструкция COPY создает изображение, которое включает в себя все из Alpine, а также файл my-file:

my-file
bin/
dev/
etc/
...

А вторая инструкция COPY добавляет another-file поверх этого изображения:

another-file
my-file
bin/
dev/
etc/
...

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

Представляем «–ссылка»

«–link» изменяет COPY для создания новой автономной файловой системы каждый раз, когда она используется. Вместо того, чтобы копировать новые файлы поверх предыдущего слоя, они отправляются в совершенно другое место, чтобы стать независимым слоем. Затем слои соединяются вместе, чтобы получить окончательное изображение.

Давайте изменим пример Dockerfile, чтобы использовать --link:

FROM alpine
COPY --link my-file /my-file
COPY --link another-file /another-file

Результат инструкции FROM неизменен — он дает слой Alpine со всем содержимым этого изображения:

bin/
dev/
etc/
...

Первая инструкция COPY имеет заметно другой эффект. На этот раз создается еще один независимый слой. Это новая файловая система, содержащая только my-file:

my-file

Затем вторая инструкция COPY создает еще один новый снимок только с другим-файлом:

another-file

Когда сборка завершается, Docker сохраняет эти независимые снимки как новые архивы слоев (tarballs). Архивные файлы снова связываются с цепочкой предыдущих слоев, создавая окончательный образ. Он состоит из всех трех моментальных снимков, объединенных вместе, что приводит к файловой системе, которая соответствует исходной при создании контейнеров:

my-file
another-file
bin/
dev/
etc/
...

Это изображение из проекта BuildKit иллюстрирует различия между двумя подходами.

Добавление «COPY –link» в ваши сборки

COPY --link доступен, только если вы используете BuildKit для создания изображений. Либо запустите сборку с помощью docker buildx --create, либо используйте docker buildx с установленной переменной среды DOCKER_BUILDKIT=1.

Вы также должны согласиться на синтаксис Dockerfile v1.4, используя комментарий в верхней части файла:

# syntax=docker/dockerfile:1.4
FROM alpine:latest
COPY --link my-file /my-file
COPY --link another-file /another-file

Теперь вы можете создать свой образ с поддержкой связанных копий:

DOCKER_BUILDKIT=1 docker build -t my-image:latest .

Образы, созданные из Dockerfiles с помощью COPY --link, можно использовать так же, как и любые другие. Вы можете запустить контейнер с помощью docker run и отправить его прямо в реестры. Флаг --link влияет только на то, как содержимое добавляется к слоям изображения во время сборки.

Почему связанные копии имеют значение

Использование флага --link позволяет повторно использовать кеши сборки, даже если содержимое, которое вы КОПИРУЕТЕ, изменяется. Кроме того, сборки могут выполняться даже без базового образа, существующего на вашем компьютере.

Возвращаясь к приведенному выше примеру, стандартное поведение COPY требует, чтобы образ alpine существовал на вашем хосте Docker, прежде чем можно будет добавить новый контент. Образ будет загружен автоматически во время сборки, если вы его ранее не вытащили.

Со связанными копиями Docker не нужен контент изображения alpine. Он извлекает манифест alpine, создает новые независимые слои для скопированных файлов, а затем создает пересмотренный манифест, который связывает слои с теми, которые предоставляет alpine. Содержимое образа alpine — BLOB-объекты его слоев — будет загружено только в том случае, если вы запустите контейнер из нового образа или экспортируете его в tar-архив. Когда вы отправите образ в реестр, этот реестр сохранит свои новые слои и удаленно получит alpine.

Эта функциональность также способствует эффективному перемещению изображений. Возможно, в настоящее время вы распространяете образ Docker, используя последнюю версию Ubuntu 20.04 LTS:

FROM golang AS build
...
RUN go build -o /app .

FROM ubuntu:20.04
COPY --link --from=build /app /bin/app
ENTRYPOINT ["/bin/app"]

Вы можете создать образ с включенным кэшированием, используя флаг BuildKit --cache-to. Кэш inline хранит данные кэша сборки внутри выходного изображения, где их можно повторно использовать в последующих сборках:

docker buildx build --cache-to type=inline -t example-image:20.04 .

Теперь предположим, что вы хотите предоставить образ, основанный на следующей LTS после ее выпуска, Ubuntu 22.04:

FROM golang AS build
...
RUN go build -o /app .

FROM ubuntu:22.04
COPY --link --from=build /app /bin/app
ENTRYPOINT ["/bin/app"]

Восстановите образ, используя данные кеша, встроенные в исходную версию:

docker buildx build --cache-from example-image:20.04 -t example-image:22.04 .

Сборка завершится почти мгновенно. Используя кешированные данные из существующего образа, Docker может проверить, что файлы, необходимые для сборки /app, не изменились. Это означает, что кэш для независимого уровня, созданный инструкцией COPY, остается действительным. Поскольку этот уровень не зависит ни от каких других, образ ubuntu:22.04 также не будет загружен. Docker просто связывает слой снимков, содержащий /bin/app, с новым манифестом в цепочке слоев ubuntu:22.04. Уровень моментального снимка эффективно «перебазируется» на новый родительский образ без каких-либо операций с файловой системой.

Модель также оптимизирует многоэтапные сборки, в которых изменения могут происходить между любыми этапами:

FROM golang AS build
RUN go build -o /app .

FROM config-builder AS config
RUN generate-config --out /config.yaml

FROM ubuntu:latest
COPY --link --from=config /config.yaml build.conf
COPY --link --from=build /app /bin/app

Без --link любое изменение в сгенерированном config.yaml приводит к извлечению ubuntu:latest и копированию файла. двоичный файл должен быть перекомпилирован, так как его кеш становится недействительным из-за изменений файловой системы. В связанных копиях изменение config.yaml позволяет продолжить сборку без извлечения ubuntu:latest или перекомпиляции двоичного файла. Слой снимка с build.conf внутри просто заменяется новой версией, независимой от всех остальных слоев.

Когда не использовать его

В некоторых ситуациях флаг --link не будет работать правильно. Поскольку он копирует файлы в новый слой, а не добавляет их поверх предыдущего, вы не можете использовать неоднозначные ссылки в качестве пути назначения:

COPY --link my-file /data

При использовании обычной инструкции COPY my-file будет скопирован в /data/my-file, если /data уже существует как каталог в образе. С --link файловая система целевого слоя всегда будет пустой, поэтому my-file записывается в /data.

То же самое относится и к разрешению символических ссылок. Стандартный COPY автоматически разрешает пути назначения, которые являются символическими ссылками в изображении. Когда вы используете --link, такое поведение не поддерживается, так как символическая ссылка не будет существовать в независимом слое копии.

Рекомендуется начать использовать --link везде, где эти ограничения не применяются. Принятие этой функции ускорит ваши сборки и сделает кэширование более мощным. Если вы не можете сразу удалить неоднозначные или символические пути назначения, вы можете продолжать использовать существующую инструкцию COPY. Именно из-за этих обратно несовместимых изменений --link является необязательным флагом, а не новым значением по умолчанию.

Краткое содержание

COPY --link в BuildKit — это новая функция Dockerfile, которая может ускорить и повысить эффективность сборки. Изображения, использующие связанные копии, не должны извлекать предыдущие слои только для того, чтобы в них можно было скопировать файлы. Вместо этого Docker создает новый независимый слой для каждого COPY, а затем связывает эти слои обратно в цепочку.

Вы можете начать использовать связанные копии уже сейчас, если вы создаете образы с помощью BuildKit и последней версии интерфейса командной строки Buildx или Docker. Принятие «–link» — это новый этап сборки Docker, основанный на передовой практике, при условии, что на вас не влияют изменения разрешения пути назначения, которые он требует.