Jenkins流水线配置与CI/CD实战优化技巧

♫栾同 前端 阅读 1,683
赞 33 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

先上代码,这是我现在标准的 Jenkinsfile 写法,已经用在好几个生产项目里了,稳定性比一开始强太多了。

Jenkins流水线配置与CI/CD实战优化技巧

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 版本更新导致的兼容性问题。

还有就是 archiveArtifactspublishHTML 这些,别嫌麻烦,加上去。下次谁问“上次构建的包在哪”,你直接甩个链接就完事了。

这几种错误写法,别再踩坑了

先说最常见的:把所有逻辑塞进一个 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 写单元测试。

这个方案不是最优的,但最简单,团队其他人也能看懂、能维护。有时候“能跑”比“优雅”重要得多。

以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论