【CI/CD·Jenkins篇】Pipeline 基础:Declarative 与 Scripted 语法详解

前言

Jenkins Pipeline 是 Jenkins 的核心能力------用代码定义完整的交付流水线。Pipeline 有两种写法:Declarative(声明式)和 Scripted(脚本式)。本篇详解两种语法的区别、选择策略和编写技巧,让你能独立编写生产级流水线。


一、两种语法的选择

| 维度 | Declarative | Scripted |

|------|-------------|----------|

| 语法 | 结构化 YAML 风格 | Groovy 脚本 |

| 上手难度 | 低 | 中高 |

| 灵活性 | 中(但足够覆盖90%场景) | 高 |

| 可读性 | 强 | 中 |

| 代码审查 | 容易 | 较难 |

| 推荐场景 | 90% 的流水线 | 复杂条件逻辑 |

结论:优先用 Declarative,只有遇到它无法表达的复杂逻辑时才回退到 Scripted。


二、Declarative Pipeline 语法详解

基本结构

复制代码
pipeline {
    agent any  // 在任意可用节点执行
    
    environment {
        // 环境变量定义
        APP_NAME = 'myapp'
        VERSION = "${env.BUILD_NUMBER}"
        DOCKER_REGISTRY = 'registry.mycompany.com'
    }
    
    options {
        // 流水线选项
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()  // 禁止并发构建
    }
    
    parameters {
        // 参数定义
        choice(name: 'ENV', choices: ['dev', 'test', 'staging', 'prod'], description: '部署环境')
        string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像标签')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '是否跳过测试')
    }
    
    triggers {
        // 触发器
        cron('H 2 * * *')  // 每天凌晨2点执行
    }
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        
        stage('Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/TEST-*.xml'
                }
            }
        }
    }
    
    post {
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
            emailext to: 'team@mycompany.com', subject: 'Build Failed: ${JOB_NAME} #${BUILD_NUMBER}'
        }
        always {
            cleanWs()  // 清理工作空间
        }
    }
}

agent 指令详解

复制代码
pipeline {
    // 方式1:任意节点
    agent any
    
    // 方式2:指定标签的节点
    agent { label 'docker-build' }
    
    // 方式3:指定 Docker 镜像(不需要预装工具)
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-17'
            args '-v $HOME/.m2:/root/.m2 -v $HOME/.gradle:/root/.gradle'
            label 'docker-build'
        }
    }
    
    // 方式4:Kubernetes Pod(需要 K8s 插件)
    agent {
        kubernetes {
            label 'mypod'
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.9-eclipse-temurin-17
    command: ['cat']
    volumeMounts:
    - mountPath: /root/.m2
      name: maven-cache
  volumes:
  - name: maven-cache
    persistentVolumeClaim:
      claimName: maven-cache-pvc
'''
        }
    }
}

**培训要点**:用 Docker agent 是最佳实践------不需要在 Agent 上预装 Maven/Gradle 等工具,直接在 Pipeline 中指定镜像版本,保证构建环境的确定性。

environment 指令

复制代码
pipeline {
    environment {
        // 静态值
        APP_NAME = 'myapp'
        
        // 引用构建号
        BUILD_TAG = "build-${BUILD_NUMBER}"
        
        // 引用凭据(敏感信息)
        DB_PASSWORD = credentials('db-password')  // 会生成 DB_PASSWORD 和 DB_PASSWORD_USR 变量
        
        // 条件赋值
        DEPLOY_ENV = "${env.BRANCH_NAME == 'main' ? 'prod' : 'dev'}"
    }
    
    stages {
        stage('Config') {
            // 局部环境变量,只在此 stage 生效
            environment {
                LOCAL_VAR = 'stage-specific'
            }
            steps {
                echo "APP_NAME=${APP_NAME}"
                echo "DEPLOY_ENV=${DEPLOY_ENV}"
                // 使用凭据时用 $ 而非 ${} 在 sh 中
                sh 'echo $DB_PASSWORD | wc -c'
            }
        }
    }
}

when 指令:条件执行

复制代码
stages {
    stage('Deploy Prod') {
        when {
            branch 'main'           // 只在 main 分支执行
            // expression { env.BRANCH_NAME == 'main' }  // 等价写法
        }
        steps {
            sh './deploy.sh prod'
        }
    }
    
    stage('Deploy Dev') {
        when {
            branch 'dev'
        }
        steps {
            sh './deploy.sh dev'
        }
    }
    
    stage('Nightly Build') {
        when {
            triggeredBy 'TimerTrigger'  // 只在定时触发时执行
        }
        steps {
            sh './run-full-test-suite.sh'
        }
    }
    
    stage('Skip on PR') {
        when {
            not { changeRequest() }  // 非 PR 时执行
        }
        steps {
            sh './heavy-test.sh'
        }
    }
}

parallel:并行执行

复制代码
stage('Test') {
    parallel {
        stage('Unit Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Integration Test') {
            steps {
                sh 'mvn verify -Pintegration'
            }
        }
        stage('Code Scan') {
            steps {
                sh 'sonar-scanner'
            }
        }
    }
}

**踩坑提示**:`parallel` 中的每个 stage 默认在同一个 agent 上执行。如果需要在不同 agent 上并行,需要在每个 stage 内部指定 `agent`。

script 块:在 Declarative 中嵌入 Groovy

复制代码
stage('Dynamic Deploy') {
    steps {
        script {
            // 在 Declarative 中使用 Groovy 逻辑
            def services = ['user-service', 'order-service', 'payment-service']
            for (service in services) {
                echo "Deploying ${service}..."
                sh "kubectl set image deployment/${service} app=myapp:${BUILD_NUMBER} -n prod"
            }
        }
    }
}

三、Scripted Pipeline 语法详解

基本结构

复制代码
// Scripted Pipeline 没有外层 pipeline {} 块
// 直接写 Groovy 脚本

node('docker-build') {
    // 定义环境变量
    def appName = 'myapp'
    def version = env.BUILD_NUMBER
    
    try {
        stage('Checkout') {
            checkout scm
        }
        
        stage('Build') {
            sh 'mvn clean package -DskipTests'
            // 归档构建产物
            archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
        }
        
        stage('Test') {
            sh 'mvn test'
            step([$class: 'JUnitResultArchiver', testResults: '**/surefire-reports/TEST-*.xml'])
        }
        
        stage('Deploy') {
            if (env.BRANCH_NAME == 'main') {
                sh "./deploy.sh prod ${version}"
            } else {
                sh "./deploy.sh dev ${version}"
            }
        }
        
    } catch (Exception e) {
        // 错误处理
        emailext to: 'team@mycompany.com',
            subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: "Check: ${env.BUILD_URL}"
        throw e  // 重新抛出,让 Jenkins 标记为失败
    } finally {
        // 清理
        cleanWs()
    }
}

Scripted 中的条件与循环

复制代码
node('docker-build') {
    stage('Multi-Service Deploy') {
        def services = [
            [name: 'user-service', replicas: 3],
            [name: 'order-service', replicas: 2],
            [name: 'payment-service', replicas: 2]
        ]
        
        for (svc in services) {
            echo "Deploying ${svc.name} with ${svc.replicas} replicas"
            sh "kubectl set image deployment/${svc.name} app=myapp:${env.BUILD_NUMBER} -n prod"
            sh "kubectl scale deployment/${svc.name} --replicas=${svc.replicas} -n prod"
            sh "kubectl rollout status deployment/${svc.name} -n prod --timeout=120s"
        }
    }
}

并行构建(Scripted 方式)

复制代码
def branches = [:]
branches['unit-test'] = {
    node('java-build') {
        stage('Unit Test') {
            sh 'mvn test'
        }
    }
}
branches['integration-test'] = {
    node('java-build') {
        stage('Integration Test') {
            sh 'mvn verify -Pintegration'
        }
    }
}
branches['code-scan'] = {
    node('java-build') {
        stage('Code Scan') {
            sh 'sonar-scanner'
        }
    }
}

parallel branches  // 并行执行所有分支

四、实战对比:同一个需求的两种写法

需求

根据分支决定部署环境:main→生产,develop→测试,其他→不部署。

Declarative 写法

复制代码
pipeline {
    agent any
    stages {
        stage('Build') { steps { sh 'mvn clean package' } }
        
        stage('Deploy') {
            stages {
                stage('Deploy Prod') {
                    when { branch 'main' }
                    steps { sh './deploy.sh prod' }
                }
                stage('Deploy Test') {
                    when { branch 'develop' }
                    steps { sh './deploy.sh test' }
                }
            }
        }
    }
}

Scripted 写法

复制代码
node {
    stage('Build') {
        sh 'mvn clean package'
    }
    stage('Deploy') {
        if (env.BRANCH_NAME == 'main') {
            sh './deploy.sh prod'
        } else if (env.BRANCH_NAME == 'develop') {
            sh './deploy.sh test'
        } else {
            echo "Branch ${env.BRANCH_NAME} does not trigger deployment"
        }
    }
}

**培训要点**:Declarative 的 `when` 让流水线结构更清晰------即使不部署的 stage 也会显示在流水线视图上(标记为 skipped)。Scripted 的 `if/else` 更灵活但不够直观。


五、常用内置变量速查

| 变量 | 说明 | 示例值 |

|------|------|--------|

| env.JOB_NAME | Job 名称 | myapp-pipeline |

| env.BUILD_NUMBER | 构建编号 | 42 |

| env.BUILD_ID | 构建 ID | 2024-01-15_10-30-00 |

| env.BUILD_URL | 构建 URL | http://jenkins/job/myapp/42/ |

| env.WORKSPACE | 工作空间路径 | /home/jenkins/workspace/myapp |

| env.NODE_NAME | 执行节点名 | agent-docker |

| env.JENKINS_URL | Jenkins URL | http://jenkins:8080 |

| env.BRANCH_NAME | 分支名 | main |

| env.GIT_COMMIT | Git commit hash | a1b2c3d4 |

| env.GIT_URL | Git 仓库 URL | https://github.com/org/repo |


六、本篇要点回顾

  1. 优先用 Declarative,复杂逻辑用 script {} 块嵌入 Groovy

  2. agent 指令推荐用 Docker 镜像方式,保证构建环境确定性

  3. when 指令控制条件执行,替代 if/else 更结构化

  4. parallel 实现并行执行,大幅缩短流水线时间

  5. credentials() 函数安全引用密钥,不要在代码中硬编码

  6. post 块处理流水线结果通知

下一篇预告:《Jenkinsfile 最佳实践:多分支流水线与共享库》------从语法层面进入工程层面,学习如何管理多个项目的 Jenkinsfile 和复用流水线代码。

相关推荐
测试者家园1 天前
Harness由浅入深:CI/CD流水线里的质量门禁怎么设计
软件测试·人工智能·ci/cd·混沌测试·智能化测试·harness·质量门禁
超爱西西鸭1 天前
鸿蒙企业级CI/CD高级工程化搭建:自动化构建流水线/多环境配置/自动化测试集成/签名打包发布
android·学习·ci/cd·华为·自动化·harmonyos·鸿蒙
遨游DATA1 天前
Jenkins 修改 config.xml 后页面仍是旧配置:让 live job 配置真正生效
ci/cd·jenkins·devops·故障排查·config.xml
ZeroNews内网穿透1 天前
无公网 IP 打通 CI/CD:内网穿透对接 GitHub Actions 自动部署内网服务
运维·tcp/ip·ci/cd·github·远程工作·内网穿透
Dobby_051 天前
【CICD】Jenkins Pipeline 快速学习入门
运维·学习·jenkins
上海安当技术2 天前
统一身份认证平台怎么落地?11 个异构业务系统接入 ASP 的完整实施路径
数据库·servlet·架构·kubernetes·jenkins
heimeiyingwang2 天前
【CI/CD·入门篇】CI vs CD vs Continuous Deployment:三个层次的区别与联系
ci/cd
回眸不遇3 天前
Elasticsearch嵌套类型nested使用指南
大数据·elasticsearch·jenkins
tianyuanwo3 天前
当提交标题重复阻塞CI:Git分支修复实战
git·ci/cd·分支基线调整