Многоступенчатая сборка 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 позволяет оптимизировать сборку образов. Сделать контейнеры более «легкими», а также унифицировать процесс разработки, сборки и деплоя.

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

Похожие статьи:
Владислав Сергієв — веброзробник з Чернігова з досвідом роботи 6 років. На початку 2023-го він був призваний на військову службу і зміг...
Уважаемые друзья! Brain Academy приглашает Вас на встречу для работающих ІТ-специалистов, посвященную теме “Стал профессионалом — научи...
Silicon Valley Bank (SVB) останні 40 років служив техноіндустрії та був лідером серед банків, які обслуговували стартапи Кремнієвої долини....
Компания Google подвела очередные итоги использования различных версий операционной системы Android на мобильных устройствах. На ее...
Привет. Я Леша Науменко, позиция моя в Plarium Kharkiv называется Unity Software Architect, и сегодня я расскажу о своем опыте применения...
Яндекс.Метрика