Если ты когда-нибудь открывал чужой Jenkinsfile, написанный в стиле "как пошло, так и пошло", то знаешь эту боль: сто строк Groovy, переменные плодятся посреди логики, try/catch обмотан вокруг половины сборки, и понять, где тут вообще этапы, можно только запустив. Scripted pipeline даёт полную свободу - и ровно поэтому в нём так легко устроить помойку, которую через полгода никто (включая автора) не хочет трогать.
Declarative pipeline появился как ответ на этот хаос. Это не отдельный язык, а строгий каркас поверх того же Pipeline-движка: ты описываешь, ЧТО должно произойти и в каком порядке, а не как именно это исполнить шаг за шагом. Структура фиксированная, валидируется до запуска, читается сверху вниз как нормальный конфиг. В 2026 году declarative - это стандарт де-факто: с него начинают, на нём держат прод, а scripted достают только там, где реально нужна динамика (например, генерация этапов в цикле). Именно поэтому весь оставшийся курс мы будем вешать на этот скелет, и в этом уроке разберём его до косточек.
Сравни с соседями по цеху: в GitLab CI и GitHub Actions ты вообще пишешь YAML, где структура задана жёстко самим форматом. Declarative - это попытка Jenkins дать ту же предсказуемость, но с возможностью в любой момент провалиться в Groovy через блок script, когда декларативности перестаёт хватать. Лучшее из двух миров, если не злоупотреблять.

Обязательная структура jenkins pipeline
У любого declarative pipeline есть жёсткий минимум, без которого он просто не запустится. Вот этот минимум, голый каркас:
Код: Выделить всё
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Собираем проект'
}
}
}
}
- pipeline - внешний блок, обёртка вокруг всего. Весь декларативный код живёт строго внутри него. Снаружи - ничего, кроме разве что общих shared library импортов.
- agent - где исполняется конвейер. Обязателен либо на верхнем уровне, либо в каждом stage. Это ответ на вопрос "на какой машине крутить сборку".
- stages - контейнер для этапов. Внутри - один или несколько блоков stage.
- stage('имя') - конкретный этап с обязательным именем в кавычках. Имя попадёт в визуализацию, поэтому называй по-человечески: Build, Test, Deploy.
- steps - то, что реально выполняется внутри этапа. Без steps stage невалиден.
Как работает jenkins agent: any, none, label
Директива agent отвечает на вопрос, на каком исполнителе крутится твой код. Здесь три рабочих режима, которые нужно знать наизусть.
agent any - бери любой свободный агент. Самый частый вариант для простых сборок: Jenkins сам выберет узел, выделит workspace и запустит там все этапы.
agent { label '...' } - привязка к агенту по метке. Так ты говоришь "мне нужна машина с Docker" или "собирай только на linux-узлах". Метки навешиваются на агентов в настройках, и это основной инструмент маршрутизации нагрузки:
Код: Выделить всё
pipeline {
agent { label 'linux && docker' }
stages {
stage('Build') {
steps {
sh 'docker build -t myapp:${BUILD_NUMBER} .'
}
}
}
}
agent none - на верхнем уровне глобальный агент НЕ выделяется. Это осознанный приём для тяжёлых конвейеров: ты не держишь дорогую машину занятой всё время сборки, а назначаешь jenkins agent точечно, на каждом этапе свой. Но за свободу надо платить: при agent none каждый stage обязан объявить собственный agent, иначе сборка падает.
Код: Выделить всё
pipeline {
agent none
stages {
stage('Build') {
agent { label 'builder' }
steps {
sh 'make build'
}
}
stage('Deploy') {
agent { label 'deploy-host' }
steps {
sh './deploy.sh production'
}
}
}
}
Код: Выделить всё
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/agent/stages - это скелет, но на него навешивается набор директив верхнего уровня. Их полезно знать сразу, потому что именно их ты будешь дописывать в каждом боевом Jenkinsfile:
- environment - переменные окружения. Объявленные вверху видны всем этапам, объявленные внутри stage - только ему.
- options - опции сборки: таймауты, число хранимых сборок, запрет параллельного запуска.
- parameters - параметры, которые спрашиваются при ручном запуске (строки, булевы, выбор из списка).
- triggers - автозапуск: по cron, по опросу SCM, по вебхуку.
- post - что сделать в конце: блоки always, success, failure, unstable. Сюда вешают уведомления и очистку.
Код: Выделить всё
pipeline {
agent any
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
}
environment {
APP_VERSION = "1.0.${BUILD_NUMBER}"
}
stages {
stage('Build') {
steps {
sh 'mvn -B clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
failure {
echo "Сборка ${APP_VERSION} упала, разбираемся"
}
}
}
Типичные грабли
- Забыл steps внутри stage. Пустой stage невалиден. Если этап-заглушка нужен, поставь хотя бы echo.
- agent none без agent на этапах. Классика: объявил none сверху, а в stage агента не указал - сборка падает с ошибкой ещё на старте.
- Groovy-логика прямо в steps. Внутри steps нельзя писать произвольный Groovy (циклы, присваивания) - только шаги. Нужна логика - оборачивай в блок script { ... }, но не злоупотребляй: много script внутри declarative - сигнал, что пора пересмотреть подход.
- Ставка на Blue Ocean. Красивый редактор конвейеров фактически заброшен и не развивается. Не строй на нём рабочий процесс - пиши Jenkinsfile руками в репозитории, это и есть pipeline as code.
- Имя stage без кавычек. stage(Build) - синтаксическая ошибка. Имя этапа всегда строка: stage('Build').
Руками, чтобы отложилось:
- Создай pipeline-задание, вставь голый каркас из первого примера, запусти. Убедись, что собралось.
- Удали блок steps и нажми Replay - посмотри, как линтер ругается. Верни обратно.
- Переделай конвейер на agent none с двумя этапами, каждому задай свой agent { label '...' } (метку можно взять у встроенного узла). Запусти, открой визуализацию stages.
- Добавь блок post с секциями always и failure, специально сломай команду в steps (например, sh 'exit 1') и проверь, что отработал именно failure.
Контрольные вопросы
- Какие четыре уровня вложенности обязательны в минимальном валидном declarative pipeline?
- Чем agent none отличается от agent any и что обязательно делать в каждом stage при agent none?
- Куда поместить переменную окружения, чтобы она была видна только одному этапу, а не всему конвейеру?
- Почему произвольный Groovy нельзя писать прямо в steps и как из этой ситуации выходят?
Declarative pipeline - это строгий, предсказуемый каркас: pipeline снаружи, agent для выбора исполнителя, stages с отдельными stage, внутри каждого steps. Этот скелет валидируется до запуска и читается как конфиг. Разберёшься с jenkins stages, режимами agent и порядком директив - и дальше любой урок курса будет просто навешиванием новых возможностей на уже знакомую структуру. Следующий шаг - параметры, переменные окружения и условия, которые оживят этот каркас.