Groovy для Jenkins: основы скриптового конвейера

Рейтинг: 66.7% · 13 голосов
Подробный курс по Jenkins и CI/CD: установка и настройка, плагины и Configuration as Code, конвейеры (pipeline) и Jenkinsfile, declarative и scripted, агенты, Docker и Kubernetes, общие библиотеки на Groovy, безопасность и credentials, интеграции (SonarQube, артефакты), траблшутинг. Актуально на 2026.
Ответить
Аватара пользователя
Andrey_CICD
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Groovy для Jenkins: основы скриптового конвейера

Сообщение Andrey_CICD »

Оглавление курса (47)
  1. Что такое Jenkins и зачем нужен CI/CD
  2. Jenkins в 2026: версии, экосистема и сравнение с GitLab CI и GitHub Actions
  3. Архитектура Jenkins: контроллер, агенты, узлы и исполнители
  4. Установка Jenkins: пакеты, Docker и Kubernetes
  5. Первичная настройка Jenkins: мастер, плагины, пользователи
  6. Плагины Jenkins: установка, обновление и ключевой набор
  7. Configuration as Code (JCasC): настройка Jenkins декларативно
  8. Конвейеры Jenkins и Jenkinsfile: pipeline as code
  9. Scripted против Declarative: два синтаксиса конвейера
  10. Структура конвейера: node, stage, steps и первый pipeline
  11. Генератор сниппетов, запуск конвейера и Replay
  12. Типы заданий Jenkins: Freestyle, Pipeline, Multibranch и Organization
  13. Декларативный конвейер: структура pipeline, agent, stages
  14. agent, environment и переменные в конвейере
  15. tools, options, parameters и triggers в декларативном конвейере
  16. stages, parallel и matrix: организация этапов
  17. Условное выполнение when и блок post
  18. Триггеры сборки: webhook от GitHub, SCM polling и расписание
  19. Параметры сборки и пользовательский ввод
  20. Управление потоком: timeout, retry, sleep и waitUntil
  21. Параллелизм и блокировки: parallel, lock, milestone
  22. Постобработка и уведомления о результате сборки
  23. Groovy для Jenkins: основы скриптового конвейера (вы здесь)
  24. Скриптовый конвейер: логика, циклы и функции
  25. Общие библиотеки Jenkins: Shared Libraries
  26. Подключение и загрузка общих библиотек
  27. Шаги оболочки: sh, bat, powershell и коды возврата
  28. Переменные среды, withEnv и рабочие пространства
  29. Работа с файлами, артефактами и отпечатками
  30. Сборка проектов в конвейере: Maven, Gradle, npm
  31. Качество кода: SonarQube и покрытие тестами
  32. Управление артефактами: публикация в Nexus и Artifactory
  33. Docker в Jenkins: образы как агенты сборки
  34. Сборка и публикация Docker-образов из конвейера
  35. Jenkins в Kubernetes: динамические pod-агенты
  36. Развёртывание в Kubernetes из конвейера
  37. Безопасность Jenkins: аутентификация и матрица прав
  38. Учётные данные Jenkins: credentials в конвейере
  39. Безопасность сценариев: Groovy sandbox и Vault
  40. Уведомления и отчёты: email, Slack, Telegram, HTML
  41. Blue Ocean и интерфейс Jenkins в 2026
  42. Автоматизация Jenkins: CLI, REST API и script console
  43. Поиск и устранение неисправностей: читаем логи конвейера
  44. Типичные ошибки конвейера: сериализация и неутверждённый код
  45. Эксплуатация Jenkins: бэкап, обновление, масштабирование
  46. Конвертация и миграция: от Freestyle к Jenkinsfile
  47. Итог: сквозной CI/CD конвейер и Jenkins против альтернатив в 2026
Ты пишешь declarative pipeline, всё красиво и декларативно, и тут понадобилось разобрать JSON из ответа API, собрать список модулей в цикле или принять решение по результату shell-команды. И declarative вдруг кончается. Ты открываешь блок script, а внутри - незнакомый язык, где переменные через def, строки с ${} и какие-то загадочные ошибки про сериализацию. Это Groovy. И даже если ты целиком сидишь на declarative, минимум Groovy знать придётся: связка jenkins groovy - это не опция, а фундамент, на котором стоит весь Pipeline.

Хорошая новость: чтобы быть продуктивным в конвейерах, тебе не нужен Groovy целиком. Нужен небольшой практичный срез плюс понимание одной важной особенности - как Jenkins исполняет этот код. Этим и займёмся, а в конце выйдем на shared libraries, где Groovy раскрывается полностью.

Зачем Groovy в Jenkins и где он живёт

Весь Jenkins Pipeline - это Groovy. Declarative-конвейер, который мы изучали раньше - это аккуратная обёртка-DSL поверх Groovy: ты пишешь pipeline, stages, steps, а движок превращает это в исполняемый Groovy-код. Поэтому groovy jenkins - не два разных мира, а один. Declarative просто прячет язык за фасадом, пока тебе не понадобится логика.

Понадобится она в трёх местах. Первое - блок script внутри declarative-конвейера: островок настоящего Groovy там, где декларативного синтаксиса не хватает. Второе - scripted pipeline, целиком написанный на Groovy без declarative-каркаса. Это более старый и более низкоуровневый стиль (jenkins scripted pipeline), и в 2026 он не стандарт - по умолчанию берём declarative. Но scripted незаменим, когда нужна динамика: генерировать stage'и в цикле, ветвиться сложными условиями, строить map стадий на лету. Третье и самое ценное - shared libraries, общие библиотеки на Groovy, куда выносят повторяющуюся логику для всех команд.

Код: Выделить всё

// Declarative со вставкой настоящего Groovy через script
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script {
                    def modules = ['api', 'web', 'worker']
                    for (m in modules) {
                        echo "Собираю модуль ${m}"
                        sh "make build MODULE=${m}"
                    }
                }
            }
        }
    }
}
То же самое в scripted-стиле выглядит так - без pipeline/stages, голый groovy pipeline:

Код: Выделить всё

node {
    stage('Build') {
        def modules = ['api', 'web', 'worker']
        modules.each { m ->
            echo "Собираю модуль ${m}"
            sh "make build MODULE=${m}"
        }
    }
}
Изображение

Минимум Groovy: переменные, строки, коллекции, условия

Пройдёмся по практичному срезу. Переменные объявляют через def (тип выводится сам) либо указывают тип явно. Точки с запятой не нужны, return в конце метода тоже опционален - возвращается значение последнего выражения.

Код: Выделить всё

def name = 'cyberlake'        // тип выведется как String
String env = 'prod'           // или явно
int retries = 3
def enabled = true
Строки. Одинарные кавычки - обычная строка без подстановки. Двойные кавычки - это GString с интерполяцией через ${...}. Внутри ${} - любое Groovy-выражение. Тройные кавычки удобны для многострочных блоков, например для shell-скриптов.

Код: Выделить всё

def version = '2.555.2'
echo 'Просто строка без подстановки: ${version}'   // выведет ${version} как есть
echo "Версия Jenkins LTS: ${version}"              // подставит значение
echo "Через год будет минимум: ${version.split('\\.')[0]}.x"

// многострочный shell через тройные двойные кавычки
sh """
    echo "Деплою версию ${version}"
    ./deploy.sh --tag ${version}
"/'"'/""".replace('/'"'/', '')
Важная деталь безопасности: в sh-шаге не вставляй пользовательские данные через двойные кавычки и ${}, иначе получишь инъекцию в shell. Лучше передавать через переменные окружения и одинарные кавычки внутри sh - об этом подробнее в уроке про безопасность.

Коллекции. Список - в квадратных скобках, map - в фигурных с двоеточием. У них куча удобных методов: each, collect, find, findAll. Замыкание (closure) пишется в фигурных скобках, неявный аргумент называется it.

Код: Выделить всё

def services = ['api', 'web', 'worker']
services.each { echo "Сервис: ${it}" }                    // обход
def upper = services.collect { it.toUpperCase() }         // ['API','WEB','WORKER']
def hasApi = services.find { it == 'api' } != null        // true

def config = [env: 'prod', replicas: 3, debug: false]
echo "Окружение ${config.env}, реплик ${config['replicas']}"
config.each { k, v -> echo "${k} = ${v}" }
Условия и циклы - почти как в Java, плюс groovy-сахар. Есть тернарный оператор и удобный "Elvis" ?: для значений по умолчанию.

Код: Выделить всё

def branch = env.BRANCH_NAME ?: 'main'   // если null, берём 'main'
if (branch == 'main') {
    echo 'Деплой в продакшен'
} else if (branch.startsWith('release/')) {
    echo 'Релизная ветка'
} else {
    echo 'Обычная фича-ветка'
}

def target = (branch == 'main') ? 'prod' : 'staging'   // тернарный
Методы определяют через def. В scripted они живут прямо в Jenkinsfile, в declarative - вне блока pipeline или в shared library.

Код: Выделить всё

def buildImage(String name, String tag) {
    sh "docker build -t ${name}:${tag} ."
    return "${name}:${tag}"
}
Главная специфика: CPS-трансформация и почему "обычный Groovy" ломается

Тут начинается то, что отличает jenkins script от любого другого Groovy. Конвейер обязан переживать перезапуск контроллера: остановили Jenkins на середине сборки, подняли обратно - сборка продолжается с того же места. Чтобы это работало, движок должен уметь в любой момент сохранить состояние выполнения на диск и потом восстановить. Достигается это через CPS - Continuation-Passing Style: твой Groovy-код переписывается в форму, которую можно сериализовать и приостановить.

Отсюда два следствия, на которые натыкаются все.

Первое: каждая переменная в CPS-коде должна быть сериализуемой. Если положишь в переменную несериализуемый объект (например, результат парсинга через стороннюю библиотеку, java.io.File, matcher регулярки) и он переживёт шаг-паузу вроде sh - получишь java.io.NotSerializableException. Классическая ловушка - matcher:

Код: Выделить всё

// ПЛОХО: matcher несериализуем, упадёт после sh
def m = ('abc123' =~ /\d+/)
echo m[0]
sh 'sleep 1'    // здесь состояние сохраняется - и падает на m

// ХОРОШО: достали нужное и сразу забыли про matcher
def num = ('abc123' =~ /\d+/)[0]
sh 'sleep 1'
Второе: не весь синтаксис Groovy переживает CPS-трансформацию. Создание объектов через new в конструкторах, некоторые операции с замыканиями, методы стандартной библиотеки в особых случаях - могут вести себя неожиданно. На практике достаточно держать в голове правило: пиши простой, прямолинейный код, без хитрых конструкций, и проблем почти не будет.

@NonCPS - мощно и опасно

Для тяжёлых вычислений CPS добавляет накладные расходы, и иногда нужен кусок настоящего, не трансформированного Groovy. Для этого есть аннотация @NonCPS. Метод, помеченный ею, исполняется как обычный Groovy - быстро и со всем синтаксисом. Но цена велика, и осторожность здесь не формальность.

Правила для @NonCPS, которые нельзя нарушать:
  • внутри нельзя вызывать pipeline-шаги (sh, echo, sh-подобное) - только чистый Groovy
  • нельзя вызывать обычные CPS-методы
  • не принимай и не возвращай несериализуемые значения - метод хорош для "посчитал и вернул сводку"
  • метод не должен переживать паузу: он отрабатывает целиком и сразу

Код: Выделить всё

// Корректное применение: распарсить и вернуть простой результат
@NonCPS
def parseModules(String json) {
    def data = new groovy.json.JsonSlurper().parseText(json)
    // вернём обычный список строк - он сериализуем
    return data.modules.collect { it.name as String }
}

pipeline {
    agent any
    stages {
        stage('Plan') {
            steps {
                script {
                    def names = parseModules('{"modules":[{"name":"api"},{"name":"web"}]}')
                    names.each { echo "Модуль: ${it}" }   // echo тут, СНАРУЖИ @NonCPS
                }
            }
        }
    }
}
Эмпирическое правило 2026: не тянись к @NonCPS, пока реально не уперся в производительность или в синтаксис, который CPS не тянет. В большинстве конвейеров он не нужен вовсе. Для разбора JSON, кстати, есть готовый безопасный шаг readJSON из плагина Pipeline Utility Steps - часто он лучше ручного JsonSlurper.

Типичные грабли
  • NotSerializableException после успешного на вид шага - почти всегда несериализуемая переменная (matcher, File, объект библиотеки) пережила sh/паузу. Извлеки примитив и не храни сам объект.
  • Путаница кавычек: ${var} в одинарных кавычках не подставляется. Подстановка - только в двойных и тройных двойных.
  • Цикл for с ленивыми диапазонами или сложными замыканиями иногда ведёт себя странно под CPS. Если что-то необъяснимо - перепиши на простой each или классический for по индексу.
  • @NonCPS с echo внутри: тихо или странно работает, потом ломается. Шаги - только снаружи.
  • Сборка списка стадий на лету в declarative невозможна - для динамики бери scripted или генерируй map стадий и запускай через parallel.
Мини-лаба

Повтори руками, это 15 минут и даёт интуицию:
  • Создай scripted Jenkinsfile с node/stage и пройди each по списку из трёх сервисов, на каждом вызвав sh "echo ...".
  • Сделай map с конфигом (env, replicas) и выведи его через интерполяцию ${}. Сравни одинарные и двойные кавычки на одной строке - убедись, что подстановка работает только в двойных.
  • Воспроизведи NotSerializableException: положи matcher (=~) в переменную, потом вызови sh 'sleep 1' и обратись к matcher после. Затем почини, вытащив значение до sh.
  • Напиши @NonCPS-метод, который принимает строку JSON и возвращает список имён. Вызови его из script-блока и выведи результат через echo (echo - снаружи метода).
Контрольные вопросы
  • Почему declarative pipeline - это тоже Groovy, и в каких трёх местах ты пишешь Groovy-код напрямую?
  • Что такое CPS-трансформация и зачем она нужна именно Jenkins?
  • Почему появляется NotSerializableException и как её обычно чинят?
  • Какие четыре правила надо соблюдать в методе с @NonCPS и почему к нему не стоит тянуться без нужды?
Итог

Groovy - язык, на котором стоит весь Jenkins Pipeline, и минимальный его срез нужен даже в declarative: переменные через def, строки с ${}, списки, map, условия и методы. Ключевая особенность - CPS-трансформация: код переписывается ради устойчивости к перезапускам, отсюда требование сериализуемости переменных и ограничения синтаксиса. @NonCPS - острый инструмент для редких случаев, не для повседневности. Этот фундамент - прямой мост к следующей большой теме: shared libraries, где Groovy выносит общую логику конвейеров за пределы одного Jenkinsfile.
👍1 ❤️2 🔥1 😄 🤔
Аватара пользователя
clickhousesmith
Сообщения: 1
Зарегистрирован: 15 май 2026, 21:48

Re: Groovy для Jenkins: основы скриптового конвейера

Сообщение clickhousesmith »

Спасибо, наконец дошло откуда лезет NotSerializableException. У меня как раз matcher был в переменной перед sh, вынес значение до шага - пайплайн перестал падать.
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
westbrook
Сообщения: 1
Зарегистрирован: 24 май 2026, 03:05

Re: Groovy для Jenkins: основы скриптового конвейера

Сообщение westbrook »

Вопрос: а readJSON из Pipeline Utility Steps реально лучше чем JsonSlurper в @NonCPS? Звучит так что ручной JsonSlurper вообще можно не трогать в обычных конвейерах.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Постобработка и уведомления о результате сборки
Следующая глава →
Скриптовый конвейер: логика, циклы и функции

Все главы курса «Jenkins: конвейеры CI/CD от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: что такое jenkins простыми словамикак установить jenkins на linux и в dockerпервичная настройка jenkins после установкикакие плагины jenkins нужны новичкукак настроить агенты jenkins для сборкиcicd для начинающих с чего начать на jenkins

Вернуться в «Jenkins: конвейеры CI/CD от основ до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя