- PVSM.RU - https://www.pvsm.ru -

Собирать Docker-образы в werf теперь можно и по обычному Dockerfile

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

Собирать Docker-образы в werf теперь можно и по обычному Dockerfile - 1

Речь пойдёт про werf [1] — GitOps-утилиту, которая интегрируется с любой CI/CD-системой и обеспечивает управление всем жизненным циклом приложения, позволяя:

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


Философия проекта — собрать низкоуровневые инструменты в единую унифицированную систему, дающую DevOps-инженерам контроль над приложениями. По возможности должны быть задействованы уже существующие утилиты (вроде Helm и Docker). Если же решения какой-то задачи нет — мы можем создать и поддерживать всё необходимое для этого.

Предыстория: свой сборщик образов

Так и случилось со сборщиком образов в werf: привычного Dockerfile нам не хватало. Если бегло окунуться в историю проекта, то эта проблема проявилась уже в первых версиях werf (тогда еще известного как dapp [2]).

Создавая инструмент для сборки приложений в Docker-образы, мы быстро поняли, что Dockerfile нам не подходит для некоторых вполне конкретных задач:

  1. Необходимость собирать типичные небольшие веб-приложения по следующей стандартной схеме:
    • установить общесистемные зависимости приложения,
    • установить bundle библиотек зависимостей приложения,
    • собрать ассеты,
    • и самое важное — обновлять код в образе быстро и эффективно.
  2. При изменениях в файлах проекта сборщик должен быстро создавать новый слой путем наложения патча на измененные файлы.
  3. Если поменялись определенные файлы, то необходимо пересобирать соответствующую зависимую стадию.

На сегодняшний день в нашем сборщике есть и многие другие возможности, но изначальные желания и позывы были таковы.

В общем, недолго думая, мы вооружились используемым языком программирования (см. ниже) и отправились в путь — реализовывать собственный DSL! Соответствуя поставленным задачам, он был предназначен для описания процесса сборки по стадиям и определения зависимостей этих стадий от файлов. А дополнял его собственный сборщик, который превращал DSL в конечную цель — собранный образ. Сначала DSL был на Ruby, а по мере перехода на Golang [3] — конфиг нашего сборщика стал описываться в YAML-файле.

Собирать Docker-образы в werf теперь можно и по обычному Dockerfile - 2
Старый конфиг для dapp на Ruby

Собирать Docker-образы в werf теперь можно и по обычному Dockerfile - 3
Актуальный конфиг для werf на YAML

Механизм работы сборщика тоже менялся со временем. Сначала мы просто генерировали на лету некий временный Dockerfile из нашей конфигурации, а потом стали запускать сборочные инструкции во временных контейнерах и делать commit.

NB: На данный момент наш сборщик, который работает со своим конфигом (в YAML) и называется Stapel-сборщиком, уже развился в достаточно мощный инструмент. Его развернутое описание заслуживает отдельных статей, а основные подробности можно узнать из документации [4].

Осознание проблемы

Но мы поняли, причем не сразу, что совершили одну ошибку: не добавили возможность собирать образы через стандартный Dockerfile и интегрировать их в ту же инфраструктуру комплексного управления приложением (т.е. собирать образы, деплоить и чистить их). Как можно было сделать инструмент для деплоя в Kubernetes и не реализовать поддержку Dockerfile, т.е. стандартного способа описания образов для большинства проектов?..

Вместо ответа на такой вопрос мы предлагаем его решение. Что делать, если у вас уже имеется Dockerfile (или набор Dockerfile’ов) и вы хотите использовать werf?

NB: К слову, с чего бы вам вообще захотеть использовать werf? Основные фичи сводятся к следующим:

  • полный цикл управления приложением включая очистку образов;
  • возможность управлять сборкой сразу нескольких образов из единого конфига;
  • улучшенный процесс деплоя совместимых с Helm чартов.

С более полным их списком можно ознакомиться на странице проекта [5].

Итак, если раньше мы бы предложили переписать Dockerfile на наш конфиг, то теперь с радостью скажем: «Позвольте werf собрать ваши Dockerfile’ы!»

Как использовать?

Полная реализация этой возможности появилась в релизе werf v1.0.3-beta.1 [6]. Общий принцип прост: пользователь указывает путь до существующего Dockerfile в конфиге werf, после чего запускает команду werf build… и всё — werf соберёт образ. Рассмотрим на абстрактном примере.

Объявим следующий Dockerfile в корне проекта:

FROM ubuntu:18.04
RUN echo Building ...

И объявим werf.yaml, который использует этот Dockerfile:

configVersion: 1
project: dockerfile-example
---
image: ~
dockerfile: ./Dockerfile

Всё! Осталось запустить werf build:

Собирать Docker-образы в werf теперь можно и по обычному Dockerfile - 4

Кроме того, можно объявить следующий werf.yaml для сборки сразу нескольких образов из разных Dockerfile’ов:

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

Наконец, поддерживается и передача дополнительных параметров сборки — таких как --build-arg и --add-host — через конфиг werf. Полное описание конфигурации Dockerfile image доступно на странице документации [7].

Как это работает?

В процессе сборки функционирует стандартный кэш локальных слоёв в Docker. Однако, что важно, werf также интегрирует конфигурацию Dockerfile в свою инфраструктуру. Что это означает?

  1. Каждый образ, собранный из Dockerfile, состоит из одного stage под названием dockerfile (подробнее про то, что такое stages в werf, можно почитать здесь [8]).
  2. Для stage’а dockerfile werf рассчитывает сигнатуру, которая зависит от содержимого конфигурации Dockerfile. При изменении конфигурации Dockerfile происходит смена сигнатуры стадии dockerfile и werf инициирует пересборку этой стадии с новым конфигом Dockerfile. Если же сигнатура не меняется, то werf берет образ из кэша (подробнее об использовании сигнатур в werf рассказывалось в этом докладе [9]).
  3. Далее собранные образы можно опубликовать командой werf publish (или werf build-and-publish) и использовать для деплоя в Kubernetes. Опубликованные образы в Docker Registry будут чиститься стандартными средствами очистки werf, т.е. произойдет автоматическая очистка старых образов (старше N дней), образов, связанных с несуществующими Git-ветками, и по другим политикам.

Подробнее об описанных здесь моментах можно узнать из документации:

Примечания и предосторожности

1. Внешний URL в ADD не поддерживается

На данный момент не поддерживается использование внешнего URL в директиве ADD. Werf не будет инициировать пересборку при изменении ресурса по указанному URL. В скором времени планируется добавление данной возможности.

2. Нельзя добавлять .git в образ

Вообще говоря, добавление директории .git в образ — порочная плохая практика и вот почему:

  1. Если .git остается в финальном образе, это нарушает принципы 12 factor app [13]: поскольку итоговый образ должен быть связан с одним коммитом, не должно быть возможности сделать git checkout произвольного коммита.
  2. .git увеличивает размер образа (репозиторий может быть большим из-за того, что в него когда-то добавили большие файлы, а потом удалили). Размер же work-tree, связанного только с определенным коммитом, не будет зависеть от истории операций в Git. При этом добавление и последующее удаление .git из финального образа не сработает: образ все равно приобретет лишний слой — так работает Docker.
  3. Docker может инициировать лишнюю пересборку, даже если идет сборка одного и того же коммита, но из разных work-tree. Например, GitLab создает отдельные склонированные директории в /home/gitlab-runner/builds/HASH/[0-N]/yourproject при включенной параллельной сборке. Лишняя пересборка будет связана с тем, что директория .git отличается в разных склонированных версиях одного и того же репозитория, даже если собирается один и тот же коммит.

Последний пункт имеет последствие и при использовании werf. Werf требует, чтобы собранный кэш присутствовал при запуске некоторых команд (например, werf deploy). Во время работы таких команд werf рассчитывает сигнатуры стадий для образов, указанных в werf.yaml, и они должны быть в сборочном кэше — иначе команда не сможет продолжить работу. Если же сигнатура стадий будет зависеть от содержимого .git, то мы получаем неустойчивый к изменениям в нерелевантных файлах кэш, и werf не сможет простить такую оплошность (подробнее см. в документации [14]).

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

Итог

Наш изначальный путь с написанием своего сборщика для определенных потребностей был тяжелым, честным и прямолинейным: вместо использования костылей поверх стандартного Dockerfile мы написали своё решение с кастомным синтаксисом. И это дало свои плюсы: Stapel-сборщик отлично справляется со своей задачей.

Однако в процессе написания собственного сборщика мы упустили из виду поддержку уже существующих Dockerfile’ов. Сейчас этот недостаток исправлен, а в дальнейшем мы планируем развивать поддержку Dockerfile наряду с нашим кастомным сборщиком Stapel для распределенной сборки и для сборки с использованием Kubernetes (т.е. сборки на runner’ах внутри Kubernetes, как это сделано в kaniko).

Так что, если у вас вдруг завалялось пара Dockerfile’ов… попробуйте werf [1]!

P.S. Список документации по теме

Читайте также в нашем блоге: «werf — наш инструмент для CI/CD в Kubernetes (обзор и видео доклада) [9]».

Автор: Timofey Kirillov

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/327739

Ссылки в тексте:

[1] werf: https://github.com/flant/werf

[2] известного как dapp: https://habr.com/ru/company/flant/blog/333682/

[3] перехода на Golang: https://habr.com/ru/company/flant/blog/437044/

[4] документации: https://werf.io/documentation/reference/build_process.html#stapel-image-and-artifact

[5] странице проекта: https://github.com/flant/werf#complete-features-list

[6] werf v1.0.3-beta.1: https://github.com/flant/werf/releases/tag/v1.0.3-beta.1

[7] странице документации: https://werf.io/documentation/configuration/dockerfile_image.html

[8] здесь: https://werf.io/documentation/reference/stages_and_images.html#stages

[9] этом докладе: https://habr.com/ru/company/flant/blog/460351/

[10] Процесс публикации: https://werf.io/documentation/reference/publish_process.html

[11] Интеграция с процессом деплоя в Kubernetes: https://werf.io/documentation/reference/deploy_process/deploy_into_kubernetes.html#integration-with-built-images

[12] Процесс очистки: https://werf.io/documentation/reference/cleaning_process.html

[13] 12 factor app: https://12factor.net/build-release-run

[14] документации: https://werf.io/documentation/reference/stages_and_images.html

[15] Гайды для быстрого старта: https://werf.io/documentation/guides/getting_started.html

[16] Источник: https://habr.com/ru/post/463613/?utm_source=habrahabr&utm_medium=rss&utm_campaign=463613