装完 Jenkins 打开首页,很多人第一反应是懵的:左侧"新建任务"点进去,一堆类型摆在那——自由风格、流水线、多配置、多分支、文件夹,名字看着都认识,合起来就不知道选哪个。更麻烦的是,任务建好之后跑一次失败一次,控制台日志翻半天也看不出到底是拉代码挂了还是脚本写错了。这篇就把 Jenkins 的"任务"从概念到落地拆开讲一遍,偏实战,不堆概念,适合刚接手 CI 的运维、后端,以及被"自动化部署"这个词吸引过来但还没真正跑通第一套流程的人。
我把任务理解成流水线上的一个工位:它知道原料从哪来(源码)、什么时候开工(触发器)、用哪些工具加工(构建环境与步骤)、成品放哪(归档)、出问题怎么报警(构建后操作)。把这五件事理顺,剩下的都是配置项填空。
1. Jenkins 任务不是"点一下就跑",先搞懂它的五种形态
新手最容易犯的错,是拿自由风格项目去硬干流水线该干的活,或者反过来。类型选错了,后面每一步都别扭。所以先把五种任务的边界讲清楚,再谈怎么建。
1.1 自由风格项目为什么至今还在用
自由风格(Freestyle Project)是 Jenkins 最老、也最"所见即所得"的任务类型。整个配置界面就是一堆勾选框:源码管理、构建触发器、构建环境、构建步骤、构建后操作,从上到下填一遍就完事。它的优势是零学习成本,一个只会写 Shell 的人十分钟就能把"拉代码 + 打包 + 拷贝到服务器"这条链路跑通。
但它的短板也很明显。所有逻辑都固化在界面的输入框里,想改就得点进页面一项项改;换一个环境(比如从测试切到生产)要复制一份任务再手改,任务一多就变成"复制粘贴地狱";版本控制更是无从谈起,谁什么时候改了哪个参数,界面本身不给你留痕迹。
所以自由风格不是"过时",而是有明确的适用区间:单条链路、结构简单、改动不频繁的任务,比如"每天凌晨清理一次临时目录""定时拉取某个数据接口落库"。这类任务本就没有复杂的分支判断,用流水线反而杀鸡用牛刀。
1.2 流水线任务:把构建逻辑写成代码
流水线(Pipeline)是把整个构建过程写成一份 Groovy 脚本,通常放在项目根目录的Jenkinsfile里,跟业务代码一起进版本库。这一点是它和自由风格最本质的差别——构建逻辑变成了可评审、可回滚、可复用的代码,而不是散落在界面上的配置。
Jenkinsfile 有两种写法:声明式(Declarative)和脚本式(Scripted)。新手建议从声明式入手,结构规整,不容易写出失控的脚本:
pipeline { agent any stages { stage('拉取代码') { steps { checkout scm } } stage('编译打包') { steps { sh 'mvn clean package -DskipTests' } } stage('归档制品') { steps { archiveArtifacts artifacts: 'target/*.jar', fingerprint: true } } } post { failure { echo "构建失败了,编号 ${env.BUILD_NUMBER}" } } }这段脚本的价值不在语法,而在可复现:换台 Jenkins 只要指向同一个仓库,行为完全一致。声明式的stages强制你把流程切成阶段,post块则把"成功做什么、失败做什么、永远做什么"统一收口,比自由风格里东一个"构建后操作"、西一个"邮件通知"要清晰得多。
选型建议很直接:凡是需要分支逻辑(比如不同分支走不同部署策略)、需要多人协作维护、需要回滚构建逻辑的,一律上流水线。反过来说,如果一条链路永远不变,用流水线就是给自己加学习成本。
1.3 多配置、多分支与文件夹任务分别解决什么
剩下三种类型各自解决一个特定问题,混用会很难受。
多配置项目(Matrix Project)解决的是"同一套流程跑多组参数"。比如同一份代码要在 JDK 8、11、17 三个版本上分别编译测试。它允许你定义多个"轴"(axis),比如 jdk 轴取值 8/11/17,os 轴取值 linux/windows,Jenkins 会自动做笛卡尔积,生成 6 个组合并行跑。手写 6 个任务再手动维护,成本高到没人愿意干。
多分支流水线(Multibranch Pipeline)解决的是"每个分支自动有自己的构建任务"。它扫描指定仓库,发现一个分支就自动建一个子任务,分支删了这个任务也跟着消失。配合Jenkinsfile里的分支判断,开发分支只跑测试、主分支才做部署,是这样落地的:
stage('部署') { when { branch 'main' } steps { sh './deploy.sh' } }文件夹(Folder)不是构建任务,是组织容器。它把任务按业务线、环境或团队分组,还能对文件夹整体做权限控制。任务上百之后,没有文件夹的 Jenkins 首页基本没法看。
| 任务类型 | 最擅长的事 | 典型误用 |
|---|---|---|
| 自由风格 | 单条简单链路 | 拿它维护十几套环境 |
| 流水线 | 复杂逻辑、需版本化 | 简单定时任务强行上 |
| 多配置 | 多参数组合矩阵 | 参数其实是固定两套 |
| 多分支 | 分支自动建任务 | 仓库分支成百上千 |
| 文件夹 | 分组与权限隔离 | 当成构建任务使用 |
把这张表对着自己的场景过一遍,选型基本不会错。
2. 一个任务从源码到制品,中间要经过哪几道关
任务类型定下来,接下来是配置。不管是哪种类型,主链路都逃不出四关:拉代码、定触发、跑构建、收尾处理。这一节按执行顺序拆。
2.1 源码管理:Git 拉取失败的三个高频原因
源码管理(Source Code Management,SCM)里最常见的就是 Git。填仓库地址、选凭据、指定分支,看起来比写代码简单,但实际报错里一大半都出在这。
第一类问题是凭据类型选错。Jenkins 的凭据分 Username/Password、SSH Username with private key、Secret text 等。用 SSH 地址(git@...)却配了账密凭据,或者用 HTTPS 地址却配了 SSH 私钥,都会认证失败。判断方法很简单:地址前缀是git@就用 SSH 私钥,是https://就用账密或 Token。报错通常长这样:Permission denied (publickey),看到这个别急着改代码,先看凭据。
第二类问题是known_hosts 校验。Jenkins 机器第一次连某台 Git 服务器时会问"是否信任这台主机",但构建过程是非交互的,没人能回答 yes,于是直接失败。解决办法是在"源码管理"里选对应凭据,或者在 Jenkins 所在机器上用ssh-keyscan提前把主机指纹写进 known_hosts。这一步在离线内网环境尤其关键,因为内网机器上阵前根本没连过 Git 服务器。
第三类问题是分支名写死。很多人填master,但仓库其实叫main,或者想构建任意分支却在"Branch Specifier"里写死了具体名字。这里推荐用*/分支名这样的通配形式,或者干脆交给参数化构建(后面第三节细讲)来动态传。
提示:配置完源码管理后,先在任务页面点一次"立即构建"看日志里 checkout 那一段,确认能把代码拉到
$WORKSPACE目录,再往下配构建步骤。很多"构建失败"其实是代码压根没拉下来。
2.2 触发器:轮询、Webhook 和上游触发怎么选
触发器决定任务什么时候自动开跑,三种方式各有取舍。
轮询 SCM(Poll SCM)让 Jenkins 定时去问 Git 服务器"代码变了没"。它填的是类似 cron 的表达式,比如H/5 * * * *表示每 5 分钟查一次。H是 Jenkins 特有的"散列"写法,用来把整点任务打散,避免所有任务都在 00:00 同时启动把机器压垮。写法对照如下:
| 表达式 | 含义 |
|---|---|
H/5 * * * * | 每 5 分钟检查一次 |
H 2 * * * | 每天凌晨 2 点左右检查 |
H H(0-6) * * 1-5 | 工作日 0 到 6 点之间某时刻检查 |
轮询的缺点是延迟和浪费:代码提交后要等到下一个轮询周期才触发,而且代码没变时也在白跑查询请求。
Webhook 触发是 Git 服务器在收到推送时主动通知 Jenkins,实时且零浪费。GitLab 侧配置 webhook 指向 Jenkins 地址,Jenkins 侧勾选"Build when a change is pushed to GitLab",两边对上就通了。它比轮询好,但依赖网络通、依赖 Git 服务器能访问到 Jenkins,内网隔离环境下经常行不通。
上游任务触发是任务之间串联:A 构建成功后自动触发 B。这在"先编译公共库再构建业务应用"的场景里非常常见,配置在"构建后操作"里选"构建其他工程"即可。
现实中的组合拳通常是:核心仓库用 Webhook 保证实时,网络受限的用轮询兜底,有依赖关系的用上游触发。三者不冲突,可以同时挂在一个任务上。
2.3 构建环境与构建步骤:脚本写在哪、怎么跑
构建环境是给这次构建准备"场地"的,比如设置超时时间、注入环境变量、准备构建节点。比较实用的两项是"构建超时"和"使用自定义工作空间"。
构建超时报错卡死是运维的噩梦。很多卡死在拉取依赖或等待某个服务上,不设超时,任务能挂一整天占着执行器不放。建议给每个任务设一个合理上限,比如30分钟,超了直接失败并告警。
构建步骤(Build Steps)是干活的地方。自由风格里可以选"执行 Shell"或"执行 Windows 批处理命令",流水线里就是sh和bat。脚本写在这里有几个常年被忽视的要点:
#!/bin/bash # 关键:任何一步出错就退出,避免错误被吞掉 set -e echo "当前构建编号: $BUILD_NUMBER" echo "工作目录: $WORKSPACE" # 清理旧产物,避免残留文件混进新构建 rm -rf target/ echo "开始打包..." mvn clean package -DskipTestsset -e这一行价值极高。默认情况下 Shell 脚本是"最后一条命令的退出码决定整体成败",中间某一步失败了它还会继续往下跑,最后可能得到一个"构建成功"的假象,产物其实是坏的。加上set -e,任何一步非零退出立刻中断,日志里一眼就能定位。
另外,$WORKSPACE是当前任务独占的工作目录。别在不同任务里通过写死绝对路径去共享文件,那是并发冲突的头号来源,后面第五节还会展开。
2.4 构建后操作:归档、报告与失败通知
构建跑完不算结束,产物要收、报告要看、失败要通知,"构建后操作"就是干这个的。
归档制品(Archive the artifacts)把打包出来的 jar、war、zip 存进 Jenkins,构建历史里可以直接下载。它解决的是"我上个月那版制品去哪找"的问题。归档路径用相对$WORKSPACE的模式,比如target/*.jar。
发布 JUnit 测试报告做单元测试的团队一定要开。它把target/surefire-reports/*.xml解析成趋势图,哪次构建开始出现测试用例变红,一目了然。没这个,测试结果只能靠翻日志数"Tests run: 120, Failures: 2"。
失败通知通常是发邮件或发钉钉。这块内容比较独立,放到第四节专门讲怎么把消息拼得清楚不啰嗦。
这里有个常被忽略的细节:归档要趁早。如果构建后面还有一步部署,部署过程中清理了产物目录,再归档就会报"没有匹配的文件"。归档路径的匹配要在部署步骤之前完成。
3. 环境变量与参数化:让任务从死板变得可配置
如果说任务是流水线工位,环境变量就是工位之间传话的通道。它让脚本能感知"我在被谁跑、跑第几次、跑的是哪个分支",也让同一份脚本能适配不同环境。
3.1 Jenkins 自带的环境变量清单与取用姿势
Jenkins 在每次构建时会自动注入一批环境变量,常用的有这么几个:
| 变量名 | 含义 | 典型用途 |
|---|---|---|
BUILD_NUMBER | 当前构建序号 | 制品命名、日志标记 |
BUILD_ID | 构建时间戳形式的 ID | 需要按时间排序时用 |
JOB_NAME | 任务名称 | 通知消息里标明来源 |
WORKSPACE | 工作目录绝对路径 | 脚本内路径拼接 |
BUILD_URL | 本次构建的页面地址 | 通知里附直达链接 |
GIT_COMMIT | 本次构建对应的提交哈希 | 制品打标、追溯 |
GIT_BRANCH | 构建的分支 | 按分支走不同逻辑 |
取用方式分两套。Shell 脚本里直接用$BUILD_NUMBER;流水线 Groovy 里用env.BUILD_NUMBER。写流水线时混用是最常见的低级错误——在 Groovy 的字符串里写$BUILD_NUMBER不会被 Jenkins 替换,得写成env.BUILD_NUMBER或者"${BUILD_NUMBER}"(双引号才会展开)。
这些变量里我最常用的是BUILD_URL。任何一条通知消息,末尾附上这个链接,收到消息的人点一下就能跳到日志页,比在群里问"哪次构建失败了"效率高太多。
3.2 自定义环境变量的作用域坑
除了自带的,Jenkins 允许你定义自己的环境变量,位置在"构建环境"里勾选"注入环境变量"或"设置环境变量",流水线里则是environment块:
pipeline { agent any environment { APP_NAME = 'user-service' DEPLOY_ENV = 'staging' } stages { stage('构建') { environment { LOCAL_ONLY = 'just-here' } steps { sh 'echo $APP_NAME $DEPLOY_ENV $LOCAL_ONLY' } } } }这里有个作用域陷阱:顶层environment块里的变量对所有 stage 可见,stage 内定义的只对当前 stage 可见。曾经踩过的坑是把编译参数定义在最后一个 stage 里,前面几个 stage 引用的时候拿到的却是空值,排查半天才发现是作用域问题。
另一个坑是变量覆盖顺序。Jenkins 的变量优先级大致是:任务级设置 > 参数化构建传入 > 系统级全局变量。也就是说任务里显式设了DEPLOY_ENV=prod,就算参数化构建传了staging,最终生效的还是prod。生产环境部署这种敏感操作,一定要确认这个覆盖关系,否则很容易把测试代码发到生产。
3.3 参数化构建的七种参数类型与传参实战
参数化构建让同一个任务能在启动时接收外部输入,是提升复用率的关键。常见参数类型和用途:
- String Parameter:普通文本输入,比如版本号、镜像 tag。
- Boolean Parameter:勾选框,比如"是否跳过测试"。
- Choice Parameter:下拉单选,比如环境选择
dev/test/prod。 - Password Parameter:密码输入,日志里自动打码。
- Text Parameter:多行文本,比如批量主机列表。
- File Parameter:上传文件,构建时放进
$WORKSPACE。 - Git Parameter:从仓库动态拉取分支或 tag 供选择。
一个体会很深的设计原则:凡是会变化、又不该写死在脚本里的值,都做成参数。比如部署目标机器 IP、发布版本号、数据库连接地址。把这些硬编码进 Shell 脚本,换环境就得改脚本、提交、等上线,参数化之后在页面上选一下就行。
调用时将参数传给脚本有两种方式。Shell 里直接读环境变量:
if [ "$SKIP_TEST" = "true" ]; then echo "跳过测试" mvn package -DskipTests else mvn package fi流水线里则通过params.访问:
stage('部署') { steps { sh "./deploy.sh ${params.DEPLOY_ENV} ${params.APP_VERSION}" } }注意:参数化构建一旦勾选,这个任务就不能用普通的"立即构建"来传参了,得点"Build with Parameters"。团队协作时要把这个约定写进文档,否则会有人抱怨"我传的参数怎么没生效"。
4. 让任务自己"说话":通知、串联与定时策略
任务跑起来了,但如果没人知道它跑得怎么样,自动化的价值就折损一半。这一节讲怎么让构建结果主动找到人,以及任务之间怎么接力。
4.1 钉钉自定义机器人的消息拼装
即时通知里钉钉机器人是使用率最高的方案之一。思路是:在群里创建一个"自定义机器人",拿到 Webhook 地址和加签密钥,然后在 Jenkins 构建后调用它。
最朴素的方式是直接在构建后步骤里用 curl 发请求:
#!/bin/bash WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=你的token" curl -s -X POST "$WEBHOOK" \ -H "Content-Type: application/json" \ -d "{ \"msgtype\": \"markdown\", \"markdown\": { \"title\": \"构建通知\", \"text\": \"### 构建结果\\n- 任务:$JOB_NAME\\n- 编号:$BUILD_NUMBER\\n- 状态:失败\\n- [点击查看日志]($BUILD_URL)\" } }"把这段封装成一个独立脚本文件,比如notify.sh,各任务调用它并传参,比在每个任务里复制一遍 curl 要干净。密钥这类敏感信息不要硬编码在脚本里,用 Jenkins 凭据管理,构建时通过环境变量注入。
一个经验:消息里最重要的不是"成功"两个字,而是可点击的直达链接和关键上下文。只说"构建失败"的通知,等于把排查工作原封不动推给了别人。
4.2 构建状态判断与消息模板设计
光发一条消息还不够,谁都有"这次失败是偶发还是真的坏了"的疑问。所以通知要分场景设计。
流水线的post块天然支持状态分支,可以把不同结果发到不同地方:
post { success { sh './notify.sh success' } failure { sh './notify.sh failure' } unstable { sh './notify.sh unstable' } aborted { sh './notify.sh aborted' } }unstable这个状态值得单独说。它通常表示"构建本身成功,但测试有失败用例"。把它和failure混在一起告警,会让真正的构建失败被淹没。合理做法是:failure立即@相关负责人,unstable只发到群里做日报汇总。
再进阶一点是失败重试抑制。同一任务连续失败 10 次就往群里刷 10 条消息,属于典型的告警风暴。可以在通知脚本里做判断,只有状态从"成功"变成"失败"的那一次才 @ 全员,后续持续失败只记录不打扰。这需要缓存上一次构建状态,实现方式可以用一个状态文件,思路是"状态变化才告警"。
4.3 上下游任务串联与并发控制
任务之间接力,最常见的是"公共库先构建,业务应用后构建"。在"构建后操作"里勾选"构建其他工程",填上游成功后要触发的下游任务名,就串起来了。但串联有几个坑要防。
循环触发:A 触发 B,B 又配置了触发 A,两个任务互相叫板,能把执行器瞬间占满。配置下游时一定要在脑子里画一遍依赖图,确认没有环。
并发冲突:同一个任务被上游和定时两个触发器同时唤起,两份构建共享工作空间,脚本写的临时文件互相覆盖。Jenkins 有个"必要时并发执行"选项,默认关闭意味着同一任务同一时刻只跑一个,后来者排队。新手建议保持默认,别急着开并发,真需要并发时应该拆成两个任务而不是让一个任务自己撞自己。
执行器耗尽:Jenkins 每个节点有固定的执行器数量(默认 2 个)。任务多、又都赶在同一时间跑,就会出现"pending"排队。解决办法有两个:一是把定时任务的 cron 用H打散,别都堆在整点;二是给不同任务分配不同节点,把负载摊开。
5. 任务跑不起来时,按这个顺序排查
前面讲的是怎么建,这一节讲建完之后跑不通怎么办。我习惯按"实例是否在线 → 插件源是否可用 → 工作空间与权限 → 资源是否耗尽"的顺序查,从外到内,能过滤掉大部分低级问题。
5.1 实例离线与插件源不可用的处理
打开 Jenkins 页面顶部飘红提示"该 Jenkins 实例似乎已离线",先别怀疑配置。这个提示通常有三种含义:Jenkins 主进程没启动、主进程启动了但页面加载的资源(CSS/JS)取不到、或者主从节点通信断了。第一种看进程,第二种多半是浏览器到服务器网络问题,第三种看节点的"Launch agent"日志。
内网离线环境里,插件装不上是另一个高频问题。Jenkins 默认去公网插件站点拉元数据,连不上就一直转圈。处理办法是进"系统管理 → 插件管理 → 高级",把 Update Site 换成内网可达的镜像地址,或者干脆手动下载.hpi文件后上传安装。手动装插件时要注意依赖关系,一个插件往往依赖另外几个,版本对不上会启动报错。
安装环节如果打算离线部署,建议提前在有网络的机器上把jenkins.war和常用插件一并下载好,把插件放进$JENKINS_HOME/plugins目录,再整体拷贝过去。这比到了内网再一个个找插件要省事得多。
5.2 工作空间冲突与权限问题
构建日志里出现大量Permission denied,先分清两类。一类是文件权限,比如 Jenkins 用户对$WORKSPACE没有写权限,或者执行脚本没有x位。另一类是系统调用权限,比如某些操作需要更高权限,这在容器化部署的 Jenkins 里尤其常见。
工作空间冲突的典型症状是"构建时快时慢,偶尔产物损坏"。根源常常是多个任务共用了同一份目录。Jenkins 的$WORKSPACE设计上就是给单个任务独占的,跨任务共享文件应该走归档制品或专门的共享存储,而不是直接去读写别人的工作目录。
还有一种隐蔽情况:脚本里用了相对路径,但构建步骤的默认工作目录和你想的不一样。养成在构建脚本开头加cd $WORKSPACE或者打印pwd的习惯,能省下大量"我的文件呢"的困惑。
5.3 并发、磁盘与执行器耗尽
任务卡在"pending"不跑,常见原因是执行器满了。进节点管理页面看看有多少个 executor,再看看有多少构建在排队。临时解法是调大执行器数量,长期解法是优化任务时长、把重任务分散到不同节点。
磁盘写满也经常被忽视。Jenkins 的构建历史默认一直保留,日积月累,$JENKINS_HOME/jobs下的旧构建产物能把磁盘吃干净。构建一失败就开始各种诡异报错,清理磁盘后又好了。建议给每个任务设置构建保留策略,比如"最多保留 30 次构建"或"保留最近 7 天",用"丢弃旧的构建"配置项就能做到。这个设置越早开越好,等磁盘满了再清理,运维成本会翻几倍。
我把这些年在 Jenkins 上踩的坑归纳成一句话:构建失败十次里有七八次,问题不在脚本,而在环境、权限和依赖这些"任务之外"的东西。所以排查时别一头扎进 Shell 代码,先确认基础条件齐不齐。
最后再分享一个自己一直在用的习惯:每建一个新任务,第一次先加一句echo "变量: $BUILD_NUMBER, 目录: $WORKSPACE, 分支: $GIT_BRANCH",跑通了再往里填真正的构建逻辑。这一句话看起来笨,但它能在几秒钟内证明"任务确实被触发了、环境确实进来了、代码确实在正确的目录",比盲猜配置快得多。任务多了之后,把这句探测语句固化成一个任务模板,新建任务时先复制模板再改,比从空白页开始配要省心。