Scripted против Declarative: два синтаксиса конвейера

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

Scripted против Declarative: два синтаксиса конвейера

Сообщение 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
Ты открываешь чужой Jenkinsfile, а там стена из Groovy: node, ветвления, замыкания, try/catch, переменные плодятся как кролики. Чтобы понять, на каком шаге падает сборка, нужно прочитать половину файла. А в соседнем проекте Jenkinsfile читается почти как список дел: stages, stage, steps - видно с первого взгляда. Это не разные люди с разной аккуратностью. Это два разных синтаксиса pipeline в Jenkins, и выбор между ними определяет, будешь ли ты потом проклинать свой конвейер или спокойно его поддерживать.

В этом уроке разбираем оба подхода бок о бок: scripted pipeline (полный Groovy, гибкий и опасный) и declarative pipeline (структурированный и читаемый). Покажу одну и ту же задачу в двух вариантах, объясню, когда хватает declarative (а это процентов девяносто случаев), когда реально нужен scripted, и как блок script внутри declarative служит мостом между мирами.

Откуда взялись два синтаксиса pipeline в Jenkins

Сначала был только scripted. Когда в Jenkins 2 появился Pipeline-as-Code, конвейер описывали обычным кодом на Groovy - языке поверх JVM. Это давало абсолютную свободу: хочешь цикл - пиши цикл, хочешь сгенерировать стадии на лету - пожалуйста. Но свобода обернулась болью. Каждый автор изобретал свою структуру, jenkins groovy расползался, и поддерживать чужой конвейер было мучением.

В ответ команда Jenkins сделала declarative - надстройку с жёсткой структурой. Ты не пишешь произвольный код, а заполняешь заранее определённые блоки: pipeline, agent, stages, steps, post. Меньше гибкости - больше порядка, читаемости и понятных сообщений об ошибках. На 2026 год официальная рекомендация однозначна: declarative - выбор по умолчанию для новых конвейеров, scripted - только когда декларативного синтаксиса реально не хватает.

Важная деталь под капотом: declarative - это не отдельный движок, а слой проверки и структуры поверх того же Groovy. Поэтому внутри он умеет в нужном месте пускать произвольный код через блок script. Об этом ниже.

Пара слов про окружение, чтобы примеры были применимы. Актуальная Jenkins LTS на середину 2026 - 2.541.x (релиз 2.541.1 от 21 января 2026). Эта линейка LTS - последняя с поддержкой Java 11; weekly-сборки уже требуют Java 21, а ближайшие LTS перейдут на Java 17/21. Если ставишь Jenkins с нуля - бери Java 21, не прогадаешь. И ещё: визуальный редактор Blue Ocean заморожен - новых функций не будет, только критические правки безопасности. Так что синтаксис мы пишем руками в Jenkinsfile, а смотреть стадии удобнее через Pipeline Stage View или Pipeline Graph View.

Изображение

Одна задача, два синтаксиса: declarative pipeline jenkins и scripted рядом

Возьмём типовую задачу: собрать Maven-проект, прогнать тесты, при успехе - условно задеплоить, и в любом случае сохранить артефакты и убрать рабочую директорию. Сначала declarative.

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

pipeline {
    agent { label 'linux' }

    options {
        timeout(time: 20, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    environment {
        APP_VERSION = '1.4.0'
    }

    stages {
        stage('Build') {
            steps {
                sh 'mvn -B -DskipTests clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn -B test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh "./deploy.sh ${APP_VERSION}"
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
            cleanWs()
        }
    }
}
Прочитай этот jenkins pipeline синтаксис сверху вниз - и тебе сразу ясно: где агент, какие стадии, что деплой идёт только на ветке main, что артефакты архивируются всегда. Структура говорит сама за себя, jenkins declarative тем и хорош.

Теперь то же самое на scripted pipeline:

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

node('linux') {
    def appVersion = '1.4.0'

    timeout(time: 20, unit: 'MINUTES') {
        try {
            stage('Build') {
                sh 'mvn -B -DskipTests clean package'
            }

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

            if (env.BRANCH_NAME == 'main') {
                stage('Deploy') {
                    sh "./deploy.sh ${appVersion}"
                }
            }
        } finally {
            archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
            cleanWs()
        }
    }
}
Делает то же самое. Но обрати внимание на разницу в мышлении. В declarative ты описываешь, ЧТО должно быть: блок when, блок post, options. В scripted ты пишешь, КАК это сделать: руками оборачиваешь в try/finally, чтобы артефакты сохранились даже при падении тестов, руками проверяешь env.BRANCH_NAME через if. Гибко - да. Но любая опечатка в логике, и post-обработка молча не сработает. Declarative такие вещи берёт на себя.

Когда хватает declarative, а когда нужен scripted pipeline

Практическое правило простое. Если твой конвейер - это линейная или почти линейная последовательность стадий с парой условий, declarative покрывает всё: matrix для параллельных матриц сборки, parallel внутри стадии, when для условий, post для уведомлений, credentials для секретов. Это и есть те самые девяносто процентов.

Scripted реально оправдан, когда нужна динамика, которую структура declarative не выражает. Классические примеры: генерация стадий в цикле по списку, который известен только во время выполнения; сложные вычисления над результатами предыдущих шагов; нетривиальная работа с замыканиями и Map. Вот где scripted на месте - динамические стадии:

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

node {
    def services = ['auth', 'billing', 'gateway']
    def branches = [:]

    for (svc in services) {
        def name = svc
        branches["build-${name}"] = {
            stage("Build ${name}") {
                sh "docker build -t registry.local/${name}:latest ./${name}"
            }
        }
    }

    parallel branches
}
Здесь количество и имена стадий рождаются из списка. В чистом declarative так в лоб не сделать - его stages статичны на этапе разбора. (Тонкость: переменную name внутри цикла переприсваиваем специально, иначе замыкание захватит последнее значение svc - классические грабли Groovy.)

Но прежде чем тянуться к scripted, вспомни про мост. Внутри declarative-стадии есть блок script, который пускает произвольный jenkins groovy ровно там, где он нужен, не ломая остальную структуру:

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

pipeline {
    agent any
    stages {
        stage('Resolve version') {
            steps {
                script {
                    def pom = readMavenPom file: 'pom.xml'
                    env.APP_VERSION = pom.version
                    echo "Собираем версию ${env.APP_VERSION}"
                }
            }
        }
        stage('Build') {
            steps {
                sh "docker build -t app:${env.APP_VERSION} ."
            }
        }
    }
}
Для тех самых девяноста процентов случаев, когда кажется, что нужен scripted, правильный ответ - declarative с локальным блоком script. Ты сохраняешь читаемую структуру и вставляешь Groovy-логику точечно, а не переписываешь весь конвейер императивно. Тянуться к полностью scripted стоит, только когда блоков script становится больше, чем декларативной обвязки - тогда структура уже не помогает, а мешает.

И раз речь про современный стек: declarative прекрасно дружит с эфемерными агентами в Kubernetes. Pod создаётся под сборку и удаляется после неё, а описывается это прямо в agent:

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

pipeline {
    agent {
        kubernetes {
            yaml '''
                apiVersion: v1
                kind: Pod
                spec:
                  containers:
                  - name: maven
                    image: maven:3.9-eclipse-temurin-21
                    command: ['sleep']
                    args: ['infinity']
            '''
        }
    }
    stages {
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn -B clean package'
                }
            }
        }
    }
}
Типичные грабли
  • Блок pipeline должен быть на самом верху Jenkinsfile - никакого кода до него. Если хочешь определить функцию или переменную глобально, выноси её ВЫШЕ блока pipeline, а внутри стадий вызывай через script. Перемешивать свободный Groovy и declarative на одном уровне нельзя - получишь ошибку разбора.
  • Присваивание env-переменных делается в блоке environment или внутри script - не пытайся писать env.FOO = 'bar' прямо в steps без script.
  • В scripted легко забыть try/finally и потерять post-обработку при падении. В declarative блок post гарантирует выполнение - не воспроизводи его руками без необходимости.
  • Не путай scripted с declarative-через-script. Блок script внутри стадии - это островок Groovy, а не разрешение писать весь конвейер императивно.
  • Захват переменной цикла в замыкании (как в примере с динамическими стадиями) - переприсваивай значение в локальную переменную внутри тела цикла, иначе все замыкания получат последнее значение.
Мини-лаба
  • Возьми любой свой проект и напиши для него declarative Jenkinsfile с тремя стадиями: Build, Test, Deploy. Деплой оберни в when по ветке main.
  • Перепиши тот же конвейер в scripted-стиле через node и try/finally. Сравни длину и читаемость - почувствуй разницу руками.
  • Добавь в declarative-вариант стадию с блоком script, которая читает версию из файла (readFile или readMavenPom) и кладёт её в env.
  • Попробуй сгенерировать 3 параллельные стадии в цикле в scripted-варианте через parallel. Убедись, что без переприсваивания переменной цикла стадии "слипаются".
Контрольные вопросы
  • Чем принципиально отличается подход declarative от scripted: что ты описываешь в каждом из них?
  • Где разрешено размещать блок pipeline в Jenkinsfile и почему нельзя ставить код выше него?
  • Какую роль играет блок script внутри declarative-стадии и почему он закрывает большинство случаев "мне нужен scripted"?
  • Назови задачу, которую чисто декларативно выразить нельзя и где scripted оправдан.
Итог

Два синтаксиса - это не вкусовщина, а инструмент под задачу. Declarative даёт структуру, читаемость и встроенные гарантии (post, when, options) - и поэтому он выбор по умолчанию в 2026, особенно для новичков. Scripted даёт полную мощь Groovy для редких динамических сценариев. А блок script внутри declarative объединяет лучшее: читаемый каркас плюс точечная гибкость там, где она нужна. Начинай с declarative, переходи на scripted осознанно - когда декларативного синтаксиса действительно перестаёт хватать, а не из привычки писать всё кодом.
👍4 ❤️ 🔥1 😄 🤔
Аватара пользователя
elastic_veteran
Сообщения: 1
Зарегистрирован: 13 май 2026, 21:11

Re: Scripted против Declarative: два синтаксиса конвейера

Сообщение elastic_veteran »

Спасибо, наконец дошло зачем нужен блок script. Полгода писал всё на node {} потому что в старых гайдах так, а оказывается declarative + script решает почти всё.
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
Ivamaseg
Сообщения: 1
Зарегистрирован: 19 май 2026, 18:23

Re: Scripted против Declarative: два синтаксиса конвейера

Сообщение Ivamaseg »

Вопрос: а matrix в declarative реально заменяет генерацию стадий в цикле? У меня сборка под 4 версии php, сейчас на scripted через parallel в цикле. Стоит переписывать?
👍3 ❤️ 🔥1 😄 🤔
Ответить
← Предыдущая глава
Конвейеры Jenkins и Jenkinsfile: pipeline as code
Следующая глава →
Структура конвейера: node, stage, steps и первый pipeline

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: что такое jenkins простыми словамиcicd для начинающих с чего начать на jenkinsjenkinsfile declarative или scripted что выбратьgroovy в jenkins scripted pipeline как написатьjenkins shared library как создать и подключитьпараметры и параллельные стейджи в jenkins pipeline

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

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

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