Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Лучше поздно, чем никогда. Или как мы чуть не допустили серьёзную ошибку, не имея поддержки обычных Dockerfiles для сборки образов приложения.
Речь пойдёт про werf — GitOps-утилиту, которая интегрируется с любой CI/CD-системой и обеспечивает управление всем жизненным циклом приложения, позволяя:
Философия проекта — собрать низкоуровневые инструменты в единую унифицированную систему, дающую DevOps-инженерам контроль над приложениями. По возможности должны быть задействованы уже существующие утилиты (вроде Helm и Docker). Если же решения какой-то задачи нет — мы можем создать и поддерживать всё необходимое для этого.
Так и случилось со сборщиком образов в werf: привычного Dockerfile нам не хватало. Если бегло окунуться в историю проекта, то эта проблема проявилась уже в первых версиях werf (тогда еще известного как dapp).
Создавая инструмент для сборки приложений в Docker-образы, мы быстро поняли, что Dockerfile нам не подходит для некоторых вполне конкретных задач:
На сегодняшний день в нашем сборщике есть и многие другие возможности, но изначальные желания и позывы были таковы.
В общем, недолго думая, мы вооружились используемым языком программирования (см. ниже) и отправились в путь — реализовывать собственный DSL! Соответствуя поставленным задачам, он был предназначен для описания процесса сборки по стадиям и определения зависимостей этих стадий от файлов. А дополнял его собственный сборщик, который превращал DSL в конечную цель — собранный образ. Сначала DSL был на Ruby, а по мере перехода на Golang — конфиг нашего сборщика стал описываться в YAML-файле.
Старый конфиг для dapp на Ruby
Актуальный конфиг для werf на YAML
Механизм работы сборщика тоже менялся со временем. Сначала мы просто генерировали на лету некий временный Dockerfile из нашей конфигурации, а потом стали запускать сборочные инструкции во временных контейнерах и делать commit.
NB: На данный момент наш сборщик, который работает со своим конфигом (в YAML) и называется Stapel-сборщиком, уже развился в достаточно мощный инструмент. Его развернутое описание заслуживает отдельных статей, а основные подробности можно узнать из документации.
Но мы поняли, причем не сразу, что совершили одну ошибку: не добавили возможность собирать образы через стандартный Dockerfile и интегрировать их в ту же инфраструктуру комплексного управления приложением (т.е. собирать образы, деплоить и чистить их). Как можно было сделать инструмент для деплоя в Kubernetes и не реализовать поддержку Dockerfile, т.е. стандартного способа описания образов для большинства проектов?..
Вместо ответа на такой вопрос мы предлагаем его решение. Что делать, если у вас уже имеется Dockerfile (или набор Dockerfile’ов) и вы хотите использовать werf?
NB: К слову, с чего бы вам вообще захотеть использовать werf? Основные фичи сводятся к следующим:
С более полным их списком можно ознакомиться на странице проекта.
Итак, если раньше мы бы предложили переписать Dockerfile на наш конфиг, то теперь с радостью скажем: «Позвольте werf собрать ваши Dockerfile’ы!»
Полная реализация этой возможности появилась в релизе werf v1.0.3-beta.1. Общий принцип прост: пользователь указывает путь до существующего Dockerfile в конфиге werf, после чего запускает команду
Объявим следующий
И объявим
Всё! Осталось запустить
Кроме того, можно объявить следующий
Наконец, поддерживается и передача дополнительных параметров сборки — таких как
В процессе сборки функционирует стандартный кэш локальных слоёв в Docker. Однако, что важно, werf также интегрирует конфигурацию Dockerfile в свою инфраструктуру. Что это означает?
Подробнее об описанных здесь моментах можно узнать из документации:
На данный момент не поддерживается использование внешнего URL в директиве
Вообще говоря, добавление директориипорочная плохая практика и вот почему:
Последний пункт имеет последствие и при использовании werf. Werf требует, чтобы собранный кэш присутствовал при запуске некоторых команд (например,
В целом добавление только определенных необходимых файлов через инструкцию
Наш изначальный путь с написанием своего сборщика для определенных потребностей был тяжелым, честным и прямолинейным: вместо использования костылей поверх стандартного Dockerfile мы написали своё решение с кастомным синтаксисом. И это дало свои плюсы: Stapel-сборщик отлично справляется со своей задачей.
Однако в процессе написания собственного сборщика мы упустили из виду поддержку уже существующих Dockerfile’ов. Сейчас этот недостаток исправлен, а в дальнейшем мы планируем развивать поддержку Dockerfile наряду с нашим кастомным сборщиком Stapel для распределенной сборки и для сборки с использованием Kubernetes (т.е. сборки на runner’ах внутри Kubernetes, как это сделано в kaniko).
Так что, если у вас вдруг завалялось пара Dockerfile’ов… попробуйте werf!
Читайте также в нашем блоге: «werf — наш инструмент для CI/CD в Kubernetes (обзор и видео доклада)».
Речь пойдёт про werf — GitOps-утилиту, которая интегрируется с любой CI/CD-системой и обеспечивает управление всем жизненным циклом приложения, позволяя:
- собирать и публиковать образы,
- разворачивать приложения в Kubernetes,
- удалять неиспользуемые образы с помощью специальных политик.
Философия проекта — собрать низкоуровневые инструменты в единую унифицированную систему, дающую DevOps-инженерам контроль над приложениями. По возможности должны быть задействованы уже существующие утилиты (вроде Helm и Docker). Если же решения какой-то задачи нет — мы можем создать и поддерживать всё необходимое для этого.
Предыстория: свой сборщик образов
Так и случилось со сборщиком образов в werf: привычного Dockerfile нам не хватало. Если бегло окунуться в историю проекта, то эта проблема проявилась уже в первых версиях werf (тогда еще известного как dapp).
Создавая инструмент для сборки приложений в Docker-образы, мы быстро поняли, что Dockerfile нам не подходит для некоторых вполне конкретных задач:
- Необходимость собирать типичные небольшие веб-приложения по следующей стандартной схеме:
- установить общесистемные зависимости приложения,
- установить bundle библиотек зависимостей приложения,
- собрать ассеты,
- и самое важное — обновлять код в образе быстро и эффективно.
- При изменениях в файлах проекта сборщик должен быстро создавать новый слой путем наложения патча на измененные файлы.
- Если поменялись определенные файлы, то необходимо пересобирать соответствующую зависимую стадию.
На сегодняшний день в нашем сборщике есть и многие другие возможности, но изначальные желания и позывы были таковы.
В общем, недолго думая, мы вооружились используемым языком программирования (см. ниже) и отправились в путь — реализовывать собственный DSL! Соответствуя поставленным задачам, он был предназначен для описания процесса сборки по стадиям и определения зависимостей этих стадий от файлов. А дополнял его собственный сборщик, который превращал DSL в конечную цель — собранный образ. Сначала DSL был на Ruby, а по мере перехода на Golang — конфиг нашего сборщика стал описываться в YAML-файле.
Старый конфиг для dapp на Ruby
Актуальный конфиг для werf на YAML
Механизм работы сборщика тоже менялся со временем. Сначала мы просто генерировали на лету некий временный Dockerfile из нашей конфигурации, а потом стали запускать сборочные инструкции во временных контейнерах и делать commit.
NB: На данный момент наш сборщик, который работает со своим конфигом (в YAML) и называется Stapel-сборщиком, уже развился в достаточно мощный инструмент. Его развернутое описание заслуживает отдельных статей, а основные подробности можно узнать из документации.
Осознание проблемы
Но мы поняли, причем не сразу, что совершили одну ошибку: не добавили возможность собирать образы через стандартный Dockerfile и интегрировать их в ту же инфраструктуру комплексного управления приложением (т.е. собирать образы, деплоить и чистить их). Как можно было сделать инструмент для деплоя в Kubernetes и не реализовать поддержку Dockerfile, т.е. стандартного способа описания образов для большинства проектов?..
Вместо ответа на такой вопрос мы предлагаем его решение. Что делать, если у вас уже имеется Dockerfile (или набор Dockerfile’ов) и вы хотите использовать werf?
NB: К слову, с чего бы вам вообще захотеть использовать werf? Основные фичи сводятся к следующим:
- полный цикл управления приложением включая очистку образов;
- возможность управлять сборкой сразу нескольких образов из единого конфига;
- улучшенный процесс деплоя совместимых с Helm чартов.
С более полным их списком можно ознакомиться на странице проекта.
Итак, если раньше мы бы предложили переписать Dockerfile на наш конфиг, то теперь с радостью скажем: «Позвольте werf собрать ваши Dockerfile’ы!»
Как использовать?
Полная реализация этой возможности появилась в релизе werf v1.0.3-beta.1. Общий принцип прост: пользователь указывает путь до существующего 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
:Кроме того, можно объявить следующий
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 доступно на странице документации.Как это работает?
В процессе сборки функционирует стандартный кэш локальных слоёв в Docker. Однако, что важно, werf также интегрирует конфигурацию Dockerfile в свою инфраструктуру. Что это означает?
- Каждый образ, собранный из Dockerfile, состоит из одного stage под названием
dockerfile
(подробнее про то, что такое stages в werf, можно почитать здесь). - Для stage’а
dockerfile
werf рассчитывает сигнатуру, которая зависит от содержимого конфигурации Dockerfile. При изменении конфигурации Dockerfile происходит смена сигнатуры стадииdockerfile
и werf инициирует пересборку этой стадии с новым конфигом Dockerfile. Если же сигнатура не меняется, то werf берет образ из кэша (подробнее об использовании сигнатур в werf рассказывалось в этом докладе). - Далее собранные образы можно опубликовать командой
werf publish
(илиwerf build-and-publish
) и использовать для деплоя в Kubernetes. Опубликованные образы в Docker Registry будут чиститься стандартными средствами очистки werf, т.е. произойдет автоматическая очистка старых образов (старше N дней), образов, связанных с несуществующими Git-ветками, и по другим политикам.
Подробнее об описанных здесь моментах можно узнать из документации:
- Процесс публикации;
- Интеграция с процессом деплоя в Kubernetes;
- Процесс очистки.
Примечания и предосторожности
1. Внешний URL в ADD не поддерживается
На данный момент не поддерживается использование внешнего URL в директиве
ADD
. Werf не будет инициировать пересборку при изменении ресурса по указанному URL. В скором времени планируется добавление данной возможности.2. Нельзя добавлять .git в образ
Вообще говоря, добавление директории
.git
в образ — - Если
.git
остается в финальном образе, это нарушает принципы 12 factor app: поскольку итоговый образ должен быть связан с одним коммитом, не должно быть возможности сделатьgit checkout
произвольного коммита. -
.git
увеличивает размер образа (репозиторий может быть большим из-за того, что в него когда-то добавили большие файлы, а потом удалили). Размер же work-tree, связанного только с определенным коммитом, не будет зависеть от истории операций в Git. При этом добавление и последующее удаление.git
из финального образа не сработает: образ все равно приобретет лишний слой — так работает Docker. - Docker может инициировать лишнюю пересборку, даже если идет сборка одного и того же коммита, но из разных work-tree. Например, GitLab создает отдельные склонированные директории в
/home/gitlab-runner/builds/HASH/[0-N]/yourproject
при включенной параллельной сборке. Лишняя пересборка будет связана с тем, что директория.git
отличается в разных склонированных версиях одного и того же репозитория, даже если собирается один и тот же коммит.
Последний пункт имеет последствие и при использовании werf. Werf требует, чтобы собранный кэш присутствовал при запуске некоторых команд (например,
werf deploy
). Во время работы таких команд werf рассчитывает сигнатуры стадий для образов, указанных в werf.yaml
, и они должны быть в сборочном кэше — иначе команда не сможет продолжить работу. Если же сигнатура стадий будет зависеть от содержимого .git
, то мы получаем неустойчивый к изменениям в нерелевантных файлах кэш, и werf не сможет простить такую оплошность (подробнее см. в документации).В целом добавление только определенных необходимых файлов через инструкцию
ADD
в любом случае повышает эффективность и надежность написанного Dockerfile
, а также улучшает устойчивость кэша, собранного по данному Dockerfile
к нерелевантным изменениям в Git.Итог
Наш изначальный путь с написанием своего сборщика для определенных потребностей был тяжелым, честным и прямолинейным: вместо использования костылей поверх стандартного Dockerfile мы написали своё решение с кастомным синтаксисом. И это дало свои плюсы: Stapel-сборщик отлично справляется со своей задачей.
Однако в процессе написания собственного сборщика мы упустили из виду поддержку уже существующих Dockerfile’ов. Сейчас этот недостаток исправлен, а в дальнейшем мы планируем развивать поддержку Dockerfile наряду с нашим кастомным сборщиком Stapel для распределенной сборки и для сборки с использованием Kubernetes (т.е. сборки на runner’ах внутри Kubernetes, как это сделано в kaniko).
Так что, если у вас вдруг завалялось пара Dockerfile’ов… попробуйте werf!
P.S. Список документации по теме
- Гайды для быстрого старта;
- Конфигурации dockerfile builder;
- Устройство stages в werf;
- Процесс публикации образов;
- Интеграция с процессом деплоя в Kubernetes;
- Процесс очистки;
- Stapel-сборщик как альтернатива Dockerfile.
Читайте также в нашем блоге: «werf — наш инструмент для CI/CD в Kubernetes (обзор и видео доклада)».