Это и есть главная боль, которую решает jenkins миграция на пайплайны. Freestyle - это конфигурация, спрятанная в UI. Pipeline - это конфигурация как код, которая лежит рядом с самим проектом в Git. В этом уроке разберём, зачем и как делать перевод на pipeline без простоя, как разложить старую джобу на стейджи, перенести секреты и параметры, и как не уронить прод во время переезда. Будет чек-лист, который можно сразу пускать в работу.
Зачем вообще нужна jenkins конвертация Freestyle в Pipeline
Давай честно: Freestyle работает. Если джоба собирает один проект и больше ничего, переписывать её ради моды смысла нет. Но как только логика растёт, Freestyle начинает мешать. Вот реальные причины, по которым стоит делать перевод.
- Версионирование. Jenkinsfile лежит в репозитории. Любое изменение сборки проходит через коммит, а значит через историю, blame и теги. Откатить сломанный деплой - это git revert, а не археология в UI.
- Ревью. Изменения в Freestyle-джобе никто не видит. Изменения в Jenkinsfile уезжают в pull request, и коллега может сказать "ты тут забыл таймаут". Это нормальный процесс инженерии вместо ковбойства.
- Воспроизводимость. Pipeline as Code легко скопировать в другой проект, шаблонизировать через shared library, прогнать локально глазами. Freestyle копируется только мышкой и только криво.
- Восстановление. Упал сервер Jenkins - ты разворачиваешь чистый контроллер (в идеале через JCasC, об этом был отдельный урок) и подтягиваешь Jenkinsfile из Git. Парк Freestyle-джоб без бэкапа config.xml восстанавливать нечем.

Как разобрать Freestyle на стейджи
Ключ к миграции - перестать думать о джобе как о свалке шагов и начать думать о ней как о конвейере с фазами. Любая Freestyle-джоба раскладывается на четыре-пять логических кусков, и каждый кусок становится стейджем.
- SCM (раздел "Source Code Management") -> блок checkout или просто опция в declarative.
- Build (шаги "Execute shell" / "Invoke Maven") -> stage('Build').
- Test (отдельные шаги тестов) -> stage('Test').
- Post-build actions (архив артефактов, публикация отчётов, уведомления) -> секция post.
Код: Выделить всё
pipeline {
agent any
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
disableConcurrentBuilds()
}
tools {
maven 'maven-3.9'
jdk 'jdk-21'
}
stages {
stage('Build') {
steps {
sh 'mvn -B clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn -B test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
failure {
slackSend channel: '#ci', message: "Сборка ${env.JOB_NAME} #${env.BUILD_NUMBER} упала"
}
}
}
Конвертация scripted в declarative и помощник миграции
Часть джоб у тебя может быть уже на pipeline, но написана в scripted-стиле - сплошной node { ... } с Groovy-логикой. Это законно, но читать такое тяжело, особенно новичкам. Перевод scripted -> declarative обычно стоит того.
Сравни. Было (scripted):
Код: Выделить всё
node {
stage('Build') {
checkout scm
sh 'make build'
}
stage('Test') {
sh 'make test'
}
}
Код: Выделить всё
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
}
}
Для большого парка джоб есть официальный помощник - плагин Declarative Pipeline Migration Assistant. Он берёт Freestyle-джобу и генерирует из неё черновой Jenkinsfile. Полезно понимать его границы: он работает best-effort. Что умеет распарсить - конвертирует, что не умеет (экзотические плагины post-build) - оставляет в виде заглушки-стейджа с комментарием "допили руками". Не жди, что он выплюнет готовый продакшен-конвейер. Это стартовая болванка процентов на семьдесят, остаток ты дописываешь сам. Но как способ за минуту получить скелет вместо чистого листа - вещь рабочая.
Перенос секретов и параметров
Тут чаще всего обжигаются. В Freestyle секреты подключались галочкой "Use secret text(s) or file(s)" в build environment, а параметры - через "This project is parameterized". В Jenkinsfile это явный код.
Параметры объявляются в секции parameters:
Код: Выделить всё
pipeline {
agent any
parameters {
string(name: 'TARGET_ENV', defaultValue: 'staging', description: 'Куда катим')
choice(name: 'REGION', choices: ['eu', 'us', 'asia'], description: 'Регион деплоя')
booleanParam(name: 'RUN_SMOKE', defaultValue: true, description: 'Гонять ли smoke-тесты')
}
stages {
stage('Deploy') {
steps {
sh "./deploy.sh --env ${params.TARGET_ENV} --region ${params.REGION}"
}
}
}
}
Секреты в declarative подключаются двумя способами. Если секрет нужен во всём пайплайне - через environment с хелпером credentials():
Код: Выделить всё
pipeline {
agent any
environment {
REGISTRY = credentials('docker-registry-creds')
}
stages {
stage('Push') {
steps {
// REGISTRY_USR и REGISTRY_PSW появляются автоматически
sh 'echo "$REGISTRY_PSW" | docker login -u "$REGISTRY_USR" --password-stdin registry.local'
}
}
}
}
Код: Выделить всё
stage('Deploy') {
steps {
withCredentials([usernamePassword(
credentialsId: 'prod-ssh',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS')]) {
sh './remote-deploy.sh'
}
}
}
Типичные грабли
- Многострочный shell теряет поведение. В Freestyle каждый шаг "Execute shell" по умолчанию падает на первой ошибке. В sh внутри Jenkins тоже, но если ты склеил несколько команд через точку с запятой в одну строку - падение проглотится. Ставь set -e в начало скрипта или используй set -euo pipefail.
- Забытый agent. В declarative блок agent обязателен на уровне pipeline или в каждом стейдже. Забудешь - валидация не пропустит.
- Переменные окружения из Freestyle. Глобальные env, которые ты настроил в "Manage Jenkins", в Multibranch-пайплайне могут вести себя иначе. Лучше объявлять нужное явно в секции environment.
- Слэш в имени ветки. При переезде на Multibranch ветка feature/foo превращается в имя джобы feature%2Ffoo. Если на эту джобу кто-то ссылается как на downstream - ссылка сломается.
- Параллельный запуск. Freestyle-джоба деплоя часто молча запрещала параллельные сборки настройкой. В pipeline добавь disableConcurrentBuilds() в options, иначе два деплоя в прод поедут одновременно.
Мини-лаба
Возьми любую свою простую Freestyle-джобу (или создай тестовую с чекаутом и одним sh-шагом) и проделай руками:
- Выпиши на бумаге, из каких логических фаз состоит джоба: SCM, сборка, тесты, post-build.
- Установи плагин Declarative Pipeline Migration Assistant и прогони через него джобу. Посмотри, что он сгенерил и где оставил заглушки.
- Допиши черновой Jenkinsfile до рабочего: добавь options с timeout и buildDiscarder, перенеси post-build в секцию post.
- Заведи в Credentials Store тестовый secret text и подключи его через withCredentials.
- Создай Pipeline-джобу "from SCM", указывающую на Jenkinsfile в репозитории, запусти и сверь результат со старой Freestyle.
- Почему Jenkinsfile в Git выигрывает у Freestyle-джобы с точки зрения ревью и восстановления после сбоя?
- Чем отличается подключение секрета через environment + credentials() от withCredentials, и когда выбирать каждый способ?
- Что реально делает плагин Declarative Pipeline Migration Assistant и почему его вывод нельзя сразу катить в прод?
- Как организовать переезд большой Freestyle-джобы деплоя, чтобы в случае проблемы откатиться одним движением?
Миграция с Freestyle на Jenkinsfile - это не про синтаксис, а про то, чтобы вытащить логику сборки из тёмного угла веб-формы на свет Git, под ревью и под версионирование. Раскладывай джобу на стейджи, переноси SCM, сборку, тесты и post-build в declarative-структуру, аккуратно переноси параметры в parameters и секреты по ID, не трогая сами значения. Используй помощник миграции как генератор болванки, а не как готовое решение. И никогда не убивай рабочую Freestyle-джобу в тот же день, когда запустил её замену - дай новой доказать себя на параллельных прогонах. Тогда переезд пройдёт без приключений на проде.