company_banner

Собирать 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’ов. Сейчас этот недостаток исправлен, а в дальнейшем мы планируем развивать поддержку Dockerfile наряду с нашим кастомным сборщиком Stapel для распределенной сборки и для сборки с использованием Kubernetes (т.е. сборки на runner’ах внутри Kubernetes, как это сделано в kaniko).

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

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



    Читайте также в нашем блоге: «werf — наш инструмент для CI/CD в Kubernetes (обзор и видео доклада)».
    Флант
    505,86
    Специалисты по DevOps и Kubernetes
    Поделиться публикацией

    Комментарии 27

      +1
      Упустили еще одно, пожалуй, главное преимущество докерфайлов – разработчики обычно работают с теми же образами через docker-compose.
      Держать две конфигурации смысла нет.
        +4

        Планируем плотно развивать тему локальной разработки с использованием werf. Это возможно включает и поддержку docker-compose. Сейчас работаем над стабилизацией версии 1.0. Где-то в конце этого года можно ждать подвижек.

        +3
        Тимофей, у меня к вам еще вопрос:
        Я видел драму в issue helm'a c интеграцией поддержки kubedog в нем.
        Можно расчитывать, что вы не оставите надежду интегрировать этот функционал в Helm 3?
          +3

          Не оставим, но в любом случае, когда выйдет стабильный helm 3 и будет способ мигрировать старые инсталляции на новый хельм — мы перейдем на кодовую базу helm 3 и будем использовать и развивать kubedog для слежения за ресурсами в werf.


          Короче советую переходить на werf ;)

            0
            Использование werf'a в GitLab где и так есть CI это overhead, теряется гибкость и информативность.
            Вы рассматривали возможность использования helper image в раннере вместо использования тулзы as is в одном джобе?

            В принципе ваша стратегия мне понятна – одно решение для любой системы хранения кода (или без неё вовсе), но всё-же это не очень удобно.
              +2

              Пока нет, не смотрели, выглядит как немного не то.


              Но интеграция с самим gitlab у нас есть и достаточно плотная:



              Например werf автоматом использует токены, которые выдает gitlab для логина в docker-registry, который также задает gitlab. Werf автоматом использует gitlab-environment если он определен.


              Так-то по возможности стараемся лишнего не делать, если это можно переложить на внешнюю CI-систему.


              Какой в gitlab есть ci конкретно для деплоя в кубы, где верф будет избыточен, можно подробнее на примере? Везде где используется kubectl apply или helm upgrade можно использовать и werf deploy — тут никаких ограничений не вносится, даже наоборот werf deploy проще использовать из-за наличия интеграции с гитлабом.

          +3
          Чего хотелось бы еще от werf, по небольшому опыту применения
          * Сборка образов на машине разработчика (для тестирования и отладки инструкций) без лишних приседаний, включая работу под Win, как, собственно, можно делать с Dockerfile.
          * Параллельная сборка независимых stages. Это сделало бы werf реально мощнее того, что может сейчас предложить многоконтейнерный Dockerfile.
            –11
            В двух словах зачем эти велосипеды нужны, когда есть встроенный docker swarm?
              –11
              Что за биомусор минусует в карму на ровном месте?
                0

                Werf ориентирован на kubernetes. И чтобы деплоить приложения туда. И в дальнейшем чтобы собирать образы используя runner-ы работающие в самом kubernetes.

                  0

                  А какие преимущества по сравнению с использованием нативных манифестов Kubernetes и kubectl в pipeline? Ведь очистить слои в docker можно и другими способами? Всегда возникает вопрос можно ли положиться на новую технологию, если это разработка одного человека, где гарантии что она будет поддерживаться и развиваться?

                    +5
                    «Всегда возникает вопрос можно ли положиться на новую технологию, если это разработка одного человека, где гарантии что она будет поддерживаться и развиваться?» — технология не так уж нова, проекту не один год. Разрабатывается он не одним человеком, а хотя бы «одной компанией» (насколько эта формулировка корректно для Open Source-проекта, конечно же, принимающего и сторонние коммиты/контрибьюторов). Посмотрите за историей развития, например, по коммитам, чтобы понять, сколько мы в нее вкладываем — это по-настоящему огромная и решающая многое инвестиция для нас как компании, помогающей организовать другим DevOps и сопутствующее обслуживание.

                    Риск, конечно, всегда есть, но это ещё и Open Source, что при достаточном сообществе + использовании стандартных для экосистемы технологий (речь и про сам язык, и про применяемые внутри технологии вроде Helm) по меньшей мере даёт куда большие шансы на жизнь в будущем, чем в иных ситуациях.

                    Этот пост (и сама фича, про которую он) — хорошая иллюстрация того, как мы стремимся развивать сообщество вокруг проекта. Заходите ещё в tg-канал (werf_ru), чтобы увидеть, как активно там люди («сторонние», т.е. вне «Фланта») пользуются и им помогают, вплоть до оперативных патчей, решающих их проблемы.

                    О преимуществах werf в сравнении с использованием родных средств Kubernetes (и не только) хорошо рассказано в недавнем докладе (по ссылке краткий его обзор и там же полное видео). Там как раз о проблемах, которые в принципе возникают при построении Continuous Delivery на базе K8s, и как они решены конкретно в werf.
                      0

                      Спасибо за содержательный ответ. Нисколько не критикую, а наоборот поддерживаю вашу разработку, пока морально. Просто иногда для начинающих не понятны мотивы использовать сторонних средств для деплоя. Например, мне показалось, что вы делаете альтернативный буилдер, который решает ваши конкретные сложности в инфраструктуре.
                      В большинстве случаев сто-двести микросервисов, которые работают с БД легко разруливаются одним человеком, который в теме разработки.

                +1

                target из Докер файла можно указать?


                Экспериментальный синтаксис с buildkit поддерживается? Все эти RUN --mount=…


                И только для сборки можно использовать? А то сегодня полдня писал build.sh — грустно как то…

                  +2

                  Target можно:


                  configVersion: 1
                  project: myproj
                  ---
                  image: backend
                  dockerfile: ./Dockerfile
                  target: backend
                  ---
                  image: frontend
                  dockerfile: ./Dockerfile
                  target: frontend

                  Поддерживается любой синтаксис стандартного Dockerfile, т.к. для билда используется docker server (который может использовать buildkit).


                  После того как образы описаны в werf.yaml, собраны и опубликованы (через werf build-and-publish), их можно использовать для деплоя в кубы. Для этого надо описать chart. В описанном чарте можно ссылаться на имена образов из werf.yaml, в нашем случае например так:


                  apiVersion: apps/v1beta1
                  kind: Deployment
                  metadata:
                    name: backend
                  spec:
                    template:
                      metadata:
                        labels:
                          service: backend
                      spec:
                        containers:
                        - name: main
                          command: [ ... ]
                  {{ tuple "backend" . | include "werf_container_image" | indent 8 }}
                          env:
                  {{ tuple "backend" . | include "werf_container_env" | indent 8 }}
                    0

                    Имелось в виду, можно ли использовать исключительно для сборки, возможностями деплоя не пользуясь. Только закончил описывать собственно деплой на helm 3, перешёл к написанию build.sh "универсального", намучался с "конфигом" на баш массивах, а тут ваш пост. :)

                    +1
                    Поддерживается любой синтаксис стандартного Dockerfile, т.к. для билда используется docker server (который может использовать buildkit).

                    Поигрался сегодня, не смог заставить интерпретировать директиву syntax, создал issue https://github.com/flant/werf/issues/1767 — оно и не должно работать? Или нужно в схему werf.yaml добавлять какой-то buildkit: true, чтобы эмулировать что-то вроде DOCKER_BUILDKIT=1 werf build --stages-storage :local?

                      +3

                      Решили. Была проблема с использованием RUN --mount.


                      https://github.com/flant/werf/pull/1769


                      Проверить можно в бета-канале <(multiwerf use 1.0 beta).

                  0

                  Зачем вообще упоминать команду ADD? Мое мнение, что ее нужно выжигать калёным железом в пользу COPY. Докеровцы реально "молодцы", что сделали такой комбайн как ADD — который и ссылки скачает, и архивы распакует, что делает прогноз того, что произойдет весьма нетривиальным. Поэтому мой выбор — вполне ясная директива COPY для копирования файлов внутрь образа.

                    +1

                    На самом деле хочу сказать:
                    Ребята! Вы — молодцы. Похоже, что несмотря на весь мой скепсис — werf взлетит. Как говорится, одна картинка — лучше тысячи слов (отсылаю к скриншоту о сборке Dockerfile из статьи). Но не помешало бы описать больше юзкейсов, если вы действительно заинтересованы в популяризации утилиты и снадбить их видосиками в ASCII формате (забыл как этот чудесный сайт называется, который позволяет такие штуки)...


                    Интересно вообще как пойдет развитие технологий, но пока видится, что чтобы собрать какой-либо софт — нужно тащить целый стек разных штуковин. А хочется что-то типа самораспаковывающейся коробки — установить кубер, ввести команду "поехали!" и чтобы он сам из исходников все скомпилировал и внутрь себя задеплоил (влажные мечты опса). Работа в этом направлении, насколько мне известно, ведется, но как-то не особо видно результаты. Это отсылка к Argo-CI и Tekton.

                      0

                      Это https://asciinema.org/ скорее всего, делали ролики когда-то для статей про dapp. Жалко, что хабр не умеет делать для них embed, можно только ссылку с картинкой.

                        0

                        ну, так давайте убедим редакцию включить возможность включения embed asciinema )

                          0
                          Отправил «Гениальную идею» через форму обратной связи с редакцией Хабра. Почему-то был уверен, что уже просил их об этом когда-то давно, но не нашёл в своей почте тому подтверждений.
                            +1
                            Пообщались с поддержкой хабра:

                            Для встраивания стороннего контента мы используем API сервиса iframely.com
                            К сожалению, нам не известно планирует ли этот сервис добавить поддержку указанного вами ресурса.

                            Спасибо за ответ! Если вставка должна выглядеть просто так: <oembed>https://asciinema.org/a/168763</oembed> — то это не работает, т.к. браузер показывает URL без каких-либо картинок. Однако проверка вставки URL'а через сам iframely показывает другой результат. На чьей стороне проблема?

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

                      А можно использовать werf только для сборки и хранения образов? Например, мой проект пока не требует k8s, хватает докер-композа. Но удобный ci/cd и чистый registry хочется).

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое