3、声明式Pipeline语法:pipeline块、agent、stages、steps
这一章我们聊聊声明式Pipeline的四个核心语法块。说白了,它们构成了流水线的骨架和血肉。我个人习惯在写新流水线时,先搭好pipeline块,再配置agent,然后划分stages,最后填充steps。顺序对了,后面就不容易乱。
pipeline块——整个流水线的根节点
所有声明式Pipeline都必须从pipeline { }开始。它就像房子的地基,其他指令都放在这个大括号里。嗯,这里要注意:pipeline块是根元素,外面不能再套别的东西。
一个最简示例是这样的:
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
}
}
我在项目中遇到过有人把pipeline写成了pipeline首字母大写,结果Jenkins直接报语法错误。声明式关键字严格区分大小写,全小写才认。
agent——任务在哪里执行
说白了,agent就是告诉Jenkins这个任务在哪个节点上跑。它直接影响流水线的并发和资源分配。
常用的agent配置方式有这么几种:
| 写法 | 含义 | 使用场景 |
|---|---|---|
agent any |
任意可用节点 | 快速验证、demo |
agent none |
不分配全局agent | 各stage自行指定 |
agent { label 'linux' } |
匹配标签为linux的节点 | 区分操作系统 |
agent { docker 'maven:3.8' } |
在Docker容器中执行 | 环境隔离、版本统一 |
agent none用错了地方,导致某个stage找不到节点执行,CI一直卡在队列里。后来养成了习惯——用agent none时一定在每个stage里都显式指定agent。
我个人比较推荐在全局用agent none,然后每个stage按需指定。这样灵活度最高,也最省资源。你想想看,如果所有stage都跑在同一台机器上,那编译和部署岂不是互相干扰?
stages和steps——流水线的骨架与肉
stages包含一组stage,每个stage里都有一组steps。stages负责分阶段,steps负责执行具体操作。
为什么要把流水线分成多个阶段?我有个真实的教训:早期我图省事把所有命令塞到一个stage里,结果测试失败时根本不知道是编译阶段出的问题还是包测试出的问题。后来老老实实拆成三个stage,问题定位快了一半。
看看一个三段式流水线的典型写法:
pipeline {
agent none
stages {
stage('编译') {
agent { label 'builder' }
steps {
sh 'mvn compile'
sh 'mvn package -DskipTests'
}
}
stage('测试') {
agent { docker 'maven:3.8' }
steps {
sh 'mvn test'
junit 'target/surefire-reports/*.xml'
}
}
stage('部署') {
agent { label 'deployer' }
steps {
sh 'scp target/*.jar deploy@server:/app/'
sh 'ssh deploy@server "systemctl restart myapp"'
}
}
}
}
每个stage只做一件事。编译阶段只编译,测试阶段只跑测试,部署阶段只发版。这样做的好处太多了:
- 出了问题一眼就能锁定阶段
- 不同阶段可以用不同的agent资源
- 可以给每个stage设置独立的超时和重试策略
我见过很多人把几十个步骤堆在一个stage里,结果日志动辄几千行,找一条错误信息要翻半天。其实只要把逻辑拆到不同的stage里,再给每个stage起个好读的名字(比如“代码检查”、“单元测试”、“集成测试”),整个流水线就像一本书一样好读。
嗯,这里要注意:steps里面可以放多个步骤,但原则上不要超过5个。如果某个stage的steps太多,说明这个stage拆得不够细。我曾经重构过一个遗留流水线,把一个有15个steps的stage拆成了3个,可维护性瞬间上了一个台阶。
最后说一句:声明式Pipeline的优势在于结构化,它的语法虽然比脚本式多几行代码,但换来了清晰和可维护。你如果刚开始接触Pipeline,我建议从声明式入手,先把这四个块用熟,后面学高级特性会轻松很多。