半夜被电话叫醒说线上版本没发出去登上跳板机一看Jenkins 上那个部署任务卡在第三步已经两个多小时——这种场面做过 CI/CD 的人多少都遇过。Jenkins 用得久了会发现真正让人头疼的从来不是装不上而是任务不按你想的跑。Jenkins 的任务Job这个看着朴素的概念背后其实藏着触发器、执行器、环境变量、凭据、工作空间、后置动作这一整套东西任何一环理解偏了出来的现象就是玄学失败。这篇东西只聊一件事Jenkins 任务到底怎么理解、怎么配、怎么排障。从自由风格项目到声明式流水线从在线安装到内网离线部署从 GitLab 提交自动触达到构建完把消息丢进钉钉群我会把我会的那些、踩过的那些坑都摊开讲。适合谁看刚接手公司 CI 机器但只会点立即构建的运维新人写了半年 Jenkinsfile 还是抄模板的开发以及正准备面试、被问到任务不执行怎么排查会卡壳的朋友。文中涉及参数、路径、命令的地方我尽量给出可直接照抄的版本同时说明每个选择背后的原因而不是丢一堆配置让你自己猜。1. 先搞清楚 Jenkins 的任务到底是个什么东西很多人第一次打开 Jenkins 界面看到新建 Item随手建了个自由风格项目加一条 shell点构建成功了于是就以为自己会 Jenkins 了。直到某天任务开始随机失败、构建历史把磁盘撑爆、并发构建互相覆盖文件才发现对任务的理解还停留在表面。所以先把概念捋清楚再动手。1.1 任务的本质一份被调度起来的动作说明书我的理解是Jenkins 里的一个任务本质上是什么条件下触发 在哪台机器上跑 按顺序执行哪些动作 结束后做什么这四件事的打包。它不神秘就是一份说明书Jenkins 的调度器负责读这份说明书并按图索骥。拆开看在哪台机器上跑对应的是节点Node与执行器Executor的概念。默认的 master现在官方叫 Built-In Node自带若干执行器数量在系统配置里能改。执行器就是能同时跑几个任务的名额一个执行器同一时间只能被一个构建占用。我在生产上见过最典型的误配是把 master 的执行器数量设成 0 又想在里面跑任务结果所有构建永远卡在等待下一个可用的执行器界面上就是一直转圈没有任何报错。这个坑的排查方法后面会细说。按顺序执行哪些动作是构建步骤自由风格项目里就是一堆增加构建步骤堆起来的列表流水线里就是 stage 和 steps。这里有个容易被忽略的点步骤之间的状态是共享的前一步 cd 到某个目录后一步不一定还在那——因为在某些插件环境下每一步是独立的 shell 会话。想稳就在每一步开头显式 cd 到$WORKSPACE。至于结束后做什么那是后置构建动作发通知、归档产物、触发下游任务、清理工作空间。很多团队把通知逻辑写在构建脚本最后一行一旦脚本中途exit 1通知就永远发不出去群里看不到失败提醒人还以为一切正常。正确姿势是放进 post 段或者构建后操作让它在任何情况下都被执行。1.2 自由风格、流水线、多分支三种任务别选错新手最容易纠结的就是建哪种任务。我的经验是按团队成熟度分档一个人维护、逻辑三五步的自由风格够用团队协作、需要版本化、步骤超过十步的直接上流水线代码分支多、每个分支都要独立构建的用多分支流水线。自由风格项目Freestyle Project的优点是上手快界面点一点就能跑缺点是配置全存在 Jenkins 自己的 XML 里改了什么、谁改的、为什么改全靠 Jenkins 的配置历史插件代码评审覆盖不到。我接手过一个三年没人管的自由风格任务构建步骤有 27 条里面还有硬编码的密码看得头皮发麻。流水线Pipeline的核心价值是Pipeline as Code——Jenkinsfile 跟业务代码放一起走同样的 Git 流程。声明式Declarative语法结构固定有 pipeline/agent/stages/post 这些块对新手友好脚本式Scripted更灵活能写 Groovy 逻辑但容易写成一团。我个人倾向声明式打底遇到确实需要循环、动态 stage 的场景在 script 块里写 Groovy。多分支流水线Multibranch Pipeline会自动扫描仓库里的分支和 PR为每个带 Jenkinsfile 的分支生成一个任务。它的好处是你不用手动建几十个任务坏处是扫描频率和构建历史管理需要额外注意否则几十个分支的历史攒在一起磁盘掉得飞快。1.3 任务的四个组成部分缺一不可不管建哪种任务我都习惯按触发器—环境—步骤—收尾这四段去检查一遍很少会漏东西。触发器决定任务什么时候跑常见的有手动点、定时cron 表达式、SCM 轮询、Webhook 回调、上游任务触发、其他系统调 API。这里有个细节Jenkins 的 cron 语法比标准 cron 多一个H符号H/5 * * * *表示每 5 分钟一次但起始分钟由 Jenkins 散列决定作用是避免一堆任务整点同时开跑把机器压垮。很多教程不解释这个 H导致有人以为写错了。环境决定任务能拿到什么。这包括系统环境变量、Jenkins 注入的变量BUILD_NUMBER、JOB_NAME、WORKSPACE 等、你在任务里自定义的变量以及凭据Credentials。凭据这块是安全重灾区后文单独讲。步骤是主体收尾是保障。这两块的具体写法在第三节展开。先把骨架立住任何一个诡异的任务问题最后都能归到这四个部分中的某一个上面去。2. 环境准备与离线安装任务能跑起来的前提任务配得再好环境不对也是白搭。Jenkins 的安装分两种典型场景能连外网的直接在线装最省事内网隔离的只能离线装。这两种我在不同项目里都做过坑完全不同分开说。2.1 在线安装与插件源替换在线装 Jenkins 的路径很固定装 JDK、加官方仓库、装包、启动、解锁、装推荐插件。以常见 Linux 发行版为例JDK 建议用 11 或 17具体看 Jenkins 版本要求新版本对 JDK 17 支持更好JDK 8 已经在逐步退出。装完之后默认监听 8080初始密码在/var/lib/jenkins/secrets/initialAdminPassword里cat一下就能拿到。真正卡人的通常不是安装本身而是装插件。默认的更新站点在境外插件列表刷不出来、装一半失败是家常便饭。解决办法是换国内镜像站把升级站点地址改成镜像提供的update-center.json。操作路径是系统管理 → 插件管理 → 高级 → 升级站点换完之后点立即获取插件列表几十秒就能刷出来。这一步我强烈建议装完 Jenkins 第一件事就做别等装插件失败十几次才想起来。换源之后还有个小细节如果之前已经下载失败的插件残留在本地建议清一下插件下载缓存目录再重试否则可能出现配置文件里显示已安装但实际插件目录里没有 jar的错位状态表现为启动时报一堆 ClassNotFound。2.2 离线安装的完整思路内网环境装 Jenkins思路是能在外网准备的都提前准备好搬进去只做安装和配置。我一般按这个顺序做第一步在外网机器上装一个同版本的 Jenkins版本号必须和要离线部署的一致否则插件兼容性对不上。第二步在这台机器上把需要的所有插件装好插件装在plugins目录下每个插件是一个.hpi或.jpi文件把它们连同plugins目录下的*.jpi.pinned之类的辅助文件一起打包。第三步把整个plugins目录拷进内网机器的 Jenkins 主目录重启服务。这里有个必须提醒的点直接拷plugins目录时最好把plugins下所有文件权限统一改成 Jenkins 运行用户通常是 jenkins:jenkins否则会出现插件文件在但加载不了的情况。我遇到过一次排查了半天最后发现是 UID 不一致导致 Jenkins 没读权限。.hpi文件本身是插件更新中心提供的你也可以从镜像站手动下载单个插件放进plugins目录再重启。但要注意插件之间的依赖关系A 插件依赖 B 插件只拷 A 是起不来的。稳妥做法是先在线环境把依赖树装全再整体搬。提示离线环境的 Jenkins 千万别开自动更新检查否则每次启动都会尝试连外网日志里刷一堆超时看着很吓人实际没影响但干扰排查。2.3 可用环境变量任务里最容易浪费的宝藏Jenkins 在构建过程中会注入一批环境变量这些变量是写脚本时最省事的东西但很多人不知道有哪些只能靠 echo 一个个试。其实任务页面里有个环境变量链接构建页面左侧点进去能看到本次构建的全部变量和值这是最直接的查阅方式。常见的几个我列一下都是实际写脚本必用的变量名含义典型用途BUILD_NUMBER当前构建序号拼接镜像 tag、产物文件名BUILD_ID构建时间戳格式的 ID唯一标识一次构建JOB_NAME任务名通知消息里标注来源WORKSPACE本次构建的工作目录绝对路径脚本里 cd、清理BUILD_URL本次构建的页面地址钉钉/邮件通知里附链接GIT_COMMIT本次构建对应的提交哈希产物打标、回滚定位GIT_BRANCH构建的分支区分环境部署NODE_NAME执行本次构建的节点名多节点场景排查使用这些变量时有个坑在自由风格项目的 shell 里变量是直接可用的写$BUILD_NUMBER就行但在流水线的 Groovy 字符串里单引号不解析变量必须用双引号或者用${env.BUILD_NUMBER}这种显式写法。我见过不少人在 Jenkinsfile 里写sh echo $BUILD_NUMBER因为用了单引号导致变量没展开误以为变量不存在。另外自定义环境变量建议统一放在流水线的environment块里而不是散落在各个 shell 脚本中。这样一来换环境时只改一处二来变量的来源一目了然接手的人不用翻遍整个 Jenkinsfile 找哪里定义了 APP_NAME。3. 从零写一个可用的任务以自动部署为例理论说再多不如完整走一遍。这一节我用一个真实项目里常见的场景来串GitLab 上的 Java 服务提交代码后自动拉取、编译、打包、上传到目标机器、重启服务最后把结果发到钉钉群。整个流程覆盖了触发器、凭据、环境变量、构建步骤、后置动作走完一遍基本就通透了。3.1 凭据配置与 GitLab 连接第一件事是把拿代码的权限配好。Jenkins 里千万不要把 Git 密码明文写在任务的源码管理里后果是任何人访问任务配置页都能看到而且配置历史里会留痕。正确做法是用凭据Credentials。凭据的添加路径是系统管理 → 凭据 → 系统 → 全局凭据 → 添加凭据。类型选Username with password或SSH Username with private key。前者适合 HTTPS 拉代码后者适合 SSH 免密。密码填 GitLab 的账号密码或者更推荐用 GitLab 的 Personal Access Token 当密码权限可控、可随时吊销。如果团队用的是 GitLab还可以装 GitLab 插件在系统管理 → 系统配置 → GitLab里填 Name、GitLab host URL 和凭据这里填的是 API Token用于 Jenkins 回调 GitLab 设置构建状态、读取项目信息。配置完后在任务的构建触发器里勾选Build when a change is pushed to GitLabJenkins 会给你一个回调 URL把它粘到 GitLab 项目的 Webhook 里配上 Secret Token提交即可触发。注意Webhook 触发不生效九成是网络方向的问题——要么 GitLab 所在网络访问不到 Jenkins 的地址要么 Jenkins 的 URL 配置里写的是内网 localhost导致回调 URL 生成错了。先去系统管理 → 系统配置 → Jenkins Location检查 Jenkins URL 是不是外部可达的地址。3.2 构建触发器的几种玩法与取舍触发器不是越多越好多了会互相打架。我一般按场景选一种主触发方式其他作为补充。日常开发分支用 Webhook 触发实时性最好提交即构建。但要配合分支过滤只让特定分支触发否则一个人推个文档也跑一遍完整构建浪费资源。主干或发布分支用 SCM 轮询兜底cron 写成H/10 * * * *防止 Webhook 偶发丢失导致构建漏掉。定时构建Build periodically适合夜间跑全量测试、清理任务这类到点干活的场景我会把它放在一个独立任务里不和部署任务混在一起。上游任务触发用的是构建后操作 → 构建其他工程配合参数化构建能传递变量。这里有个细节传递参数时只有你显式在预定义参数里写出来的才会传过去环境变量不会自动继承。我就踩过这个坑以为上游的 BUILD_NUMBER 能直接在下游用结果拿到的是空的。至于轮询 SCM对 Git 仓库的压力中小团队一般无所谓几千个任务的环境才需要考虑用 GitLab 的 Webhook 或 Jenkins 的 Git 插件事件推送来替代轮询。3.3 构建步骤从拉代码到重启服务的完整链路这是整个任务的核心。我把上面那个 Java 服务的部署流程写成声明式流水线逐段拆开讲。pipeline { agent { label build-node } options { timestamps() buildDiscarder(logRotator(numToKeepStr: 30, artifactNumToKeepStr: 5)) timeout(time: 30, unit: MINUTES) disableConcurrentBuilds() } environment { APP_NAME order-service IMAGE_TAG ${env.BUILD_NUMBER}-${env.GIT_COMMIT?.take(7)} DEPLOY_DIR /opt/app/order-service } stages { stage(拉取代码) { steps { checkout scm sh git log -1 --prettyformat:%h %an %s } } stage(编译打包) { steps { sh set -e mvn -B clean package -DskipTests -Dmaven.repo.local$WORKSPACE/.m2 ls -lh target/*.jar } } stage(上传产物) { steps { sh set -e scp -o StrictHostKeyCheckingno \ target/${APP_NAME}.jar \ deploy10.0.0.21:${DEPLOY_DIR}/${APP_NAME}-${IMAGE_TAG}.jar } } stage(重启服务) { steps { sh set -e ssh -o StrictHostKeyCheckingno deploy10.0.0.21 ln -sfn ${DEPLOY_DIR}/${APP_NAME}-${IMAGE_TAG}.jar ${DEPLOY_DIR}/current.jar sudo systemctl restart ${APP_NAME} sleep 5 systemctl is-active ${APP_NAME} } } } post { success { sh echo 构建成功准备发送通知 } failure { sh echo 构建失败准备发送通知 } always { archiveArtifacts artifacts: target/*.jar, allowEmptyArchive: true cleanWs() } } }几个关键点值得展开。options里我放了四样东西timestamps让每行日志带上时间排查耗时问题特别有用buildDiscarder限制历史数量这是防止磁盘被撑爆的第一道防线timeout卡死一个上限避免任务因为某个网络调用永远挂着disableConcurrentBuilds禁止同一任务并发防止两次构建同时改同一个服务目录把文件弄乱。这四条我建议写成模板新任务直接抄。environment里的IMAGE_TAG用了BUILD_NUMBER加 Git 短哈希这样每个产物名字唯一回滚时能找到具体是哪个提交。GIT_COMMIT?.take(7)里的问号是 Groovy 的安全导航符如果变量为空不会抛异常这个细节在 Jenkinsfile 里很实用因为有些触发方式比如手动且没配 Git下 GIT_COMMIT 可能是空的。shell 里第一行写set -e是必须的。默认情况下 shell 脚本中间某条命令失败后面的命令会继续执行结果就是构建显示成功但服务根本没起来。set -e让任何一条命令返回非零就立即中断让失败暴露出来。这个坑我最早也踩过一次上线完服务没起来但流水线是绿的白白耽误了半天。3.4 后置动作与钉钉自定义消息通知构建结果要让人知道尤其是失败。钉钉机器人是最轻量的方案加签后用 curl 发 JSON 就行。下面这个写法是不依赖插件、纯脚本实现的版本通用性好不挑 Jenkins 版本。#!/bin/bash # dingtalk-notify.sh WEBHOOKhttps://oapi.dingtalk.com/robot/send?access_token你的token STATUS$1 COLOR_TXT构建结果: ${STATUS} curl -s $WEBHOOK \ -H Content-Type: application/json \ -d { \msgtype\: \markdown\, \markdown\: { \title\: \Jenkins 构建通知\, \text\: \### Jenkins 构建通知\n- 任务: ${JOB_NAME}\n- 构建号: #${BUILD_NUMBER}\n- 状态: ${COLOR_TXT}\n- 分支: ${GIT_BRANCH}\n- [查看详情](${BUILD_URL})\ } }在流水线里这样调用post { success { sh chmod x ./ci/dingtalk-notify.sh ./ci/dingtalk-notify.sh 成功 } failure { sh chmod x ./ci/dingtalk-notify.sh ./ci/dingtalk-notify.sh 失败 } }用了机器人加签的话需要在请求里额外算一个 timestamp 和 sign 参数具体算法钉钉文档里有用脚本拼一下就行。如果不想自己写也可以装 DingTalk 插件界面上填 Webhook 和关键词但插件版本和 Jenkins 版本偶有兼容问题我个人更倾向脚本方案可控。提示通知脚本务必做异常兜底。如果通知接口本身挂了脚本返回非零会让本已成功的构建被标记为失败得不偿失。建议在通知脚本最后统一exit 0或者用|| true包一层。这里还有个实践细节别把通知放在构建步骤的最后而是放进 post 块原因前面提过——步骤中途失败时 post 依然会执行通知不会丢。4. 常见问题与排查技巧实录干这行久了遇到的问题百分之八十是重复的。这一节我把高频问题和排查路径整理成表再补几个排查技巧遇到问题可以直接对着找。4.1 任务不执行、一直排队、离线状态怎么查有个很典型的现象点立即构建任务一直在等待下一个可用的执行器半天不动。遇到这种情况按顺序查四点一是该任务指定的节点标签label是否真的存在这个标签的节点二是节点的执行器数量是不是 0我在节点管理里见过不下五次执行器被改成 0 的情况三是节点是否处于离线状态四是队列里是不是被别的任务占满了。Jenkins 页面右上角如果出现该 Jenkins 实例似乎已离线的黄色提示通常是浏览器访问 Jenkins 的地址和系统配置里的 Jenkins URL 不一致导致的。这个提示大多数时候不影响实际构建只是反跨站脚本保护在起作用。想消掉它去系统管理 → 系统配置 → Jenkins Location把 URL 改成你实际访问的地址也就是浏览器地址栏里那个。节点离线的原因常见有三种节点机器重启后 Jenkins 代理进程没自动拉起节点磁盘满导致代理写不了工作目录连接凭据SSH key 或 secret变更后没更新。排查节点磁盘用df -h我见过太多次因为节点/tmp分区满了、导致所有构建失败的情况清一下就好。4.2 构建失败高频原因速查表下面这张表是我自己攒的覆盖了我实际遇到的大部分构建失败按出现频率排的。现象大概率原因处理方式拉代码报 403 / Permission denied凭据失效或权限不足更新 Credentials确认账号对仓库有读权限编译阶段报内存不足构建节点内存不够或 JVM 参数过大调小 Maven/JVM 堆参数或换更大规格节点依赖下载超时构建机访问中央仓库慢配置国内 Maven 镜像或搭建内网私服上传产物失败目标机 SSH key 未配或目录不存在预先配好免密脚本里先mkdir -p服务重启后一直不起端口占用、配置错误、启动权限看服务日志检查端口和部署用户权限构建成功但线上没变软链未生效、多节点缓存、上传路径错校验软链指向确认上传目录和运行目录一致磁盘爆满导致全部失败构建历史和工作空间没清理配buildDiscarder和定期清理工作空间其中构建成功但线上没变这个最阴因为它不报错。我遇到过一次原因是上传脚本用了相对路径在不同工作空间下解析到了不同目录而运行目录的软链指向没更新。后来我把脚本里所有路径都改成绝对路径问题就消失了。经验就是脚本里能用绝对路径就不要用相对路径能显式 cd 就显式 cd。4.3 面试常问的几个点与我的答法被问到 Jenkins 相关问题时答得好的关键不是背概念而是能说出取舍。我整理了几个高频问题附上我自己的回答思路。问自由风格和流水线怎么选我会答看协作规模和变化频率。一个人维护、逻辑简单自由风格省事多人协作、需要评审和版本化流水线更合适因为 Jenkinsfile 能进代码库走 MR 流程。如果分支多多分支流水线能自动生成任务省去手工维护。问如何保证部署任务安全我的答法分三层凭据集中管理不落盘、部署用户最小权限、关键操作加人工确认可以用 input 步骤。尤其是生产环境我倾向加一道 input 审批让发布动作有人为确认节点避免误触发直接上线。问任务失败怎么定位先看是不是环境问题节点、磁盘、网络再看是不是代码问题编译、测试最后看脚本逻辑。养成读完整日志的习惯Jenkins 日志里失败那一步的上下文通常写得很清楚多数人失败是因为只看最后几行。4.4 几个只有踩过才知道的小技巧先说一个关于cleanWs()的。这个步骤会清空整个工作空间看起来无害但如果你的构建产物还没归档就调用了它产物就没了。所以cleanWs()一定放在archiveArtifacts之后顺序反了就是给自己挖坑。再说一个关于并发的问题。即使加了disableConcurrentBuilds多个不同任务同时跑也可能争抢同一台机器的资源。我习惯给部署类任务单独指定一个节点或者加锁用 Lockable Resources 插件保证同一时刻只有一个部署在跑。这个在服务重启阶段尤其重要两个任务同时重启同一个服务结果谁也说不清。最后一个关于时间。Jenkins 用的时区是 JVM 启动时的系统时区如果服务器时区设置不对日志时间会整体偏移排查问题时容易误判顺序。这个问题在查看历史日志时特别隐蔽确认时区用date命令对比一下即可。5. 任务治理从能跑到跑得好任务建起来、能跑通只是第一步。真正拉开团队差距的是跑得稳、管得住、换个人接手能看懂。这一节聊两件事磁盘治理和任务规范。5.1 磁盘空间Jenkins 最常见的慢性病Jenkins 磁盘满几乎是个必然事件除非你主动管理。占空间的三大块构建历史含日志和归档产物、工作空间、插件。日志默认每次构建都保留一个高频任务一年下来能攒出几十 G。治理办法是分层做。第一层在建任务时就配buildDiscarder把日志和产物都限制在合理数量上文示例里的numToKeepStr: 30、artifactNumToKeepStr: 5就是干这个的。第二层配置全局的丢弃策略在系统管理 → 系统配置里设置默认保留数兜住那些忘了配的临时任务。第三层加一个定时清理任务用脚本删掉超过一定时间的旧构建目录作为最后保险。工作空间这块Jenkins 不会自动清理。我一般给每个任务的post段加cleanWs()或者在节点配置里勾选在构建前清理工作空间。但注意有些任务依赖缓存目录比如 Maven 本地仓库.m2清空工作空间会导致每次都要重新下载依赖慢得让人崩溃。这种情况就单独把缓存目录放到工作空间之外比如/data/jenkins-cache任务里引用这个固定路径。5.2 任务规范让接手的人少骂两句团队里任务多了之后命名和结构规范能省下大量沟通成本。我们内部有份约定我摘几条核心的。命名统一前缀按业务线来比如order-service-deploy、user-service-test而不是测试1、new job这种。任务描述里写清楚负责人和用途别让人靠猜。参数化构建时参数名要有意义branch比b1强太多。所有流水线任务Jenkinsfile 必须入库不允许在界面上直接写脚本。这样改动能评审、能回滚、能追溯。共享的构建逻辑抽成共享库Shared Library比如通知、产物上传这些通用步骤避免每个文件复制粘贴改一处要改几十处。凭据一律走 Credentials定期轮换。我曾经见过一个仓库的 Jenkinsfile 里硬编码了数据库密码还提交到了 GitLab后果很严重。把凭据扫描加进代码检查流程能防住这类低级错误。任务上线前跑一遍 checklist有没有timeout、有没有磁盘丢弃策略、失败有没有通知、生产部署有没有审批、脚本有没有set -e。这几条看着琐碎但每一条背后都是一次真实的事故。我个人在实际操作中的体会是Jenkins 这套东西不难难的是把临时能跑和长期可靠分开。前者靠直觉就能做到后者靠的是一堆看起来啰嗦的约束——限时、限保留数、限并发、加审批、脚本必须显式失败。这些约束平时感觉不出价值只有当某个凌晨任务悄无声息失败、而你恰好因为收到了通知及时处理时才会意识到它们值钱。最后分享一个小习惯每隔半年抽时间翻一遍所有任务的配置把没人用的删掉把没配齐的补上这件事花两个小时能挡掉后面半年的意外。