Многоступенчатая сборка 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"]

build.sh

#!/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-приложения:

  1. Разработка (npm start).
  2. Сборка (ng build —prod).
  3. Деплой (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 позволяет оптимизировать сборку образов. Сделать контейнеры более «легкими», а также унифицировать процесс разработки, сборки и деплоя.

Надеюсь, эта информация была вам полезна.

Похожие статьи:
Если вы хотя бы раз пытались найти/нанять толкового менеджера, то вы, скорее всего, знаете, что данная, на первый взгляд, тривиальная...
В рубрике DOU Labs мы приглашаем IT-компании делиться опытом собственных интересных разработок и внутренних технологических...
Фонд BrainBasket при поддержке Администрации Президента Украины запускает программу Technology Nation — бесплатный курс по основам...
Почитать Фейсбуковская компания Parse выложила в открытый доступ свои SDK. Пост о том, как строить современные...
Український стартап Datrics у 2021 році залучив понад 1 млн доларів інвестицій та пройшов в YCombinator. Це no-code...
Яндекс.Метрика