Добавление образов

Для сборки c werf необходимо добавить описание образов в werf.yaml проекта. Каждый образ добавляется директивой image с указанием имени образа:

project: example
configVersion: 1
---
image: frontend
# ...
---
image: backend
# ...
---
image: database
# ...

Имя образа — это уникальный внутренний идентификатор образа, который позволяет ссылаться на него при конфигурации и при вызове команд werf.

Далее для каждого образа в werf.yaml необходимо определить сборочные инструкции с помощью Dockerfile или stapel.

Dockerfile

Написание Dockerfile-инструкций

Для описания сборочных инструкций образа поддерживается стандартный Dockerfile. Следующие ресурсы помогут в его написании:

Использование Dockerfile

Конфигурация сборки Dockerfile может выглядеть следующим образом:

# Dockerfile
FROM node
WORKDIR /app
COPY package*.json /app/
RUN npm ci
COPY . .
CMD ["node", "server.js"]
# werf.yaml
project: example
configVersion: 1
---
image: backend
dockerfile: Dockerfile

Использование определённой Dockerfile-стадии

Также вы можете описывать несколько целевых образов из разных стадий одного и того же Dockerfile:

# Dockerfile
FROM node as backend
WORKDIR /app
COPY package*.json /app/
RUN npm ci
COPY . .
CMD ["node", "server.js"]

FROM python as frontend
WORKDIR /app
COPY requirements.txt /app/
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-"]
# werf.yaml
project: example
configVersion: 1
---
image: backend
dockerfile: Dockerfile
target: backend
---
image: frontend
dockerfile: Dockerfile
target: frontend

И конечно вы можете описывать образы, основанные на разных Dockerfile:

# werf.yaml
project: example
configVersion: 1
---
image: backend
dockerfile: dockerfiles/Dockerfile.backend
---
image: frontend
dockerfile: dockerfiles/Dockerfile.frontend

Выбор директории сборочного контекста

Чтобы указать сборочный контекст используется директива context. Важно: в этом случае путь до Dockerfile указывается относительно директории контекста:

project: example
configVersion: 1
---
image: docs
context: docs
dockerfile: Dockerfile
---
image: service
context: service
dockerfile: Dockerfile

Для образа docs будет использоваться Dockerfile по пути docs/Dockerfile, а для serviceservice/Dockerfile.

Использование сборочных секретов

ЗАМЕЧАНИЕ: Чтобы использовать секреты в сборках, их нужно явно разрешить в настройках гитерминизма. Подробнее (здесь)

Секрет сборки — это любая конфиденциальная информация, например пароль или токен API, используемая в процессе сборки вашего приложения.

Аргументы сборки и переменные окружения не подходят для передачи секретов в сборку, поскольку они сохраняются в конечном образе.

Вы можете использовать секреты при сборке, описав их в werf.yaml.

# werf.yaml
project: example
configVersion: 1
---
image: backend
dockerfile: Dockerfile
secrets:
  - env: AWS_ACCESS_KEY_ID
  - id: aws_secret_key
    env: AWS_SECRET_ACCESS_KEY
  - src: "~/.aws/credentials"
  - id: plainSecret
    value: plainSecretValue
# werf-giterminism.yaml
giterminismConfigVersion: 1

config:
  secrets:
    allowEnvVariables:
      - "AWS_ACCESS_KEY_ID"
    allowFiles:
      - "~/.aws/credentials"
    allowValueIds:
      - plainSecret

Чтобы использовать секрет в сборке и сделать его доступным для инструкции RUN, используйте флаг --mount=type=secret в Dockerfile.

При использовании секрета в Dockerfile, секрет монтируется в файл по умолчанию. Путь к файлу секрета по умолчанию внутри контейнера сборки — /run/secrets/<id>. Если id секрета явно не указан в werf.yaml, то в качестве id будет использовано значение по умолчанию:

  • Для env — имя переменной окружения.
  • Для src — имя конечного файла (например, для /path/to/file будет использован id: file).

Для value — поле id является обязательным.

# Dockerfile
FROM alpine:3.18

# Пример использования секрета из переменной окружения
RUN --mount=type=secret,id=AWS_ACCESS_KEY_ID \
    export WERF_BUILD_SECRET="$(cat /run/secrets/AWS_ACCESS_KEY_ID)"

# Пример использования секрета из файла с секретами
RUN --mount=type=secret,id=credentials \
    AWS_SHARED_CREDENTIALS_FILE=/run/secrets/credentials \
    aws s3 cp ...

# Пример монтирования секрета как переменную окружения
RUN --mount=type=secret,id=AWS_ACCESS_KEY_ID,env=AWS_ACCESS_KEY_ID \
    --mount=type=secret,id=aws-secret-key,env=AWS_SECRET_ACCESS_KEY \
    aws s3 cp ...

# Пример монтирования секрета как файл с другим именем
RUN --mount=type=secret,id=credentials,target=/root/.aws/credentials \
    aws s3 cp ...

# Пример использования произвольного значения, которое не будет сохранено в конечном образе
RUN --mount=type=secret,id=plainSecret \
    export WERF_BUILD_SECRET="$(cat /run/secrets/plainSecret)"

Использование SSH-агента

Вы можете предоставить доступ к сокету SSH-агента или SSH-ключам во время сборки. Это удобно, если ваш Dockerfile содержит команды, требующие SSH-аутентификации, например, для клонирования приватных репозиториев.

Для этого используйте флаг --mount=type=ssh в командах RUN:

FROM alpine
RUN apk add --no-cache openssh-client
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh ssh -q -T git@gitlab.com 2>&1 | tee /hello

Подробную информацию об использовании SSH-агента можно найти здесь.

Добавление произвольных файлов в сборочный контекст

По умолчанию контекст сборки Dockerfile-образа включает только файлы из текущего коммита репозитория проекта. Файлы, не добавленные в Git, или некоммитнутые изменения не попадают в сборочный контекст. Такая логика действует в соответствии с настройками гитерминизма по умолчанию.

Чтобы добавить в сборочный контекст файлы, которые не хранятся в Git, нужна директива contextAddFiles в werf.yaml, а также нужно разрешить использование директивы contextAddFiles в werf-giterminism.yaml (подробнее про гитерминизм):

# werf.yaml
project: example
configVersion: 1
---
image: app
dockerfile: Dockerfile
context: app
contextAddFiles:
- file1
- dir1/
- dir2/file2.out
# werf-giterminism.yaml
giterminismConfigVersion: 1
config:
  dockerfile:
    allowContextAddFiles:
    - app/file1
    - app/dir1/
    - app/dir2/file2.out

В данной конфигурации контекст сборки будет состоять из следующих файлов:

  • app/**/* из текущего коммита репозитория проекта;
  • файлы app/file1, app/dir2/file2.out и директория dir1, которые находятся в директории проекта.

Мультиплатформенная сборка

werf поддерживает мультиплатформенную и кроссплатформенную сборку, что позволяет создавать образы для различных архитектур и операционных систем (подробнее в соответствующем разделе документации).

Кросс-компиляция

Если в вашем проекте требуется кросс-компиляция, вы можете использовать multi-stage сборки для создания артефактов для целевых платформ. Для этого доступны следующие аргументы сборки:

  • TARGETPLATFORM: платформа для сборки результата (например, linux/amd64, linux/arm/v7, windows/amd64).
  • TARGETOS: ОС целевой платформы.
  • TARGETARCH: архитектура целевой платформы.
  • TARGETVARIANT: вариант целевой платформы.
  • BUILDPLATFORM: платформа узла, выполняющего сборку.
  • BUILDOS: ОС платформы сборщика.
  • BUILDARCH: архитектура платформы сборщика.
  • BUILDVARIANT: вариант платформы сборщика.

Пример:

FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log

FROM alpine
COPY --from=build /log /log

В этом примере инструкция FROM закреплена за родной платформой сборщика с помощью опции --platform=$BUILDPLATFORM, чтобы предотвратить эмуляцию. Аргументы $BUILDPLATFORM и $TARGETPLATFORM затем используются в инструкции RUN.

Stapel

В werf встроен альтернативный синтаксис описания сборочных инструкций, называемый stapel. Подробная документация по синтаксису stapel доступна в соответствующей секции документации.

Пример минимальной конфигурации stapel-образа в werf.yaml:

project: example
configVersion: 1
---
image: app
from: ubuntu:22.04

Добавим исходники из Git в образ:

project: example
configVersion: 1
---
image: app
from: ubuntu:22.04
git:
- add: /
  to: /app

Доступно 4 стадии для описания произвольных shell-инструкций, а также директива git.stageDependencies для настройки триггеров пересборки этих стадий при изменении соответствующих стадий (см. подробнее):

project: example
configVersion: 1
---
image: app
from: ubuntu:22.04
git:
- add: /
  to: /app
  stageDependencies:
    install:
    - package-lock.json
    - Gemfile.lock
    beforeSetup:
    - app/webpack/
    - app/assets/
    setup:
    - config/templates/
shell:
  beforeInstall:
  - apt update -q
  - apt install -y libmysqlclient-dev mysql-client g++
  install:
  - bundle install
  - npm install
  beforeSetup:
  - bundle exec rails assets:precompile
  setup:
  - rake generate:configs

Поддерживаются вспомогательные образы, из которых можно импортировать файлы в целевой образ (аналог COPY --from=STAGE в multi-stage Dockerfile), а также Golang-шаблонизация:

{{ $_ := set . "BaseImage" "ubuntu:22.04" }}

{{ define "package:build-tools" }}
  - apt update -q
  - apt install -y gcc g++ build-essential make
{{ end }}

project: example
configVersion: 1
---
image: builder
from: {{ .BaseImage }}
shell:
  beforeInstall:
{{ include "package:build-tools" }}
  install:
  - cd /app
  - make build
---
image: app
from: alpine:latest
import:
- image: builder
  add: /app/build/app
  to: /usr/local/bin/app
  after: install

Подробная документация по написанию доступна в разделе stapel.

Взаимодействие между образами

Наследование и импортирование файлов

При написании одного Dockerfile в нашем распоряжении имеется механизм multi-stage. Он позволяет объявить в Dockerfile отдельный образ-стадию и использовать её в качестве базового для другого образа, либо скопировать из неё отдельные файлы.

werf позволяет реализовать это не только в рамках одного Dockerfile, но и между произвольными образами, определяемыми в werf.yaml, в том числе собираемыми из разных Dockerfile’ов, либо собираемыми сборщиком stapel. Всю оркестрацию и выстраивание зависимостей werf возьмёт на себя и произведёт сборку за один шаг (вызов werf build).

Пример использования образа собранного из base.Dockerfile в качестве базового для образа из Dockerfile:

# base.Dockerfile
FROM ubuntu:22.04
RUN apt update -q && apt install -y gcc g++ build-essential make curl python3
# Dockerfile
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
WORKDIR /app
COPY . .
CMD [ "/app/server", "start" ]
# werf.yaml
project: example
configVersion: 1
---
image: base
dockerfile: base.Dockerfile
---
image: app
dockerfile: Dockerfile
dependencies:
- image: base
  imports:
  - type: ImageName
    targetBuildArg: BASE_IMAGE

В следующем примере рассмотрено импортирование файлов из образа stapel в образ Dockerfile:

# werf.yaml
project: example
configVersion: 1
---
image: builder
from: golang:1.23rc1-alpine3.20
git:
- add: /
  to: /app
shell:
  install:
  - cd /app
  - go build -o /app/bin/server
---
image: app
dockerfile: Dockerfile
dependencies:
- image: builder
  imports:
  - type: ImageName
    targetBuildArg: BUILDER_IMAGE
# Dockerfile
ARG BUILDER_IMAGE
FROM ${BUILDER_IMAGE} AS builder

FROM alpine
COPY --from=builder /app/bin /app/bin
CMD [ "/app/bin/server", "server" ]

Передача информации о собранном образе в другой образ

werf позволяет получить информацию о собранном образе при сборке другого образа. Например, если в сборочных инструкциях образа app требуются имена и digest’ы образов auth и controlplane, опубликованных в container registry, то конфигурация могла бы выглядеть так:

# modules/auth/Dockerfile
FROM alpine
WORKDIR /app
COPY . .
RUN ./build.sh
# modules/controlplane/Dockerfile
FROM alpine
WORKDIR /app
COPY . .
RUN ./build.sh
# Dockerfile
FROM alpine
WORKDIR /app
COPY . .

ARG AUTH_IMAGE_NAME
ARG AUTH_IMAGE_DIGEST
ARG CONTROLPLANE_IMAGE_NAME
ARG CONTROLPLANE_IMAGE_DIGEST

RUN echo AUTH_IMAGE_NAME=${AUTH_IMAGE_NAME}                     >> modules_images.env
RUN echo AUTH_IMAGE_DIGEST=${AUTH_IMAGE_DIGEST}                 >> modules_images.env   
RUN echo CONTROLPLANE_IMAGE_NAME=${CONTROLPLANE_IMAGE_NAME}     >> modules_images.env
RUN echo CONTROLPLANE_IMAGE_DIGEST=${CONTROLPLANE_IMAGE_DIGEST} >> modules_images.env
# werf.yaml
project: example
configVersion: 1
---
image: auth
dockerfile: Dockerfile
context: modules/auth/
---
image: controlplane
dockerfile: Dockerfile
context: modules/controlplane/
---
image: app
dockerfile: Dockerfile
dependencies:
- image: auth
  imports:
  - type: ImageName
    targetBuildArg: AUTH_IMAGE_NAME
  - type: ImageDigest
    targetBuildArg: AUTH_IMAGE_DIGEST
- image: controlplane
  imports:
  - type: ImageName
    targetBuildArg: CONTROLPLANE_IMAGE_NAME
  - type: ImageDigest
    targetBuildArg: CONTROLPLANE_IMAGE_DIGEST

В процессе сборки werf автоматически подставит в указанные build-arguments соответствующие имена и идентификаторы. Всю оркестрацию и выстраивание зависимостей werf возьмёт на себя и произведёт сборку за один шаг (вызов werf build).

Использование промежуточных и конечных образов

По умолчанию все образы являются конечными, что позволяет пользователю оперировать ими, используя их имена в качестве аргументов для большинства команд werf, а также в шаблонах Helm-чарта. С помощью директивы final можно регулировать это свойство образа.

Промежуточные образы (final: false) в отличие от конечных:

Пример использования директивы final:

project: example
configVersion: 1
---
image: builder
final: false
dockerfile: Dockerfile.builder
---
image: app
dockerfile: Dockerfile.app
dependencies:
- image: builder
  imports:
  - type: ImageName
    targetBuildArg: BUILDER_IMAGE_NAME