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

Jenkins 2.0 вышел: переходим на Pipeline-as-Code и Jenkinsfile в каждом репозитории

Jenkins 2.0 официально вышел 20 апреля 2016. Переводим все проекты на Pipeline DSL с Jenkinsfile и shared libraries. Время настройки нового CI - с двух часов до пятнадцати минут.

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

Jenkins 2.0 официально выпущен 20 апреля 2016 года с Pipeline-as-Code как центральной функцией

20 апреля вышел Jenkins 2.0 финальным релизом. Мы ждали его с февраля, когда уже экспериментировали с Pipeline DSL на release candidate. Официальный выход - хороший повод зафиксировать, что изменилось в нашем подходе к CI за последние три месяца.

Если коротко: мы перестали настраивать CI через интерфейс Jenkins. Вообще.

Откуда взялась задача

На сопровождении у нас полтора десятка проектов разного возраста. У каждого - Jenkins-сборка, настроенная когда-то руками через UI. Часть freestyle-job-ов уходит корнями в 2013 год. Кто именно добавлял шаги, почему выбраны конкретные параметры, почему деплой идёт так, а не иначе - история не сохранилась нигде.

Запуск нового проекта означал: открыть похожий job, начать кликать, что-то скопировать, что-то забыть, получить кривую сборку, доделать. Два часа - если всё шло хорошо и ничего не забыли. Иногда больше.

В феврале мы написали о первых опытах с Jenkins Pipeline DSL. Тогда всё работало на RC с оговорками. Теперь - финальный релиз, и мы завершили миграцию основного пула проектов.

Jenkinsfile как единственный источник правды

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

Типовой Jenkinsfile для Java-проекта у нас выглядит примерно так:

@Library('adg-shared') _

node {
    stage('Checkout') {
        checkout scm
    }

    stage('Build') {
        sh 'mvn clean package -DskipTests'
    }

    stage('Test') {
        sh 'mvn test'
        junit 'target/surefire-reports/*.xml'
    }

    stage('Docker') {
        def tag = env.BRANCH_NAME == 'master' ? 'latest' : env.BRANCH_NAME
        def image = docker.build("${env.PROJECT_NAME}:${tag}")
        docker.withRegistry('https://registry.example.com', 'registry-creds') {
            image.push()
        }
    }

    stage('Deploy') {
        if (env.BRANCH_NAME == 'master') {
            adgDeploy(env: 'staging', playbook: 'deploy.yml')
        }
    }
}

@Library('adg-shared') _ - это подключение нашей shared library. adgDeploy - один из шагов, определённых в ней. О библиотеке отдельно.

Shared library: общие шаги один раз

Это ключевой момент, которого не было в феврале. Jenkins Pipeline позволяет вынести переиспользуемые шаги в отдельный git-репозиторий и подключать его как библиотеку. Мы сделали adg-shared с несколькими десятками функций: деплой через Ansible, публикация образов в registry, уведомления в Slack, сканирование зависимостей.

Структура библиотеки:

adg-shared/
  vars/
    adgDeploy.groovy      # шаг деплоя через Ansible
    adgNotify.groovy      # уведомления Slack/почта
    adgDockerPush.groovy  # публикация образа с тегами
    adgSonar.groovy       # запуск SonarQube
  src/
    ru/adg/pipeline/      # вспомогательные классы

Каждый vars/*.groovy - это один вызываемый шаг. adgDeploy внутри знает, как звать Ansible, какие переменные передавать, как интерпретировать коды возврата. Jenkinsfile в конкретном проекте не знает этих деталей.

Эффект от shared library: когда мы меняем логику деплоя (например, добавили health-check после Ansible-прогона), это изменение автоматически распространяется на все проекты при следующем запуске. Раньше такое обновление означало пройти по всем job-ам руками.

Что дал Jenkinsfile в репозитории

История изменений. git log Jenkinsfile показывает, кто и когда менял пайплайн. Больше не нужно гадать, почему в сборке появился шаг с npm run legacy-build - есть коммит, есть автор, есть сообщение.

Ревью изменений CI как кода. Если разработчик меняет Jenkinsfile в ветке, это идёт в пулл-реквест. Команда видит изменение пайплайна до того, как оно попадёт в master. Это остановило уже несколько сомнительных правок.

Новый проект - не полтора часа настройки. Создаёшь Jenkins Job типа Pipeline, указываешь репозиторий. Jenkins сам читает Jenkinsfile и строит пайплайн. Всё. При типовом проекте время настройки CI - пятнадцать минут, из которых десять уходят на базовую конфигурацию Job в интерфейсе и нажатие кнопки.

Ветки работают правильно. Multibranch Pipeline автоматически создаёт job для каждой ветки, которая несёт Jenkinsfile. Feature-ветки собираются сами, по merge в master деплоится staging. Это поведение описано в Jenkinsfile - не в интерфейсе.

Где всё ещё неудобно

Groovy-sandbox - это боль, которая никуда не делась. Часть стандартных операций требует одобрения через Script Approval. Логика не всегда очевидна: два разных метода из одного класса могут быть по-разному доступны в sandbox. Мы завели отдельный документ «что одобрить в Jenkins после установки» - без него новый администратор потратит время впустую.

Отладка Groovy-кода в Jenkinsfile неудобна. Нет нормального способа запустить его локально. Пишешь, коммитишь, ждёшь запуска Job, читаешь лог. Если синтаксическая ошибка - узнаёшь об этом только при выполнении. Команда Jenkins анонсировала Declarative Pipeline как альтернативу (синтаксис pipeline { ... } вместо imperative Groovy) - у него планируется валидатор, но в реальных проектах нам пока хватает возможностей Scripted Pipeline.

Shared library версионируется, но тянуть за собой изменения - отдельная работа. Если мы поменяли интерфейс adgDeploy, нужно обновить Jenkinsfile во всех репозиториях. Пока проектов немного - терпимо, но масштаб виден.

Текущее состояние

На Pipeline перенесено около двух третей проектов. Оставшийся хвост - либо совсем простые сборки, которые трогать незачем, либо нестандартные случаи, которые требуют отдельного разбора.

Freestyle-jobs на новых проектах больше не создаём. Shared library растёт по мере переноса: каждый нестандартный случай, который встречается при миграции, либо ложится в библиотеку как новый шаг, либо фиксируется в Jenkinsfile как явное исключение.

Вернуться к настройке CI через интерфейс Jenkins никому уже не хочется. Сравниваешь: кликать по форме без истории изменений или написать двадцать строк Groovy, которые живут в git - выбор очевидный.

Контакт

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

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