Jenkins流水线配置与CI/CD实战优化技巧
我的写法,亲测靠谱
先上代码,这是我现在标准的 Jenkinsfile 写法,已经用在好几个生产项目里了,稳定性比一开始强太多了。
pipeline {
agent any
environment {
NODE_ENV = 'production'
API_URL = 'https://jztheme.com/api'
BUILD_NUMBER = "${BUILD_ID}"
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: '*/main']],
extensions: [[$class: 'CleanBeforeCheckout']],
userRemoteConfigs: [[url: 'git@github.com:your-org/your-repo.git']]
])
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci --silent'
}
}
stage('Build') {
steps {
sh 'npm run build:prod'
}
post {
success {
archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
}
}
}
stage('Test') {
parallel {
stage('Unit Test') {
steps {
sh 'npm run test:unit -- --coverage'
}
post {
always {
junit '**/test-results/*.xml'
publishHTML([
target: [
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
]
])
}
}
}
stage('E2E Test') {
steps {
sh 'docker-compose up -d && npm run test:e2e'
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
sh './scripts/deploy-staging.sh'
}
}
stage('Manual Approval') {
when {
branch 'main'
}
input {
message "Deploy to production?"
ok "Yes, deploy"
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
sh './scripts/deploy-prod.sh'
}
}
}
post {
failure {
slackSend channel: '#deploy-alerts', message: "❌ Build ${BUILD_ID} failed: ${env.JOB_NAME}"
}
success {
slackSend channel: '#deploy-success', message: "✅ ${env.JOB_NAME} deployed successfully!"
}
always {
cleanWs()
}
}
}
我以前也写过那种一个 stage 里堆一堆 sh 命令的,看着挺简洁,实际一出问题根本没法查。现在这种结构,每个阶段职责明确,失败定位快,日志也清晰。
特别注意 npm ci 而不是 npm install。CI 环境必须用 ci,不然依赖版本可能漂移,我就因此踩过一次线上 bug,排查了两小时才发现是某个 minor 版本更新导致的兼容性问题。
还有就是 archiveArtifacts 和 publishHTML 这些,别嫌麻烦,加上去。下次谁问“上次构建的包在哪”,你直接甩个链接就完事了。
这几种错误写法,别再踩坑了
先说最常见的:把所有逻辑塞进一个 shell 脚本,Jenkinsfile 就一行 sh './deploy.sh'。看起来很干净对吧?错!出了问题你得登录到 Jenkins 机器上去翻日志,还不知道卡在脚本哪一步。我之前接手的项目就这样,报错信息就一句“script returned exit code 1”,折腾了半天发现是环境变量没加载。
另一个作死操作:不加 cleanWs()。你以为 workspace 会自动清?不会的!特别是用 git 的时候,如果分支切换不彻底,残留文件可能导致构建成功但产物不对。我就遇到过一次,构建出来的 JS 包里混进了 develop 分支的调试代码,上线后前端白屏……
还见过有人在 pipeline 里硬编码密码:
sh 'scp -i /home/jenkins/.ssh/id_rsa dist/* user@server:/var/www'
这是真不怕被审计干掉啊。正确的做法是用 Jenkins Credentials Binding Plugin:
withCredentials([sshUserPrivateKey(credentialsId: 'my-ssh-key', keyFileVariable: 'KEY')]) {
sh 'scp -i $KEY dist/* user@server:/var/www'
}
或者更推荐的做法,用 Ansible 或其他部署工具接管发布流程,Jenkins 只负责到打包为止。
还有一个隐藏巨坑:parallel 阶段里共享 workspace。比如你在两个并行 stage 里都写文件,很可能发生冲突。我之前有个项目单元测试和 E2E 测试共用同一个配置文件,结果经常因为读写竞争导致随机失败。解决方法要么加锁,要么用独立 workspace —— 但后者成本高。我的建议是:并行任务尽量只读,写操作放后面串行处理。
实际项目中的坑
有一次我在公司搞自动化部署,一切本地测试正常,推到 Jenkins 上跑,npm run build 直接卡住没输出。查了半小时才发现是 Docker 容器里没有设置 --no-deprecation 和 --progress=false,Node.js 的进度条输出被当成阻塞了。解决方案是在 sh 命令里加选项:
sh 'npm run build:prod -- --progress=false'
或者干脆在 .npmrc 里全局关掉:
progress=false
还有一次更离谱,构建成功,部署也成功,但网站访问 404。最后发现是 Nginx 配置没 reload。这种基础设施问题 Jenkins 是不会帮你检测的。所以我在部署脚本末尾强制加了一行:
ssh deploy@web-server 'sudo nginx -s reload || true'
至少保证配置重载尝试过了,就算失败也不中断整个 pipeline(除非你真想让它失败)。
关于超时,默认的 stage 超时是无限的。我见过有构建卡了八个小时还在跑……一定要加 timeout:
stage('Build') {
options {
timeout(time: 15, unit: 'MINUTES')
}
steps {
sh 'npm run build'
}
}
15 分钟够了,再长肯定是哪里出问题了。
另外一个小技巧:用 BUILD_NUMBER 环境变量打 tag。我们现在的流程是,只要 main 分支构建成功,自动打一个 git tag:
stage('Tag Release') {
steps {
sh 'git config user.name "Jenkins"'
sh 'git config user.email "jenkins@localhost"'
sh "git tag -a v${BUILD_NUMBER} -m 'Release ${BUILD_NUMBER}'"
sh 'git push origin --tags'
}
}
这样每个构建都能对应到一个 release tag,回滚的时候特别方便。
插件能不用就不用
我知道很多人喜欢装各种 Jenkins 插件,Slack、DingTalk、Enterprise WeChat、Blue Ocean……但我建议:原生能实现的,别加插件。
为什么?插件升级容易出兼容问题,而且一旦 Jenkins 挂了,你要排查是 core 问题还是插件问题,多一层复杂度。像 Slack 提醒,直接用 slackSend 就行,不需要额外装 Pipeline Utility Steps 之类的辅助插件。
还有一个血泪教训:别在 Jenkins 里做数据库迁移。曾经有同事把 sequelize db:migrate 放 pipeline 里,结果某次 migration 写错了,直接把 staging 数据库字段删了……现在我们的规范是:数据库变更必须人工确认,通过单独的运维通道执行,不在 CI 流程里自动跑。
最后一点:别追求完美 pipeline
说实话,我现在这个 Jenkinsfile 也不是完美的。比如 artifact 存储还是靠 Jenkins 自带的,没对接外部存储;比如多分支的逻辑还不够智能。但我觉得够用就行。
改完之后仍有一两个小问题,比如 parallel 阶段的 HTML 报告偶尔显示异常,但不影响主流程。这种时候我选择放过自己——毕竟没人给 Jenkins 写单元测试。
这个方案不是最优的,但最简单,团队其他人也能看懂、能维护。有时候“能跑”比“优雅”重要得多。
以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流。

暂无评论