Jenkins Pipeline DSL: Jenkinsfile в репозитории и история сборок в git
Jenkins 2.0 анонсировал Pipeline-as-Code с Jenkinsfile. Переводим freestyle-jobs на Pipeline DSL: сборка живёт в репозитории вместе с кодом.
Jenkins 2.0 анонсирован с Pipeline-as-Code (Jenkinsfile) и переработанным UI в начале 2016 года
Jenkins 2.0 ещё не вышел финальным релизом, но анонс Pipeline-as-Code в начале года дал повод наконец разобраться с тем, что давно откладывалось. Мы взяли несколько freestyle-jobs с проектов на сопровождении и попробовали переписать их на Pipeline DSL с Jenkinsfile в репозитории. Делимся тем, что получилось.
Проблема freestyle-jobs, которую все знают
Freestyle-job в Jenkins - это конфигурация в XML на сервере. Она нигде не версионируется, если не подключать специальные плагины. История изменений сборочного процесса существует только в головах тех, кто кликал по интерфейсу.
Типичная ситуация: кто-то три месяца назад добавил шаг с npm install --production, потом уволился, и теперь непонятно - это намеренно или баг. В git такого вопроса не возникло бы: есть коммит, есть автор, есть diff.
Второй момент - сложные пайплайны в freestyle не живут. Параллельные шаги, условные ветки, обработка ошибок - всё это делается через цепочку job-ов со своей конфигурацией и без общего контекста. Поддерживать это мучительно.
Что такое Jenkinsfile
Jenkinsfile - это файл в корне репозитория, который описывает весь пайплайн сборки на Pipeline DSL (Groovy-based). Jenkins читает его из репозитория и выполняет. Конфигурация сборки живёт в git наравне с кодом: те же ветки, те же пулл-реквесты, те же ревью.
Структура типового Jenkinsfile для веб-проекта:
node {
stage('Checkout') {
checkout scm
}
stage('Build') {
sh 'npm install'
sh 'npm run build'
}
stage('Test') {
sh 'npm test'
}
stage('Docker image') {
def image = docker.build("myapp:${env.BUILD_NUMBER}")
docker.withRegistry('https://registry.example.com', 'registry-creds') {
image.push()
image.push('latest')
}
}
stage('Deploy staging') {
if (env.BRANCH_NAME == 'master') {
sh 'ansible-playbook deploy.yml -i staging'
}
}
}
Это весь пайплайн - от получения кода до деплоя на стейджинг. В одном файле, в репозитории, с версионированием.
Что изменилось на практике
Первое - сборка ломается там, где надо. В freestyle-схеме с цепочкой job-ов сложно понять, на каком конкретно шаге упало и какой был контекст. Stages в Pipeline DSL дают внятную картину прямо в интерфейсе Jenkins: зелёный/красный прямоугольник на каждый этап. Дебажить стало быстрее.
Второе - параллельные шаги без танцев с бубном. Раньше запустить одновременно юнит-тесты и линтер означало делать два отдельных job-а и связывать их через Multijob Plugin или похожее. Теперь:
stage('Verify') {
parallel(
lint: { sh 'npm run lint' },
test: { sh 'npm test' }
)
}
Двадцать строк плагинной конфигурации превращаются в пять строк кода.
Третье - условная логика нормальным кодом. В freestyle условия реализовывались через параметры и пару-тройку плагинов, которые добавляли лишний интерфейс. В Jenkinsfile это просто Groovy: if, switch, тернарный оператор - всё что нужно.
Четвёртое - ревью сборочного процесса. Если разработчик меняет Jenkinsfile в своей ветке, это попадает в пулл-реквест и проходит ревью как любой другой код. Изменение шага деплоя больше нельзя протащить молча через интерфейс Jenkins.
Что не работает как ожидалось
Groovy-sandbox - головная боль. Jenkins выполняет Jenkinsfile в ограниченной sandbox-среде, и часть стандартных Groovy-возможностей требует ручного одобрения через Script Approval в интерфейсе Jenkins. Разобраться, почему метод недоступен в sandbox, - отдельный квест. На первом проекте мы потратили лишний час только на это.
Документация Pipeline DSL написана в стиле «смотри исходники плагина». Примеры есть, но они покрывают базовые сценарии. Как только начинаются нестандартные случаи - нужно разбираться самостоятельно.
Версионирование помогает только если все пользуются им. Если кто-то из команды всё равно идёт в интерфейс Jenkins и меняет конфигурацию Job напрямую - Jenkinsfile живёт своей жизнью, а Jenkins - своей. Договорённость нужна организационная, не техническая.
Миграция с freestyle
Мы не перепиливали всё разом. Взяли один проект, написали Jenkinsfile с нуля, параллельно держали старый freestyle-job как страховку. Прогнали несколько сборок, убедились что результат совпадает, отключили старый.
На следующий проект ушло вдвое меньше времени - структура Jenkinsfile уже была понятна, и часть кода скопировалась без изменений. Это тоже аргумент: типовые шаги вынесешь в общие функции или отдельные файлы и переиспользуешь.
Текущее состояние
Jenkins 2.0 финальным релизом не вышел - работаем с release candidate и плагинами с beta-статусом. Кое-что шероховато. Но концепция рабочая, и возвращаться к freestyle на новых проектах не хочется.
Проблема «история сборок нигде» решена: теперь git log на Jenkinsfile показывает, кто и когда менял пайплайн. Это само по себе оправдывает переход.