Скриптовый конвейер: логика, циклы и функции

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

Скриптовый конвейер: логика, циклы и функции

Сообщение 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-пайплайнов, и они отлично работали, пока стейджи были статичны: собрать, протестировать, задеплоить. А потом прилетела задача: "у нас 12 микросервисов, для каждого нужен свой стейдж сборки и теста, список сервисов лежит в конфиге и меняется раз в неделю". И тут declarative начинает упираться. Прописывать 12 одинаковых блоков stage руками - это не инженерия, это копипаста, которая развалится на тринадцатом сервисе. Ровно здесь на сцену выходит scripted pipeline - полноценный код на Groovy, где ты можешь крутить циклы, ветвиться через if/else и генерировать стейджи на лету.

В этом уроке разберём, как программировать конвейер по-настоящему: переменные, условия, цикл, функция, обработка ошибок и динамическая генерация стейджей. И главное - честно поговорим, когда jenkins scripted оправдан, а когда это выстрел себе в ногу.

Как устроен jenkins scripted pipeline: node как обёртка

Declarative начинается с pipeline { ... } и жёсткой структуры. Scripted - это голый Groovy-скрипт, который Jenkins исполняет сверху вниз. Корневая конструкция тут не pipeline, а node - блок, который выделяет агента (executor) и рабочую директорию (workspace). Всё, что внутри node, выполняется на этом агенте.

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

node('linux') {
    stage('Checkout') {
        checkout scm
    }
    stage('Build') {
        sh './gradlew clean build'
    }
}
Обрати внимание: stage внутри scripted - это просто шаг с блоком, а не директива с правилами. Никакого agent, when, post - всё это ты пишешь сам кодом. node('linux') означает "дай агента с меткой linux". Без аргумента node {} возьмёт любой свободный executor.

Важная деталь про код в jenkins groovy: пайплайн исполняется на контроллере (controller), а реальные команды через sh уходят на агента. Поэтому Groovy-логика (циклы, условия) крутится на контроллере, а sh, git, docker - на агенте. Из этого растёт половина граблей, до которых дойдём.

Изображение

Переменные, условия и функция в jenkins pipeline

В scripted ты пишешь обычный Groovy. Переменные объявляются через def (или с типом), условия - привычный if/else, есть тернарный оператор.

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

node {
    def branch = env.BRANCH_NAME ?: 'main'
    def isRelease = branch.startsWith('release/')

    stage('Resolve config') {
        if (isRelease) {
            env.DEPLOY_ENV = 'staging'
            echo "Релизная ветка, деплоим в staging"
        } else if (branch == 'main') {
            env.DEPLOY_ENV = 'dev'
        } else {
            echo "Фича-ветка ${branch}, деплой пропускаем"
            env.DEPLOY_ENV = ''
        }
    }
}
Функция в jenkins pipeline объявляется через def прямо в теле скрипта. Это отличный способ убрать дублирование - вынести повторяющийся набор шагов в один вызов.

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

def buildService(String name) {
    stage("Build ${name}") {
        sh "docker build -t registry.local/${name}:${env.BUILD_NUMBER} services/${name}"
    }
}

node {
    checkout scm
    buildService('auth')
    buildService('billing')
}
Функция может возвращать значение, принимать Map параметров, вызывать другие шаги - это полноценный Groovy. Тут scripted и раскрывается: ты не описываешь конвейер декларацией, ты его программируешь.

Цикл и динамическая генерация стейджей

Это коронный приём scripted, ради которого его чаще всего и берут. У тебя есть список сервисов - и jenkins цикл генерирует по стейджу на каждый, не заставляя писать их руками.

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

node('docker') {
    def services = ['auth', 'billing', 'gateway', 'notifications']

    stage('Checkout') {
        checkout scm
    }

    // обычный for по списку
    for (svc in services) {
        stage("Test ${svc}") {
            sh "docker compose run --rm ${svc} ./run-tests.sh"
        }
    }
}
Добавили сервис в список - получили новый стейдж автоматически. Ни строчки больше менять не надо.

Часто хочется гонять сервисы не последовательно, а параллельно. Тогда собираем Map с замыканиями и отдаём его шагу parallel. Здесь есть классическая ловушка с замыканием в цикле, поэтому показываю сразу правильный вариант через .each и .collectEntries:

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

node('docker') {
    def services = ['auth', 'billing', 'gateway']

    stage('Checkout') { checkout scm }

    def branches = services.collectEntries { svc ->
        ["Test ${svc}", {
            stage("Test ${svc}") {
                sh "docker compose run --rm ${svc} ./run-tests.sh"
            }
        }]
    }

    parallel branches
}
collectEntries создаёт пары "имя ветки -> замыкание", каждое замыкание захватывает свой svc корректно. parallel запускает их одновременно - четыре теста вместо одного долгого хвоста.

Обработка ошибок: try/catch/finally

В declarative для уборки есть секция post. В scripted ты пишешь обработку сам - и это даёт полный контроль. Любой упавший шаг (sh с ненулевым кодом) бросает исключение, которое ловится обычным try/catch.

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

node {
    try {
        stage('Build') {
            sh './gradlew build'
        }
        stage('Deploy') {
            sh './deploy.sh staging'
        }
        currentBuild.result = 'SUCCESS'
    } catch (err) {
        currentBuild.result = 'FAILURE'
        echo "Сборка упала: ${err.getMessage()}"
        throw err
    } finally {
        stage('Cleanup') {
            sh 'docker compose down --remove-orphans || true'
            junit testResults: '**/build/test-results/**/*.xml', allowEmptyResults: true
        }
    }
}
finally отработает всегда - и при успехе, и при падении. Сюда кладут публикацию отчётов, остановку контейнеров, уведомления. throw err в catch важен: без него Jenkins не узнает, что билд должен быть красным, и покажет зелёный, хотя деплой провалился.

Когда scripted оправдан, а когда это переусложнение

Правило 2026 года простое: declarative - выбор по умолчанию, scripted - когда упёрся в его потолок. Бери scripted, если:
  • стейджи генерируются динамически из данных (список сервисов, матрица версий, ответ внешнего API);
  • нужна сложная логика ветвления, которую when уже не вытягивает;
  • ты пишешь переиспользуемый код для shared library.
Не бери scripted, если конвейер по сути линейный "собрал - проверил - выкатил". Тогда declarative читается лучше, валидируется в редакторе и не превращается в простыню Groovy, которую через полгода никто не поймёт. Кстати, старый визуальный редактор Blue Ocean считай заброшенным - на нём ничего нового не строй, это наследие; смотри на текущий UI Jenkins и сам Jenkinsfile как источник правды.

Если динамика нужна, но Jenkinsfile разбух - переноси логику в shared library. Это отдельный Git-репозиторий с папкой vars/, где каждый файл - твой собственный шаг. Грубый скелет vars/buildAll.groovy:

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

// vars/buildAll.groovy в shared library
def call(List services) {
    services.each { svc ->
        stage("Build ${svc}") {
            sh "docker build -t ${svc}:${env.BUILD_NUMBER} services/${svc}"
        }
    }
}
После подключения библиотеки в Jenkinsfile остаётся buildAll(['auth', 'billing']) - вся динамика спрятана, пайплайн снова читаемый. Это и есть взрослый способ жить со scripted: сложность есть, но она инкапсулирована.

Пара слов про актуальность. На середину 2026 свежая LTS - линейка 2.555.x, минимальная Java уже 17, а новые сборки тянутся к Java 21. Groovy-движок и шаги node/stage/parallel/sh за годы не менялись, так что код из урока рабочий и на последних версиях. Для сравнения: в GitLab CI и GitHub Actions динамическую матрицу делают декларативно (matrix, parallel: matrix), без полноценного языка - там это плюс к простоте, но когда логика выходит за рамки матрицы, начинается пляска с генерацией YAML. Jenkins scripted в этом смысле честнее: у тебя сразу настоящий язык, плати за это дисциплиной.

Типичные грабли
  • Замыкание в for захватывает переменную по ссылке: классический баг, когда все параллельные ветки вдруг тестируют последний сервис из списка. Лечится через .each / .collectEntries, как выше, либо локальной копией def s = svc внутри тела цикла.
  • Тяжёлая Groovy-логика на контроллере. Циклы и обработка строк исполняются на контроллере, а не на агенте. Не вычитывай гигабайтные файлы в Groovy - перекладывай это на sh внутри node.
  • NotSerializableException: scripted-пайплайн сериализуется между шагами. Объекты вроде матчеров регэкспа или потоков нельзя хранить в переменных через границу sh/stage. Заворачивай такую работу в метод с аннотацией @NonCPS либо считай результат сразу.
  • Забытый throw в catch - билд зелёный, хотя всё упало. Всегда пробрасывай исключение, если не глушишь ошибку осознанно.
  • readFile/httpRequest вне node. Шаги, которым нужен workspace или executor, обязаны жить внутри node {}, иначе словишь "Required context class hudson.FilePath is missing".
Мини-лаба

Повтори руками, чтобы улеглось:
  • Создай Pipeline job со scripted-скриптом: node, внутри переменная def services = ['a', 'b', 'c'] и for, генерирующий стейдж "Echo X" с sh "echo X" на каждый элемент. Запусти, посмотри стейджи в Stage View.
  • Переделай тот же цикл на parallel через collectEntries. Сравни, как стейджи легли по времени.
  • Оберни весь node в try/catch/finally. В одном из sh поставь exit 1 и убедись, что finally всё равно отработал, а билд стал красным благодаря throw.
  • Вынеси генерацию стейджей в функцию def runStages(list) и вызови её. Прочувствуй, как Jenkinsfile стал короче.
Контрольные вопросы
  • Чем node в scripted отличается от pipeline в declarative и где физически исполняется Groovy-логика, а где шаги sh?
  • Как из списка сервисов сгенерировать стейджи циклом, и почему для параллельного запуска берут collectEntries, а не просто for?
  • Зачем в catch нужен throw и что произойдёт с результатом билда, если его забыть?
  • По каким признакам ты выберешь scripted вместо declarative, и как shared library помогает не утонуть в сложности?
Итог

Scripted pipeline - это Jenkins, когда декларации перестаёт хватать. Ты получаешь настоящий язык: переменные, if/else, цикл, функцию, try/catch/finally и динамическую генерацию стейджей из данных. Цена - дисциплина и читаемость, поэтому держи declarative дефолтом, переключайся на scripted ради реальной динамики, а разросшуюся логику прячь в shared library. Тогда даже тринадцатый микросервис добавляется одной строчкой в список.
👍2 ❤️3 🔥 😄 🤔
Аватара пользователя
VimWhale
Сообщения: 1
Зарегистрирован: 29 май 2026, 20:57

Re: Скриптовый конвейер: логика, циклы и функции

Сообщение VimWhale »

Вот этот момент с collectEntries прямо спас. У меня в parallel все три ветки тестили только последний сервис, неделю не мог понять почему. Локальная копия def s = svc тоже сработала, спасибо за разбор замыканий.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
malkia
Сообщения: 1
Зарегистрирован: 13 май 2026, 04:36

Re: Скриптовый конвейер: логика, циклы и функции

Сообщение malkia »

Подскажите, а NotSerializableException ловится только в scripted или в declarative со script {} блоком тоже прилетит? У меня регэксп в переменной падает после sh, думал дело в node.
👍1 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
Groovy для Jenkins: основы скриптового конвейера
Следующая глава →
Общие библиотеки Jenkins: Shared Libraries

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: jenkinsfile declarative или scripted что выбратьgroovy в jenkins scripted pipeline как написатьjenkins shared library как создать и подключитьпараметры и параллельные стейджи в jenkins pipelineструктура declarative pipeline в jenkins: stages, agent, steps

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

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

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