Многоступенчатая сборка Docker-образа
Разработка ПО — сложный процесс, результатом которого является работающий «в миру» продукт/сервис. Давайте познакомимся с подходом, позволяющим упростить жизненный цикл разработки.
Реализация
Начиная с версии 17.05, в докере появились многоступенчатые билды. Многоступенчатые сборки полезны для всех, кто пытается оптимизировать Docker-файлы и образы, сохраняя их легкими для чтения и обслуживания. До появления этой фичи применяли подход под названием «Builder Pattern». Подход «Builder Pattern» заключается в создании двух Docker-файлов и sh-скрипта:
- Dockerfile.build — собирает приложение (вытягивает зависимости, компилирует и т. д.).
- Dockerfile — запускает приложение.
- build.sh — копирует артифакт, полученный из Dockerfile.build, в контейнер, собирающийся из Dockerfile.
Пример «Builder Pattern». Исходный код примера можно получить на GitHub.
Dockerfile.build
FROM golang:1.12.4-stretch # Change worck directory WORKDIR /go/src/github.com/zhooravell/docker-multi-stage-builds/builder-pattern # Copy go code & dep files to worck directory COPY main.go Gopkg.toml Gopkg.toml ./ # Install dep, packages and build application RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh \ && dep version \ && dep ensure \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
Dockerfile
FROM alpine:latest # Add ssl support RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
#!/usr/bin/env bash echo Building docker-multi-stage-builds/builder-pattern:build # Билдим образ с приложение (зависимости, компиляция) docker build -t docker-multi-stage-builds/builder-pattern:build . -f Dockerfile.build # Создаем контейнер с коротким именем extract docker container create --name extract docker-multi-stage-builds/builder-pattern:build # Копируем артефакт из контейнера на хост-машину docker container cp extract:/go/src/github.com/zhooravell/docker-multi-stage-builds/builder-pattern/app ./app # Удаляем контейнер docker container rm -f extract echo Building docker-multi-stage-builds/builder-pattern:latest # Собираем образ с скомпилированным приложением docker build --no-cache -t docker-multi-stage-builds/builder-pattern:latest . # Удаляем артефакт rm ./app
Запустим контейнер и убедимся, что у все работает, как ожидалось:
$ docker run -p 8080:8080 docker-multi-stage-builds/builder-pattern
В результате получается очень маленький образ, с минимальным набором пакетов/зависимостей — только то, что нужно для запуска приложения. Production-образ не содержит инструментов сборки, компиляции, систем управления версиями.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-multi-stage-builds/builder-pattern latest 4be00adf69b0 16 seconds ago 13.8MB docker-multi-stage-builds/builder-pattern build 3f0177d07175 20 seconds ago 819MB
Что же тогда multi-stage build? И зачем он нужен?
Многоступенчатый билд позволяет достичь того же результата, но без bash-скриптов, без нескольких Docker-файлов. Это достигается за счет использования нескольких инструкций FROM. В результате предыдущий пример будет выглядеть так:
FROM golang:1.12.4-stretch as builder # Change worck directory WORKDIR /go/src/github.com/zhooravell/docker-multi-stage-builds/go-multi-stage-build # Copy go code & dep files to worck directory COPY main.go Gopkg.toml Gopkg.toml ./ # Install dep, packages and build application RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh \ && dep version \ && dep ensure \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest # Add ssl support RUN apk --no-cache add ca-certificates WORKDIR /root/ # Copy just the built artifact from the previous stage into this new stage # The Go SDK and any intermediate artifacts are left behind, and not saved in the final image. COPY --from=builder /go/src/github.com/zhooravell/docker-multi-stage-builds/go-multi-stage-build/app ./ EXPOSE 8080 CMD ["./app"]
Вся «хитрость» заключается в конструкции COPY —from=builder. Она копирует артефакт с шага с алиасом builder (FROM golang:1.12.4-stretch as builder). Соберем наш контейнер:
$ docker build --no-cache -t docker-multi-stage-builds/go:latest .
Давайте рассмотрим еще пример использования multi-stage builds.
Angular и Docker multi-stage builds
На основе docker multi-stage build можно реализовать полный жизненный цикл angular-приложения:
- Разработка (npm start).
- Сборка (ng build —prod).
- Деплой (nginx).
Исходный код примера можно получить на GitHub. Docker-файл будет содержать 3 шага (FROM) сборки и выглядеть будет так:
### STAGE 1: Develop ### FROM node:11.14.0-alpine as develop USER node RUN mkdir /home/node/.npm-global && mkdir /home/node/logs ENV PATH=/home/node/.npm-global/bin:$PATH ENV NPM_CONFIG_PREFIX=/home/node/.npm-global ENV HOME=/home/node WORKDIR $HOME/app RUN npm i -g npm RUN npm install -g @angular/cli && npm cache clean --force EXPOSE 4200 CMD [ "node" ] ### STAGE 2: Build ### FROM develop as builder USER root COPY app . RUN npm install && ng build --prod --output-path=dist ### STAGE 3: Setup ### FROM nginx:1.15.12-alpine # Remove default nginx website RUN rm -rf /usr/share/nginx/html/* # From 'builder' stage copy over the artifacts in dist folder to default nginx public folder COPY --from=builder /home/node/app/dist /usr/share/nginx/html
Первый шаг (develop) содержит в себе установку пакета Angular CLI и будет использоваться для разработки. Второй шаг (builder) предназначен для сборки приложения: RUN npm install && ng build —prod —output-path=dist. Третий шаг предназначен для копирования артефакта (сбилдженого приложения) в контейнер с HTTP-сервер.
Для этапа разработки будет использоваться docker-compose. Начиная с версии 3.4, docker-compose поддерживает build.target, что позволяет остановить сборку контейнера на конкретном шаге (в нашем случае этот шаг — develop).
version: "3.4" services: angular: build: context: . target: develop # use stage develop ports: - 4200:4200 volumes: - ./app:/home/node/app:rw command: - /bin/sh - -c - | cd /home/node/app && npm install && npm start
Выполнив команду docker-compose up, мы получим полноценно работающее окружение для разработки Angular-приложения (компиляция TypeScript, релоад браузера и т. д.).
Чтобы получить сборку приложения, готовую к деплою, нужно выполнить команду:
$ docker build --no-cache -t docker-multi-stage-builds/go:latest .
Запуск контейнера с HTTP-сервером и Angular-приложением выполняется так:
$ docker run -p 8080:80 docker-multi-stage-builds/angular:latest
Выводы
Docker multi-stage build позволяет оптимизировать сборку образов. Сделать контейнеры более «легкими», а также унифицировать процесс разработки, сборки и деплоя.
Надеюсь, эта информация была вам полезна.