声明式Pipeline语法:核心五要素

声明式Pipeline,说白了就是一套结构化的流水线写法。我刚开始学的时候,觉得它比脚本式啰嗦,但后来发现——嗯,这玩意儿才是团队协作的基石。你想想看,代码写清楚了结构,别人一看就懂,维护起来省心多了。

这一节咱们拆开看看:pipeline块、agent、stages、steps、post。这五个东西,几乎能搞定你90%的持续集成场景。

1. pipeline块:一切从这里开始

pipeline块是声明式流水线的根。任何东西都要包在它里面。说白了,文件的最外层必须是pipeline,不能多也不能少。

pipeline {
    // 剩下的都写在这里
}

⚠️ 注意:pipeline块外面不能有别的代码块,比如node、stage。我见过有人把pipeline包在node里面,结果Jenkins直接报错。声明式就是声明式,别混用。

我在项目中遇到过一个问题:有些同事喜欢把变量定义放在pipeline外面,认为“这样全局能用”。其实不对,声明式Pipeline的变量最好用environment块或者script块里定义,否则容易出作用域问题。

2. agent:告诉Jenkins去哪儿跑

agent指定流水线在哪个节点上执行。常用的有这些:

类型 写法 说明
any agent any 任何可用的节点
label agent { label 'linux' } 匹配标签的节点
docker agent { docker 'maven:3.8' } 在容器里跑,省环境配置
none agent none 全局不分配,stage单独指定

我个人习惯在顶层用agent none,然后在每个stage里单独指定agent。这样不同的阶段可以用不同的环境,比如编译用JDK镜像,部署用kubectl镜像。不过,有一个坑:如果你在某个stage里忘了写agent,那这个stage就没地方运行,流水线会卡住。

💡 我建议:如果所有stage都在同一个节点上,直接写agent any或者agent { label 'master' }就行,省事。

曾经有一个项目,我用了agent docker,但镜像太大,每次拉取要5分钟。后来我发现可以用dockerfile参数直接构建自己的镜像,但要注意缓存问题。嗯,这又是另一个故事了。

3. stages & stage:流水线的骨架

stages块里包含多个stage,每个stage代表一个逻辑阶段。比如:编译、单元测试、打包、部署。每个stage里必须有一个steps。

stages {
    stage('编译') {
        steps {
            echo '开始编译...'
            sh 'mvn compile'
        }
    }
    stage('测试') {
        steps {
            sh 'mvn test'
        }
    }
}

这里要注意几点:

  • stage名字要有意义:别用stage1、stage2,蓝海里看到全是英文名,运维会骂人的。
  • 顺序执行:默认stage按书写顺序一个个跑,除非你用parallel做并行。
  • 可以嵌套:stage里还可以用parallel或者matrix,但别太深,不然可读性下降。

我见过有人把几十个步骤写在一个stage里,结果失败时根本看不清是哪一步出的问题。所以,尽量按职责拆分stage,每个stage只做一件事。

4. steps:具体干活的地方

steps就是stage里执行的具体操作。每个stage必须有且只能有一个steps块(里面可以有多条指令)。

常用的steps命令:

  • sh:执行Shell命令
  • echo:打印信息
  • checkout scm:拉取代码
  • script:嵌入脚本式代码(必要时才用)
  • sleep:等待

⚠️ 别把steps写成复数!我一直看到有人写成steps { ... }但内部写成了step { ... },少了一个s,语法错误。

我记得有一次排查问题,发现某个stage的steps里写了sh 'npm install',但没给node镜像。结果agent用的是jdk镜像,根本没有npm。所以steps的执行环境取决于agent。

5. post:无论成败,都得收拾

post块用于定义流水线或stage执行后的条件处理。它必须放在stages之后(如果是stage级别,则放在steps之后)。

post {
    always {
        echo '无论结果如何,我都会执行'
    }
    success {
        echo '成功了!'
    }
    failure {
        echo '失败了,赶紧查日志'
    }
    unstable {
        echo '不太稳定,但没完全失败'
    }
    changed {
        echo '状态变了,比如从失败变成成功'
    }
}

我个人习惯在post的always里加一个清理步骤,比如删除临时文件,或者发送通知。工作中经常用failure发邮件给开发,success发给QA。

💡 小技巧:post可以放在pipeline顶层(全局),也可以放在stage内部(局部)。全局post会在整个流水线结束后运行,局部post只在那个stage结束后运行。

曾经有一个坑:我在stage里用了post的failure,想发钉钉通知,但忘了在全局agent none的情况下,stage的post也会继承agent。结果那个stage的agent是none,post根本没地方跑。所以,如果你在stage里用post,最好显式指定agent。

知识点总结

为了方便回顾,我把这五要素整理成一个速查表:

要素 用途 常见坑
pipeline 根容器 别在外部包别的块
agent 指定运行节点 忘记写agent导致无节点
stages/stage 流程阶段 stage内必须有steps
steps 具体操作 单数写法错误
post 结束后的条件处理 局部post需考虑agent

整体结构图

下面这张图可以帮你理解声明式Pipeline的层级关系:

声明式Pipeline核心结构 pipeline agent stages post stage('编译') stage('测试') stage('部署') steps { sh 'mvn compile' } always success / failure unstable / changed 说明: pipeline 根块包含 agent、stages、post 三个直接子元素。 stages 内可包含多个 stage,每个 stage 必须有 steps 块。 post 支持多个条件(always、success、failure 等),写在 stages 之后。