【架构实战】Jenkins+GitLab CI/CD:持续集成与持续部署实践
【架构实战】JenkinsGitLab CI/CD持续集成与持续部署实践字数统计约3800字一、真实故事引入从手动部署的噩梦到自动化救赎2023年冬天我刚入职一家生鲜电商公司第一次参与大促前的上线。当时我们的部署流程还是纯手动开发人员提交代码到GitLab然后运维人员登录跳板机拉取代码、手动执行mvn package打包、上传jar包到服务器、重启服务。那次上线前我负责的一个优惠券服务因为打包时漏了一个application-prod.yml配置文件导致线上服务启动失败整个优惠券系统宕机了15分钟影响了大促的预热活动直接导致公司损失了近20万的订单额。事故复盘会上技术总监拍着桌子说“再也不想看到手动部署了” 于是我们痛定思痛决定引入JenkinsGitLab CI/CD来实现端到端的自动化部署从此告别了手动部署的噩梦。这就是我和CI/CD的第一次亲密接触也让我深刻理解了自动化部署对团队效率的保障作用。二、概念原理CI/CD到底是什么2.1 核心概念解析CI持续集成开发人员频繁地将代码合并到主干分支通常每天多次每次合并都触发自动构建和测试尽早发现集成错误。核心目标是早发现早解决避免集成地狱。CD持续交付/持续部署持续交付自动将通过所有测试的代码构建成可部署的制品随时可以手动部署到生产环境持续部署自动将通过所有测试的代码直接部署到生产环境不需要人工干预Jenkins开源的自动化服务器拥有超过1800个插件支持构建、测试、部署全流程的自动化适合复杂场景的定制化需求GitLab CI/CDGitLab内置的CI/CD能力通过项目根目录的.gitlab-ci.yml文件定义流水线轻量且和代码仓库深度集成2.2 两者集成的核心逻辑我们的集成方案采用GitLab触发Jenkins的模式开发人员提交代码到GitLab的指定分支如mainGitLab通过Webhook通知Jenkins有新的代码变更Jenkins拉取代码执行预定义的流水线构建、测试、打包、部署流水线执行结果反馈到GitLab的Pipeline页面这种组合既利用了GitLab的代码管理能力又发挥了Jenkins插件生态丰富的优势。三、配置代码从零搭建集成环境3.1 基础环境准备# 1. 用Docker部署JenkinsLTS版本dockerrun-d--namejenkins\-p8080:8080-p50000:50000\-vjenkins_home:/var/jenkins_home\-v/var/run/docker.sock:/var/run/docker.sock\jenkins/jenkins:lts# 2. 部署GitLab如果还没部署的话dockerrun-d--namegitlab\-p443:443-p80:80-p22:22\-vgitlab_config:/etc/gitlab\-vgitlab_logs:/var/log/gitlab\-vgitlab_data:/var/opt/gitlab\gitlab/gitlab-ce:latest3.2 Jenkins配置GitLab集成在GitLab生成Access Token进入User Settings → Access Tokens勾选api权限生成token后保存在Jenkins安装GitLab Plugin进入Manage Jenkins → Configure System → GitLab填写GitLab host URLhttp://your-gitlab-urlCredentials选择GitLab API Token填入上一步生成的token点击Test Connection验证连通性配置Webhook在GitLab项目的Settings → Webhooks中URL填写http://your-jenkins-url/gitlab-webhook/Secret Token在Jenkins的GitLab配置中生成的Webhook Secret勾选Push events分支过滤填写main3.3 核心流水线配置Jenkinsfile我们在项目根目录创建Jenkinsfile声明式流水线pipeline{agent any// 环境变量配置environment{DOCKER_REGISTRYharbor.example.comPROJECT_NAMEcoupon-serviceKUBECONFIGcredentials(k8s-kubeconfig)}stages{// 阶段1拉取代码stage(拉取代码){steps{git(url:http://gitlab.example.com/ecommerce/coupon-service.git,branch:main,credentialsId:gitlab-ssh-credentials)}}// 阶段2编译构建stage(编译构建){steps{shmvn clean package -DskipTests}post{success{archiveArtifacts artifacts:target/*.jar,fingerprint:true}}}// 阶段3单元测试stage(单元测试){steps{shmvn test}post{always{junittarget/surefire-reports/*.xmlpublishCoverage adapters:[jacocoAdapter(target/site/jacoco/jacoco.xml)],sourceFileResolver:sourceFiles(STORE_LAST_BUILD)}}}// 阶段4代码质量检查集成SonarQubestage(代码质量检查){steps{shmvn sonar:sonar -Dsonar.projectKeycoupon-service -Dsonar.host.urlhttp://sonarqube.example.com -Dsonar.login${SONAR_TOKEN}}}// 阶段5构建Docker镜像stage(构建镜像){steps{script{defimageName${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER}shdocker build -t${imageName}.shdocker push${imageName}}}}// 阶段6部署到测试环境stage(部署测试环境){steps{shkubectl set image deployment/${PROJECT_NAME}${PROJECT_NAME}${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER}-n testshkubectl rollout status deployment/${PROJECT_NAME}-n test}}// 阶段7自动化测试可选stage(自动化测试){steps{shcurl -X POST http://test.example.com/api/health-check}}}// 流水线后置处理post{success{echo流水线执行成功// 发送成功通知到企业微信shcurl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxx -d {\msgtype\:\text\,\text\:{\content\:\优惠券服务部署成功版本${BUILD_NUMBER}\}}}failure{echo流水线执行失败// 发送失败通知shcurl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxx -d {\msgtype\:\text\,\text\:{\content\:\优惠券服务部署失败请查看控制台日志\}}}}}3.4 GitLab CI补充配置可选如果不需要Jenkins的复杂能力也可以直接用GitLab CI在项目根目录创建.gitlab-ci.ymlstages:-build-test-deploybuild_job:stage:buildimage:maven:3.8-openjdk-17script:-mvn clean package-DskipTestsartifacts:paths:-target/*.jartest_job:stage:testimage:maven:3.8-openjdk-17script:-mvn testartifacts:reports:junit:target/surefire-reports/*.xmldeploy_job:stage:deployimage:bitnami/kubectl:latestscript:-kubectl set image deployment/coupon-service coupon-serviceharbor.example.com/coupon-service:${CI_PIPELINE_ID}-n prodonly:-main四、实战案例优惠券服务的CI/CD全流程我们的优惠券服务是一个Spring Boot项目部署在K8s集群中完整流程如下开发人员完成需求开发提交代码到main分支触发GitLab WebhookJenkins自动拉取代码执行流水线编译打包耗时2分钟生成coupon-service-1.0.0.jar单元测试耗时1分钟覆盖率85%无失败用例代码质量检查SonarQube评分A无阻断性问题构建Docker镜像基于openjdk:17-alpine镜像大小280MB推送到Harbor仓库部署到测试环境K8s滚动更新耗时30秒无downtime测试人员验证测试环境功能确认通过后手动点击Jenkins的部署生产按钮生产环境滚动更新健康检查通过后流水线结束总耗时从代码提交到生产部署完成约8分钟纯自动化部分5分钟对比原来的手动部署流程30分钟容易出错效率提升了6倍且零人为错误。五、踩坑实录那些年我们踩过的坑5.1 Jenkins插件版本不兼容现象安装最新的GitLab Plugin 1.5.0后Webhook触发时报403 Forbidden原因Jenkins版本是2.346和插件1.5.0不兼容解决降级GitLab Plugin到2.36.0版本重启Jenkins后解决5.2 Docker镜像推送失败现象执行docker push时报x509: certificate signed by unknown authority原因Harbor仓库用的是自签名证书Docker默认不信任解决在Jenkins节点上配置Docker的insecure-registries// /etc/docker/daemon.json{insecure-registries:[harbor.example.com]}然后重启Docker服务5.3 K8s部署权限问题现象执行kubectl set image时报Error from server (Forbidden): deployments.apps is forbidden原因Jenkins使用的kubeconfig文件权限不足没有test和prod命名空间的部署权限解决在K8s中创建ServiceAccount绑定edit角色apiVersion:v1kind:ServiceAccountmetadata:name:jenkins-sanamespace:test---apiVersion:rbac.authorization.k8s.io/v1kind:RoleBindingmetadata:name:jenkins-rolebindingnamespace:testsubjects:-kind:ServiceAccountname:jenkins-sanamespace:testroleRef:kind:Rolename:editapiGroup:rbac.authorization.k8s.io5.4 流水线执行到一半卡住现象单元测试阶段执行了10分钟还没结束原因单元测试中用了SpringBootTest加载了完整的Spring上下文且有一个测试用例死锁解决优化单元测试减少SpringBootTest的使用用MockBean模拟依赖执行时间从10分钟降到1分钟5.5 Webhook触发重复执行现象一次代码提交触发了3次流水线执行原因GitLab Webhook配置了Push events和Merge Request events且合并请求时也会触发push事件解决在Jenkins的流水线中增加分支过滤只处理main分支的push事件六、总结与思考6.1 核心总结JenkinsGitLab CI/CD的组合完美覆盖了从代码提交到生产部署的全流程核心价值在于效率提升原来30分钟的部署流程缩短到8分钟质量保障自动化测试代码质量检查提前拦截低质量代码可追溯性所有部署记录都在Jenkins和GitLab中留存出现问题可快速回滚减少人为错误零手动操作避免漏打包、错配置等问题6.2 思考题如果要在CI/CD流程中加入蓝绿部署或金丝雀发布应该如何修改流水线配置如何优化流水线的执行速度比如缓存Maven依赖、并行执行测试等对于多环境开发、测试、预发、生产的部署如何复用流水线配置6.3 个人观点CI/CD不是银弹需要根据团队规模和项目特点灵活调整小团队5人以下直接用GitLab CI即可无需引入Jenkins减少维护成本中大型团队Jenkins的插件生态更丰富适合复杂场景的定制化需求核心是适合自己不要为了用工具而用工具解决团队的痛点才是关键另外CI/CD只是DevOps的一部分还要配合监控、告警、可观测性体系才能构建完整的研发运维闭环。