В проекте, для которого создавалось решение — да, достаточно коммита в master, а из него cherry-pick в feature-ветку.
Если проекты не совсем однотипны, есть отклонения, то можно в переменных проекта указывать особенности, а в distribute.sh получать переменные через API: https://docs.gitlab.com/ce/api/project_level_variables.html
Например, где-то нужно в master сделать коммит, а где-то в develop.
Это уже вопрос требований в конкретной задаче. Дело в том, что по коммиту с [CI SKIP] pipeline создаётся без задач. Поэтому, чтобы запустить сборку с новой конфигурацией, потребуется, например, добавить новый коммит или подмёржить master в ветку release — повторюсь, всё зависит от требований и от принятого на проекте процесса внесения изменений.
P.S. Навесить тэг на прилетевший коммит с [ci skip] — не вариант, pipeline тоже будет пустой.
Есть сотня проектов, у которых один и тот же .gitlab-ci.yml, Dappfile и папка с helm chart-ом. Задача — обновлять эти файлы, желательно из одного репозитория.
На практике это может быть что угодно, лишь бы этого много и не хотелось бегать по репозиториям, обновлять файлы. Например, статические сайты на jekyll, собираемые в docker-образ (сайты правят отдельные команды, за сборку отвечает одна команда). Внутренние java-библиотеки, собираемые в jar-артефакты, да вообще любые компоненты, которые не требуют индивидуальных правил сборки (тоже самое — компоненты правят разные люди, сборкой занимается одна команда).
Пожалуй не соглашусь с вами про 400mb. Образ ubuntu:16.04 это 119Mb по показаниям docker images. Плюс есть облегчённые сборки ещё меньшего размера. Вот тут отличная картинка есть с размерами https://hub.docker.com/r/blitznote/debootstrap-amd64/
Понятно, что всё равно больше, чем Alpine (3.6 == 3.97Mb), но всё же не в 100 раз.
P.S. 424Mb наблюдаю у вагрант-коробки trusty 14.04, а вот коробка xenial 16.04 уже 275Mb. Похоже, что были проведены какие-то работы по уменьшению образов.
docker run -it -v ./:/build my-js-build-tools /build/build.sh
Да, упрощенно artifact в Dappfile так и делает. Только для артефакта доступно 5 стадий, то есть 5 скриптов. Можно использовать внешние докер-образы, собранные разработчиками языков или фреймоворков (тот же golang:1.8), дополнив их своими сценариями (добавить пакетов, создать нужных директорий и т.п.) на стадиях before_install, install, которые после первой сборки надолго закэшируются. В случае же my-js-build-tools — получается, что у нас есть где-то отдельный образ с инструментами, сценарий сборки этого образа надо поддерживать, собирать отдельными заданиями, версионировать, при этом как-то указывать нужную версию для сборки конкретного коммита приложения (тут у вас кстати некое противоречие с вашим последним абзацем). Но инструменты они тоже «живые». Иногда оправдано держать сборку образа с инструментами отдельно, в большинстве же случаев сценарий сборки образа с инструментами в Dappfile получается выгоднее, т.к. всегда видно, чем будет собираться конкретный релиз приложения.
Получается, основное преимущество dapp — он умеет сохранять и восстанавливать предыдущее состояние сборки, чтобы делать инкрементальный билд вместо полного?
В каком-то смысле да, можно считать так, dapp умеет что-то похожее на инкрементальный билд в зависимости от изменений файлов в git. Но я бы не сказал, что это прям основное преимущество. Одно из — это да.
Прямая сборка приложения из нескольких репозиториев, имхо, странная идея, т.к. усложняет версионность: чтобы воспроизвести сборку, нужно знать нужный коммит в каждом репозитории, участвующим в сборке.
Разработчики go так не считают (и вот кто их только надоумил). В Dappfile можно указать коммит или ветку или тэг во внешнем репозитории, который нужно склонировать перед сборкой. Если ветка наросла, то запустится пересборка. В целом это не сложнее, чем указывать версию внешних библиотек в pom.xml или Composer.json.
Я правда не хотел, но всё-таки получилось много буковок. Этот вопрос вообще достоин своей отдельной статьи.
А можно как-то объяснить, зачем всё это нужно вообще?
Если философски без конкретики, то dapp создавался, чтобы решить проблемы сборки, которые возникают в CI/CD системе с несколькими билд машинами и большим количеством приложений, когда поддерживать развесистые скрипты сборки в каждом репозитории становится совсем накладно и когда одно приложение это не один репозиторий.
Ну ок, решили собирать приложения не на хосте дженкинсом, а в контейнере.
Делаем отдельный образ с build environment, содержащий утилиты сборки без кода приложения.
На хосте тем же дженкинсом делаем чекаут и запускам образ сборки, примонтировав ему зачекаутенные исходники и папки с кешами (~/.m2 для мавена, node_modules для npm и т.п.)
Образ с приложением делаем банальным docker build в папке с артефактами, собранными системой сборки.
Вижу первый вопрос про то, зачем нужна multi-stage сборка, если можно все инструменты поставить на билд машину. Тут ответ простой — инструменты в докер-образе обладают всеми плюсами приложений в докер-образе. Так проще обновлять, так проще иметь несколько версий, так проще обеспечить повторяемость сборки, если агентов сборки несколько на разных машинах и так далее.
Второй вопрос про дженкинс. В целом ваш сценарий может быть организован в любой CI-системе как некий начальный, быстрый вариант. В перспективе всплывают такие минусы: сборка приложения будет происходить всегда, даже когда без этого можно обойтись. Взять например, java-приложение, которое надо собрать maven-ом и собрать статику webpack-ом. C dapp это можно разделить на две секции artifact и объявить, что сборка приложения зависит от изменений в /src/main/java/, а статику собирать только при изменениях в /src/main/webapp/.
Другой минус — docker build и все его недостатки, точнее осознанно введённые ограничения при разработке Dockerfile и docker build, которые в случае CI/CD являются недостатками: нельзя пушить слои кэширования во внешний регистри, нет поддержки модульности сборки (общие скрипты), нет зависимостей выполнения команд от изменений файлов в git (есть зависимость на копирование) и прочее.
И это мы пока говорим про сборку из одного репозитория. А если перед сборкой итогового образа нужно пособирать части приложения из других репозиториев? dapp умеет в зависимости стадий сборки от изменений во внешних репозиториях.
Хотим, чтобы npm install/composer install вызывался только при изменении соответствующих файлов, а не всегда? пишем простой Makefile, gnu make создавался специально для этой цели.
Ничего не имею против gnu make — проверенный временем инструмент. Однако агент на билд машине в общем случае не обязан хранить дерево репозитория и файлы от предыдущей сборки, чтобы gnu make смог бы правильно обнаружить изменения файлов после git pull. dapp вычисляет изменившиеся файлы по git истории, что в системе CI/CD надёжнее, чем проверять mtime файлов. Т.е. если агент имеет доступ к предыдущей сборке, то gnu make может и хватить.
Укажу ещё несколько моментов, почему применение dapp и описание сборки в Dappfile интереснее, чем скрипт сборки в дженкинс.
Первое — Dappfile можно разрабатывать и отлаживать на локальной машине, последующая сборка на билд-машине будет предсказуемой.
Второе — сборка разных версий приложения может отличаться и вариант со своим хитрым скриптом в дженкинс сложно поддерживать (а это нужно поддерживать, т.к. новая версия, которая требует новый скрипт сборки, на прод ещё не выкатилась, а хотфикс нужно прямо сейчас собрать старым скриптом).
Третье — стадии сборки и их кэш в виде нормальных образов позволяют попасть в проблемную стадию и посмотреть, что там пошло не так (--introspect* флаги http://flant.github.io/dapp/debug_for_advanced_build.html)
Тема общих частей была упомянута в обзорном докладе про dapp https://habrahabr.ru/company/flant/blog/324274/. В данный момент dapp поддерживает два способа выполнения команд на пользовательских стадиях: shell (как в этой статье) и chef-рецепты. Chef-рецепты живут в кукбуке приложения и в dimod-ах — кукбуках-модулях, которые выносятся в отдельные репозитории.
Также можно использовать функцию проброса порта в современных SSHD/SSH, но эта методика не рекомендуется, так как еще довольно много устаревших SSHD, которые не поддерживают эту функцию.
Непонятно. Можно подробнее про неподдерживаемый проброс порта?
Если у них в правилах голосования указано, что не больше одного голоса с ip адреса в сутки, то да, ваши действия противоречат правилам и голоса могут обнулить. Если правил нет или в них не указано какие голоса принимаются, то понятно, что организаторам честное голосование не нужно и скорее всего сами будут накручивать.
Если сливать именно после коммита (git commit), то достаточно git хуков в локальном репозитории. Если же после push или после принятия MR, то хуки в gitlab. Ещё есть кнопка merge when build succeeds — но это наверно другая история.
По вашей ссылке сказано, что проблема из-за версий glibc и скорее всего в официальном gradle image старая версия glibc, либо проблема из-за использования alpine с его musl-libc — мы тоже на эти грабли наступили, правда в другом случае https://github.com/flant/dapp/issues/164.
Я так понял, вам было бы интересно узнать, как не перекачивать лишний раз java-артефакты или вообще чтобы скачивать только те, у которых версия изменилась? Немножко за рамками темы статьи на самом деле, но попробую ответить.
Мы в основном не используем возможности gitlab-а по сборке через docker, а используем свою утилиту dapp. В ней реализована поддержка сборки отдельных образов и копирование файлов из этих образов в финальный. На поверхности это похоже на multi stages в последних версиях docker, но есть и существенные отличия, как минимум dapp знает про git и dapp собирает образы по стадиям, что даёт возможность пересобирать только те стадии, для которых изменились указанные файлы.
Для примера в вашем случае в Dappfile будет описан отдельный образ в котором живёт gradle (можно указать, что он на основе официального gradle), куда при сборке подключится .gradle и где выполнится gradle build по исходникам.
После из этого образа скопируются указанные war, jar, ear или вся папка target в указанное место в финальном образе.
Т.к. dapp знает про git, то можно на одной стадии попросить gradle скачать свежие версии зависимостей, если изменился build.gradle (времязатратная стадия, но изменения редки, поэтому будет пересобираться не часто), а на следующей стадии gradle будет собирать проект, если изменились исходники в src.
Костылик с BUILD_ENV тоже использовали, но начиная с 8.15 появились CI_ENVIRONMENT_NAME и CI_ENVIRONMENT_SLUG.
Ну и хотел бы уточнить, как у вас происходит сборка сборочного образа? То есть в репозитории есть Dockerfile с описанием образа сборки и Dockerfile с описанием образа приложения. В какой момент и как собирается сборочный образ? Это отдельная задача в пайплайне? Как вы ставите тэг, если вдруг сборочный образ обновился? В общем интересен этот процесс.
Похоже, что нужно аккуратно ставить тэги или руками запускать сборку сборочного образа или даже менять .gitlab-ci.yml, когда изменится тэг сборочного образа. В силу объёмности нашего проекта, мы используем свою утилиту dapp для сборки и описание в двух Dockerfile не требуется, за обновлениям сборочного образа следит сам dapp. Но теперь в самом docker из коробки есть multi stages и похоже этот способ (с двумя Dockerfile) перестаёт быть актуальным. Что думаете?
Согласен, про REST API невнятно вышло. Зачем вообще он упомянут? Вот есть такой тикет https://gitlab.com/gitlab-org/gitlab-ce/issues/21911 — он про права пользователей на запуск задач, на запуск задач в определённых environment, на доступ к секретным переменным, на доступ к раннерам. Когда это будет в gitlab, то права пользователям будем выдавать в интерфейса gitlab-а и не надо будет никаких "самописных с боку систем с REST API".
Переменная GITLAB_USER_EMAIL это email того, кто запускает задачу в пайплайне, а не email последнего коммитера. Ваш вариант пригодился бы, как решение проблемы с правкой .gitlab-ci.yml, однако email последнего коммитера в общем случае не обязан совпадать с email того, кто пушит.
В достаточно большом проекте эти трёхэтажные Yaml-ы делегированы как раз devops-инженерам. А в небольшой команде обязательно кто-то будет заниматься непрофильной, но тем не менее важной деятельностью. Если разработчики начали заниматься деплоем и это отнимает их время, то почему бы не отдать devops на аутсорс, благо сейчас много кто этим занимается.
В проекте, для которого создавалось решение — да, достаточно коммита в master, а из него cherry-pick в feature-ветку.
Если проекты не совсем однотипны, есть отклонения, то можно в переменных проекта указывать особенности, а в distribute.sh получать переменные через API: https://docs.gitlab.com/ce/api/project_level_variables.html
Например, где-то нужно в master сделать коммит, а где-то в develop.
Это уже вопрос требований в конкретной задаче. Дело в том, что по коммиту с [CI SKIP] pipeline создаётся без задач. Поэтому, чтобы запустить сборку с новой конфигурацией, потребуется, например, добавить новый коммит или подмёржить master в ветку release — повторюсь, всё зависит от требований и от принятого на проекте процесса внесения изменений.
P.S. Навесить тэг на прилетевший коммит с [ci skip] — не вариант, pipeline тоже будет пустой.
Есть сотня проектов, у которых один и тот же .gitlab-ci.yml, Dappfile и папка с helm chart-ом. Задача — обновлять эти файлы, желательно из одного репозитория.
На практике это может быть что угодно, лишь бы этого много и не хотелось бегать по репозиториям, обновлять файлы. Например, статические сайты на jekyll, собираемые в docker-образ (сайты правят отдельные команды, за сборку отвечает одна команда). Внутренние java-библиотеки, собираемые в jar-артефакты, да вообще любые компоненты, которые не требуют индивидуальных правил сборки (тоже самое — компоненты правят разные люди, сборкой занимается одна команда).
Не, на графиках не видно. Centos c ~250Mb до ~750Mb
Думаю что большой вклад в увеличение образа вносят apt-get update/yum update и последующее не удаление кэша.
Вот как раз сегодня нашёл картиночку про варианты базовых образов ubuntu в этом репозитории: https://hub.docker.com/r/blitznote/debootstrap-amd64/
Пожалуй не соглашусь с вами про 400mb. Образ ubuntu:16.04 это 119Mb по показаниям docker images. Плюс есть облегчённые сборки ещё меньшего размера. Вот тут отличная картинка есть с размерами https://hub.docker.com/r/blitznote/debootstrap-amd64/
Понятно, что всё равно больше, чем Alpine (3.6 == 3.97Mb), но всё же не в 100 раз.
P.S. 424Mb наблюдаю у вагрант-коробки trusty 14.04, а вот коробка xenial 16.04 уже 275Mb. Похоже, что были проведены какие-то работы по уменьшению образов.
Если вам интересна концептуальная сторона этого дела (зачем мы всё это городим, почему тратим свои силы и время) в докладе distol всё по полочкам разложено: видео https://www.youtube.com/watch?v=8R5UDg29Vic, текст https://habrahabr.ru/company/flant/blog/324274/
Да, упрощенно artifact в Dappfile так и делает. Только для артефакта доступно 5 стадий, то есть 5 скриптов. Можно использовать внешние докер-образы, собранные разработчиками языков или фреймоворков (тот же golang:1.8), дополнив их своими сценариями (добавить пакетов, создать нужных директорий и т.п.) на стадиях before_install, install, которые после первой сборки надолго закэшируются. В случае же my-js-build-tools — получается, что у нас есть где-то отдельный образ с инструментами, сценарий сборки этого образа надо поддерживать, собирать отдельными заданиями, версионировать, при этом как-то указывать нужную версию для сборки конкретного коммита приложения (тут у вас кстати некое противоречие с вашим последним абзацем). Но инструменты они тоже «живые». Иногда оправдано держать сборку образа с инструментами отдельно, в большинстве же случаев сценарий сборки образа с инструментами в Dappfile получается выгоднее, т.к. всегда видно, чем будет собираться конкретный релиз приложения.
В каком-то смысле да, можно считать так, dapp умеет что-то похожее на инкрементальный билд в зависимости от изменений файлов в git. Но я бы не сказал, что это прям основное преимущество. Одно из — это да.
Разработчики go так не считают (и вот кто их только надоумил). В Dappfile можно указать коммит или ветку или тэг во внешнем репозитории, который нужно склонировать перед сборкой. Если ветка наросла, то запустится пересборка. В целом это не сложнее, чем указывать версию внешних библиотек в pom.xml или Composer.json.Я правда не хотел, но всё-таки получилось много буковок. Этот вопрос вообще достоин своей отдельной статьи.
Если философски без конкретики, то dapp создавался, чтобы решить проблемы сборки, которые возникают в CI/CD системе с несколькими билд машинами и большим количеством приложений, когда поддерживать развесистые скрипты сборки в каждом репозитории становится совсем накладно и когда одно приложение это не один репозиторий.
Вижу первый вопрос про то, зачем нужна multi-stage сборка, если можно все инструменты поставить на билд машину. Тут ответ простой — инструменты в докер-образе обладают всеми плюсами приложений в докер-образе. Так проще обновлять, так проще иметь несколько версий, так проще обеспечить повторяемость сборки, если агентов сборки несколько на разных машинах и так далее.
Второй вопрос про дженкинс. В целом ваш сценарий может быть организован в любой CI-системе как некий начальный, быстрый вариант. В перспективе всплывают такие минусы: сборка приложения будет происходить всегда, даже когда без этого можно обойтись. Взять например, java-приложение, которое надо собрать maven-ом и собрать статику webpack-ом. C dapp это можно разделить на две секции artifact и объявить, что сборка приложения зависит от изменений в /src/main/java/, а статику собирать только при изменениях в /src/main/webapp/.
Другой минус — docker build и все его недостатки, точнее осознанно введённые ограничения при разработке Dockerfile и docker build, которые в случае CI/CD являются недостатками: нельзя пушить слои кэширования во внешний регистри, нет поддержки модульности сборки (общие скрипты), нет зависимостей выполнения команд от изменений файлов в git (есть зависимость на копирование) и прочее.
И это мы пока говорим про сборку из одного репозитория. А если перед сборкой итогового образа нужно пособирать части приложения из других репозиториев? dapp умеет в зависимости стадий сборки от изменений во внешних репозиториях.
Ничего не имею против gnu make — проверенный временем инструмент. Однако агент на билд машине в общем случае не обязан хранить дерево репозитория и файлы от предыдущей сборки, чтобы gnu make смог бы правильно обнаружить изменения файлов после git pull. dapp вычисляет изменившиеся файлы по git истории, что в системе CI/CD надёжнее, чем проверять mtime файлов. Т.е. если агент имеет доступ к предыдущей сборке, то gnu make может и хватить.
Укажу ещё несколько моментов, почему применение dapp и описание сборки в Dappfile интереснее, чем скрипт сборки в дженкинс.
Первое — Dappfile можно разрабатывать и отлаживать на локальной машине, последующая сборка на билд-машине будет предсказуемой.
Второе — сборка разных версий приложения может отличаться и вариант со своим хитрым скриптом в дженкинс сложно поддерживать (а это нужно поддерживать, т.к. новая версия, которая требует новый скрипт сборки, на прод ещё не выкатилась, а хотфикс нужно прямо сейчас собрать старым скриптом).
Третье — стадии сборки и их кэш в виде нормальных образов позволяют попасть в проблемную стадию и посмотреть, что там пошло не так (--introspect* флаги http://flant.github.io/dapp/debug_for_advanced_build.html)
Промазал
YAML для описания и Ansible для команд сборки — две самые часто спрашиваемые опции ;) Хотим, планируем, т.к. сами решили уходить от chef.
Тема общих частей была упомянута в обзорном докладе про dapp https://habrahabr.ru/company/flant/blog/324274/. В данный момент dapp поддерживает два способа выполнения команд на пользовательских стадиях: shell (как в этой статье) и chef-рецепты. Chef-рецепты живут в кукбуке приложения и в dimod-ах — кукбуках-модулях, которые выносятся в отдельные репозитории.
Про поддержку chef можно посмотреть в документации
http://flant.github.io/dapp/chef_for_advanced_build.html
http://flant.github.io/dapp/chef_dimod_for_advanced_build.html
chef-server для сборки конечно же не нужен, используется chef-solo, который выполняет рецепты для каждой указанной стадии.
На скорости 1.5х нормально было смотреть.
Непонятно. Можно подробнее про неподдерживаемый проброс порта?
Если сливать именно после коммита (git commit), то достаточно git хуков в локальном репозитории. Если же после push или после принятия MR, то хуки в gitlab. Ещё есть кнопка merge when build succeeds — но это наверно другая история.
Если используется директива image: то команды будут запускаться через указанный там образ. И вот в этом образе может не быть команды git.
По вашей ссылке сказано, что проблема из-за версий glibc и скорее всего в официальном gradle image старая версия glibc, либо проблема из-за использования alpine с его musl-libc — мы тоже на эти грабли наступили, правда в другом случае https://github.com/flant/dapp/issues/164.
Я так понял, вам было бы интересно узнать, как не перекачивать лишний раз java-артефакты или вообще чтобы скачивать только те, у которых версия изменилась? Немножко за рамками темы статьи на самом деле, но попробую ответить.
Мы в основном не используем возможности gitlab-а по сборке через docker, а используем свою утилиту dapp. В ней реализована поддержка сборки отдельных образов и копирование файлов из этих образов в финальный. На поверхности это похоже на multi stages в последних версиях docker, но есть и существенные отличия, как минимум dapp знает про git и dapp собирает образы по стадиям, что даёт возможность пересобирать только те стадии, для которых изменились указанные файлы.
Для примера в вашем случае в Dappfile будет описан отдельный образ в котором живёт gradle (можно указать, что он на основе официального gradle), куда при сборке подключится .gradle и где выполнится gradle build по исходникам.
После из этого образа скопируются указанные war, jar, ear или вся папка target в указанное место в финальном образе.
Т.к. dapp знает про git, то можно на одной стадии попросить gradle скачать свежие версии зависимостей, если изменился build.gradle (времязатратная стадия, но изменения редки, поэтому будет пересобираться не часто), а на следующей стадии gradle будет собирать проект, если изменились исходники в src.
Костылик с
BUILD_ENVтоже использовали, но начиная с 8.15 появилисьCI_ENVIRONMENT_NAMEиCI_ENVIRONMENT_SLUG.Ну и хотел бы уточнить, как у вас происходит сборка сборочного образа? То есть в репозитории есть Dockerfile с описанием образа сборки и Dockerfile с описанием образа приложения. В какой момент и как собирается сборочный образ? Это отдельная задача в пайплайне? Как вы ставите тэг, если вдруг сборочный образ обновился? В общем интересен этот процесс.
Похоже, что нужно аккуратно ставить тэги или руками запускать сборку сборочного образа или даже менять .gitlab-ci.yml, когда изменится тэг сборочного образа. В силу объёмности нашего проекта, мы используем свою утилиту dapp для сборки и описание в двух Dockerfile не требуется, за обновлениям сборочного образа следит сам dapp. Но теперь в самом docker из коробки есть multi stages и похоже этот способ (с двумя Dockerfile) перестаёт быть актуальным. Что думаете?
Согласен, про REST API невнятно вышло. Зачем вообще он упомянут? Вот есть такой тикет https://gitlab.com/gitlab-org/gitlab-ce/issues/21911 — он про права пользователей на запуск задач, на запуск задач в определённых environment, на доступ к секретным переменным, на доступ к раннерам. Когда это будет в gitlab, то права пользователям будем выдавать в интерфейса gitlab-а и не надо будет никаких "самописных с боку систем с REST API".
Переменная GITLAB_USER_EMAIL это email того, кто запускает задачу в пайплайне, а не email последнего коммитера. Ваш вариант пригодился бы, как решение проблемы с правкой .gitlab-ci.yml, однако email последнего коммитера в общем случае не обязан совпадать с email того, кто пушит.
В достаточно большом проекте эти трёхэтажные Yaml-ы делегированы как раз devops-инженерам. А в небольшой команде обязательно кто-то будет заниматься непрофильной, но тем не менее важной деятельностью. Если разработчики начали заниматься деплоем и это отнимает их время, то почему бы не отдать devops на аутсорс, благо сейчас много кто этим занимается.