ADG Оставить заявку
Блог DevOps 4 мин чтения

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 показывает, кто и когда менял пайплайн. Это само по себе оправдывает переход.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.