Jenkins Pipeline 脚本踩坑记:我是如何被两种语法折磨并最终选择的
最近在折腾公司的 CI/CD 流水线,想把原来那套老掉牙的构建脚本升级一下。本以为 Jenkins Pipeline 挺简单的,结果一上手就懵了——竟然有两种写法!这不是逼死选择困难症吗?
我当时的内心OS:这玩意儿就像去饭店点菜,服务员问你要清汤还是麻辣的,你以为选完就完事了,结果又问你要不要加香菜、要不要加醋... 选择太多反而不知道怎么选了。
Pipeline 的两种"口味"
Jenkins Pipeline 主要有两种写法:Declarative Pipeline(声明式流水线)和 Scripted Pipeline(脚本式流水线)。
Declarative Pipeline 就像是傻瓜相机,你只需要按快门就行,其他的都帮你调好了。它用的是一种 DSL(Domain Specific Language),说白了就是专门为 Pipeline 设计的语言。整个 Pipeline 必须包在 pipeline 块里面,然后用 stages 和 steps 来组织你的构建步骤。
而 Scripted Pipeline 呢,更像是单反相机,功能强大但你得自己调参数。它直接用的是 Groovy 语言,灵活性爆表,但也意味着你得有点 Groovy 基础。整个脚本是在 node 块里执行的[4]。
我的踩坑之旅
第一坑:语法差异
刚开始我用的是 Declarative Pipeline,写起来确实简单:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}看着挺清爽的对吧?结构一目了然。
后来有个需求,需要在构建过程中动态判断一些条件,我就想着用 if-else。结果发现 Declarative Pipeline 里不能直接写 if 语句!你得用 when 指令:
stage('Deploy') {
when {
branch 'master'
}
steps {
echo 'Deploying to production...'
}
}这时候我就想,要不试试 Scripted Pipeline?切换过去一看:
node {
stage('Build') {
echo 'Building...'
if (env.BRANCH_NAME == 'master') {
echo 'This is master branch'
}
}
}嘿,这下自由了!想怎么写就怎么写。但问题又来了...
第二坑:错误处理
Declarative Pipeline 的错误处理特别规范,有专门的 post 块:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'make test'
}
}
}
post {
always {
echo 'This will always run'
}
success {
echo 'Only on success'
}
failure {
echo 'Only on failure'
mail to: 'team@example.com',
subject: 'Build Failed'
}
}
}但在 Scripted Pipeline 里,你得自己写 try-catch:
node {
try {
stage('Test') {
sh 'make test'
}
echo 'Success!'
} catch (e) {
echo 'Failed!'
throw e
} finally {
echo 'Cleanup'
}
}说实话,写多了 try-catch 真的很烦。有一次我忘了写 finally 块,结果构建失败后一些临时文件没清理,磁盘差点爆了。
第三坑:并行执行
这个是我觉得两种 Pipeline 差异最大的地方。
Declarative Pipeline 的并行执行写法很直观:
pipeline {
agent any
stages {
stage('Parallel Stage') {
parallel {
stage('Unit Tests') {
steps {
echo 'Running unit tests...'
}
}
stage('Integration Tests') {
steps {
echo 'Running integration tests...'
}
}
}
}
}
}而 Scripted Pipeline 的写法... 说实话第一次看到我是懵的:
node {
stage('Parallel Stage') {
parallel(
"Unit Tests": {
echo 'Running unit tests...'
},
"Integration Tests": {
echo 'Running integration tests...'
}
)
}
}这种 Map 的写法,不知道为啥总让我想起 JavaScript 的对象字面量。
实战经验分享
经过这么多坑,我总结了一些实战经验。
如果你的项目比较简单,构建流程相对固定,强烈建议用 Declarative Pipeline。为啥?因为它:
- 语法检查更严格,写错了立马报错
- 结构清晰,新人上手快
- 自带很多实用功能,比如
options、parameters、triggers等[3]
我们有个前端项目就是用的 Declarative:
pipeline {
agent {
docker {
image 'node:14-alpine'
}
}
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
choice(name: 'ENVIRONMENT', choices: ['dev', 'test', 'prod'], description: 'Deploy environment')
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Build') {
steps {
sh "npm run build:${params.ENVIRONMENT}"
}
}
stage('Deploy') {
when {
expression { params.ENVIRONMENT == 'prod' }
}
steps {
input message: 'Deploy to production?'
sh './deploy.sh'
}
}
}
}但如果你的构建逻辑很复杂,需要大量的条件判断、循环处理,或者要调用很多 Groovy 的高级特性,那 Scripted Pipeline 可能更合适。
我们有个数据处理的项目,需要根据不同的数据源动态生成构建步骤:
node {
def dataSources = ['mysql', 'postgres', 'mongodb']
def stages = [:]
dataSources.each { source ->
stages["Process ${source}"] = {
stage("Process ${source}") {
echo "Processing ${source} data..."
def config = readJSON file: "${source}.json"
config.tables.each { table ->
sh "python process.py --source ${source} --table ${table}"
}
}
}
}
parallel stages
}这种动态生成 stage 的需求,用 Declarative 就很难实现了。
性能和维护性对比
从性能角度看,两者其实差不多。Jenkins 最终都会把它们转换成同样的执行逻辑。
但从维护性来说,Declarative Pipeline 胜出太多了。我们团队之前有个项目用的 Scripted Pipeline,写得那叫一个自由奔放。后来换了个人维护,看着那些嵌套的闭包和各种 Groovy 黑魔法,直接崩溃了。最后花了一周时间重写成 Declarative 的。
我的最终选择
折腾了这么久,我的选择是:能用 Declarative 就用 Declarative,实在不行再用 Scripted。
为啥?因为大部分场景下,Declarative Pipeline 真的够用了。而且 Jenkins 官方也推荐优先使用 Declarative。它的限制反而是优点,能让团队的 Pipeline 风格保持一致。
当然,如果你遇到这些情况,可能真的需要 Scripted Pipeline:
- 需要复杂的流程控制逻辑
- 要动态生成大量的 stages
- 需要使用 Groovy 的高级特性
- 要集成一些特殊的 Jenkins 插件
一些实用技巧
不管用哪种 Pipeline,这些技巧都很有用:
- 使用共享库:把通用的逻辑抽取到共享库里,避免重复代码
- 参数化构建:灵活使用 parameters,让 Pipeline 更通用
- 合理使用 agent:不同的 stage 可以用不同的 agent,提高资源利用率
- 做好错误处理:该 catch 的要 catch,该重试的要重试
- 日志要清晰:多用 echo 输出关键信息,方便排查问题
总结
Jenkins Pipeline 的两种写法各有千秋。Declarative Pipeline 像是给你画好了框,你只需要往里面填东西;Scripted Pipeline 则是给你一张白纸,你想怎么画就怎么画。
我的建议是,如果你刚接触 Jenkins Pipeline,从 Declarative 开始。等你熟悉了,再根据实际需求决定是否需要 Scripted 的灵活性。记住,工具是为了解决问题的,不要为了炫技而选择复杂的方案。
最后,不管选择哪种,记得多写注释!相信我,三个月后的你会感谢现在的自己的。
如果这篇文章对你有帮助,欢迎关注 @运维躬行录,我会持续分享更多运维实战经验。遇到 Pipeline 的坑也欢迎留言交流,说不定我也踩过呢!
公众号:运维躬行录
个人博客:躬行笔记