Багатоступенева збірка Docker-образу

Розробка ПО — складний процес, результатом якого є працюючий «в миру» продукт/сервіс. Давайте познайомимося з підходом, що дозволяє спростити життєвий цикл розробки.

Реалізація

Починаючи з версії 17.05, в докері з'явилися багатоступінчасті білди. Багатоступінчасті складання корисні для всіх, хто намагається оптимізувати Docker-файли і образи, зберігаючи їх легкими для читання та обслуговування. До появи цієї фічі застосовували підхід під назвою «Builder Pattern». Підхід «Builder Pattern» полягає у створенні двох Docker-файлів і sh-скрипта:

Приклад «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 апк --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.8 MB
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 апк --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 && start npm

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

Сподіваюся, ця інформація була вам корисна.

Опубліковано: 26/04/19 @ 10:00
Розділ Різне

Рекомендуємо:

Програміст Антон Максимчук – про роботу в IBM, труднощі легалізації в Польщі й повернення до України
DOU Ревізор в Innovecs: цілодобовий R&D-центр на п'ять поверхів
Еволюція зарплат: як Junior Java Developer за 11 років став PM c $8000
Подорожі, розвиток та рівні права усім: web-аналітик Галина Харківська про роботу в Booking та життя в Амстердамі
Увагу Google Recaptcha v3 + Contact Form 7