Хабрахабр

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

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

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

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

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

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

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

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

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

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

ниже) и отправились в путь — реализовывать собственный 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 в свою инфраструктуру. Что это означает?

  1. Каждый образ, собранный из Dockerfile, состоит из одного stage под названием dockerfile (подробнее про то, что такое stages в werf, можно почитать здесь).
  2. Для stage’а dockerfile werf рассчитывает сигнатуру, которая зависит от содержимого конфигурации Dockerfile. При изменении конфигурации Dockerfile происходит смена сигнатуры стадии dockerfile и werf инициирует пересборку этой стадии с новым конфигом Dockerfile. Если же сигнатура не меняется, то werf берет образ из кэша (подробнее об использовании сигнатур в werf рассказывалось в этом докладе).
  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: поскольку итоговый образ должен быть связан с одним коммитом, не должно быть возможности сделать 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 не сможет простить такую оплошность (подробнее см. в документации).

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

Итог

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть