Изменения будут особенно полезны тем, кто собирает образы (images) на базе уже существующих и кому необходимо поддерживать их минимальный размер.
Каждый, кто собирал docker images знает, что практически каждая инструкция в Dockerfile добавляет отдельный слой и вам необходимо очистить этот слой от всех лишних артефактов, перед тем как добавить новый слой. Поэтому чтобы создать действительно эффективный Dockerfile раньше вам традиционно предлагали использовать скрипты и другую логику, чтобы поддерживать минимально возможный размер слоя. Обычной практикой было использовалось несколько Dockerfile в зависимости от целей сборки образа — один файл для DEVELOPMENT с определенным набором средства для отладки, профайлинга и всего остального и другой образ, гораздо меньшего размера для развертывания приложение на STAGING или PRODUCTION, с набором компонентов, необходимых для работы приложения.
Допустим у нас есть простой “hello world” HTTP-server, который нужно собраться и запустить тесты, а после собрать минимальные docker образ, которые содержит только исполняемые файлы.
Пример можно взять отсюда
Минимальный Dockerfile у нас будет выглядеть вот так.
Dockerfile:
FROM golang:latest
COPY . .
RUN go test && go build ./src/main.go
Давайте соберем и запустим образ:
docker image build -t hello_world:build .
Если посмотреть метаданные образа: docker image inspect hello_world:build то видно, что он состоит из 6 отдельный слоев и занимает около 800MB. И это только Hello World, а какой размер может быть у реального приложение можно только представить. Поэтому для PRODUCTION уже имеет смысл собрать образ только из исполняемых файлов.
В результате вы должны запустить вот такую последовательность команд:
Запустить базовый контейнер:
docker container run -it --name hello_world hello_world:build
Создать новый контейнер на базе уже существующего и скопировать бинарные файлы:
docker create --name extract hello_world:build
mkdir ./Production/
docker cp extract:/go/main ./Production/main
docker rm -f extract
docker rm -f hello_world
Создать PRODUCTION контейнер, содержащий только необходимые файлы для работы приложения:
docker build --no-cache -t hello_world:latest ./Production/
rm ./Production
Процесс может отличаться в зависимости от ваших требований, но в целом он будет так или иначе включать подобные шаги.
Так вот много-этапные (multi-stage builds) сборки позволяют значительно упростить этот процесс и описать его внутри Dockerfile. Каждая инструкция FROM может использовать индивидуальный базовый образ и каждая из них начинает новую стадию сборки docker образа. Но основное преимущество, что вы можете копировать необходимые артефакты из одной стадии в другую. В результате все вышеперечисленные шаги могут быть описаны вот так
Dockerfile:
FROM golang:latest as build
COPY . .
RUN go build ./src/main.go
FROM alpine:latest as production
COPY --from=build /go/main .
CMD ["./main"]
И все что вам остается, это выполнить команду:
docker image build -t hello_world:latest .
Note: отдельно стоит добавить, что к предыдущием стадия вы можете обращаться как по алиасу указанному в инструкции FROM golang:latest as build — как в примере выше COPY --from=build /go/main ., так и по индексу COPY --from=0 /go/main .
Ссылки:
Комментарии (8)
VolCh
23.02.2018 23:10COPY --from позволяет указывать не только стейджы из текущего докерфайла, но любой образ доступный для FROM. Очень полезно бывает, когда из какого-то официального образа нужен по сути только один файл или каталог.
dmitry_ch
24.02.2018 07:41-1Тема старая, страдательный. Однако тот же dapp мне как-то больше нравится: собирает быстрее, и не завязан на самую свежую версию докеры (а смена не всегда обоснована) — https://habrahabr.ru/company/flant/blog/333682/
Докеры молодцы, что лучше поздно, но сделали.
BOOTLOADER Автор
24.02.2018 21:58Не знаком с Dapp к сожалению. Спасибо за ссылку — посмотрю. А в чем его отличия может кратко сказать, и в чем удобство?
dmitry_ch
25.02.2018 11:32Во-первых, посоветовал бы флантовское выступление посмотреть, там и подходы, и решения описаны. Во-вторых, dapp сделан когда ещё докер об этом не думал. В-третьих, если у вас проект на старом докером, то переход на новый может и не быть у вас в планах — и вот эта описанная вами милая фича просто недоступна.
Dapp делался для скорости сборки образов и для уменьшения их размера. Грубо, если вы изменили исходники на 1 байт (и исходник — это код на том же ror), то размер образа не должен прыгать на 100 мб, а образ не должно собираться 5 минут.
hippoage
26.02.2018 01:15+1Dapp довольно жуткий, поэтому массово и не взелетел. С помощью примитивных скриптов и какого-нибудь проекта docker-squash (если не хочется собирать образ новым докером) то же самое отлично решается.
zelenin
https://habrahabr.ru/post/327698/
BOOTLOADER Автор
Спасибо. Немного другим язык, но об одном и том же.