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

Gradle + Jenkins Pipeline: переносим freestyle-джобы в Jenkinsfile и перестаём бояться CI-конфига

Jenkins 2.0 принёс Jenkinsfile и Pipeline DSL - CI-конфиг теперь живёт рядом с кодом в Git и проходит ревью вместе с ним. Переносим Gradle-сборки с freestyle.

Контекст момента

Jenkins 2.0 вышел в апреле 2016 года с Jenkinsfile и Pipeline DSL, превратив CI-конфигурацию в код, хранимый в репозитории

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

Что было до

Стандартная картина для проектов, с которыми мы работаем в рамках managed-инфраструктуры: несколько freestyle-джобов в Jenkins 1.x. Один джоб собирает, другой тестирует, третий деплоит. Конфигурация живёт в Jenkins на сервере в XML-файлах где-то в /var/lib/jenkins/jobs/. Бекапится ли? Формально да. Воспроизводима ли при потере сервера? Примерно на 70%, остальное придётся восстанавливать по памяти.

Самая раздражающая часть - это когда нужно изменить шаги сборки. Заходишь в веб-интерфейс, тыкаешь в поля, сохраняешь. Кто изменил, что изменил, зачем - история в Git не скажет, потому что конфиг там не лежит. Jenkins хранит свою историю изменений, но это не то же самое.

Jenkinsfile - что это и зачем

В 2.0 появился Jenkinsfile: текстовый файл в корне репозитория, который описывает пайплайн в виде кода на Groovy-диалекте Pipeline DSL. Jenkins подхватывает его автоматически, если создать Multibranch Pipeline или Pipeline-джоб, указывающий на репозиторий.

Результат такой: разработчик создаёт pull request, туда же идёт изменение Jenkinsfile если нужно поправить шаги сборки. Всё ревьюируется вместе. CI-конфиг проходит тот же путь что и код - это меняет отношение к нему.

Что мы переносили

Проект на Java, сборка через Gradle, три среды: dev, staging, prod. До обновления было четыре freestyle-джоба: сборка, unit-тесты, интеграционные тесты, деплой на staging. Деплой на prod - отдельный джоб с ручным триггером.

Структура Jenkinsfile получилась такая:

pipeline {
    agent any

    tools {
        jdk 'JDK8'
        gradle 'Gradle3'
    }

    stages {
        stage('Build') {
            steps {
                sh './gradlew clean assemble'
            }
        }
        stage('Unit Tests') {
            steps {
                sh './gradlew test'
            }
            post {
                always {
                    junit 'build/test-results/test/*.xml'
                }
            }
        }
        stage('Integration Tests') {
            steps {
                sh './gradlew integrationTest'
            }
        }
        stage('Deploy Staging') {
            when {
                branch 'develop'
            }
            steps {
                sh './gradlew deployStaging'
            }
        }
    }

    post {
        failure {
            mail to: 'team@example.com',
                 subject: "FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                 body: "Build failed. Check ${env.BUILD_URL}"
        }
    }
}

Декларативный синтаксис (pipeline { }) появился чуть позже чем скриптовый - на август 2016 он в статусе beta, скриптовый стабилен. Выше показан декларативный: мы его используем, потому что он читается как конфиг, а не как Groovy-скрипт, но нужно учитывать что API ещё может измениться.

Где потратили время

Первое - инструменты. В freestyle-джобе инструменты (JDK, Gradle) выбираются галочками в UI. В Jenkinsfile нужно прописать имена через директиву tools, и эти имена должны совпадать с тем что прописано в Global Tool Configuration. Пять минут на разбор, но без документации с ходу не очевидно.

Второе - when и ветки. Условие when { branch 'develop' } работает только в Multibranch Pipeline - там Jenkins знает о ветках. В простом Pipeline-джобе эта конструкция не работает как ожидается. Мы несколько минут потеряли пока разобрались почему деплой не срабатывает.

Третье - credentials. Freestyle-джоб умеет инжектировать credentials через UI в переменные окружения. В Jenkinsfile это делается через withCredentials([...]) - блок вокруг шагов где нужны чувствительные данные. Синтаксис немного другой, но концептуально лучше: сразу видно в коде что конкретный шаг работает с секретами.

Четвёртое - нотификации. Старый Email Extension plugin с кастомными шаблонами в freestyle и блок post { failure { } } в Pipeline - это разные вещи. Шаблоны пришлось адаптировать.

Что реально изменилось

Разработчики стали смотреть в Jenkinsfile. Раньше CI-конфиг был «где-то в Jenkins, идите к devops-ам». Теперь файл лежит в репозитории, и при очередном «а почему сборка делает вот это» ответ очевиден - смотри Jenkinsfile в корне. Пара вопросов отпала сама собой.

Восстановление джоба с нуля: если потерять Jenkins-сервер, новый подхватит пайплайн из репозитория. Конфиг среды (плагины, tool configuration) - отдельная история, там всё сложнее. Но сами шаги пайплайна - сохранены в Git.

Ревью изменений в CI: пока что это работает в обе стороны. Pull request с Jenkinsfile виден всей команде. Хорошо это или создаёт лишний шум - зависит от культуры в команде.

Ограничения, с которыми столкнулись

Отладка Jenkinsfile - не то чтобы приятная вещь. Нет способа быстро проверить синтаксис локально без прогона на Jenkins. Есть Replay (переиграть последний билд с изменённым скриптом прямо из UI) - это спасает, но всё равно медленнее чем хотелось бы.

Groovy как язык для CI-скриптов - специфичный выбор. Для декларативного синтаксиса этого почти не чувствуется, но как только нужна нетривиальная логика - начинается Groovy, и не всем это комфортно.

Shared Libraries (переиспользуемые куски пайплайна между проектами) - есть в 2.0, но мы до них не дошли. Когда Jenkinsfile-ов будет несколько - это следующий шаг.

Где сейчас

Проект работает на Jenkins 2.0 с Jenkinsfile три недели. Откатываться на freestyle никто не предлагает. Остальные проекты пока на 1.x - планируем обновлять по очереди, без спешки. Jenkins 2.0 обратно совместим с существующими джобами, так что можно переносить Jenkinsfile постепенно, не трогая то что работает.

Контакт

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

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