【架构实战】Jenkins+GitLab CI/CD:持续集成与持续部署实践
字数统计:约3800字
一、真实故事引入:从手动部署的噩梦到自动化救赎
2023年冬天,我刚入职一家生鲜电商公司,第一次参与大促前的上线。当时我们的部署流程还是纯手动:开发人员提交代码到GitLab,然后运维人员登录跳板机,拉取代码、手动执行mvn package打包、上传jar包到服务器、重启服务。那次上线前,我负责的一个优惠券服务因为打包时漏了一个application-prod.yml配置文件,导致线上服务启动失败,整个优惠券系统宕机了15分钟,影响了大促的预热活动,直接导致公司损失了近20万的订单额。
事故复盘会上,技术总监拍着桌子说:"再也不想看到手动部署了!" 于是我们痛定思痛,决定引入Jenkins+GitLab CI/CD来实现端到端的自动化部署,从此告别了手动部署的噩梦。这就是我和CI/CD的第一次亲密接触,也让我深刻理解了自动化部署对团队效率的保障作用。
二、概念原理:CI/CD到底是什么?
2.1 核心概念解析
- CI(持续集成):开发人员频繁地将代码合并到主干分支(通常每天多次),每次合并都触发自动构建和测试,尽早发现集成错误。核心目标是"早发现,早解决",避免集成地狱。
- CD(持续交付/持续部署) :
- 持续交付:自动将通过所有测试的代码构建成可部署的制品,随时可以手动部署到生产环境
- 持续部署:自动将通过所有测试的代码直接部署到生产环境,不需要人工干预
- Jenkins:开源的自动化服务器,拥有超过1800个插件,支持构建、测试、部署全流程的自动化,适合复杂场景的定制化需求
- GitLab CI/CD :GitLab内置的CI/CD能力,通过项目根目录的
.gitlab-ci.yml文件定义流水线,轻量且和代码仓库深度集成
2.2 两者集成的核心逻辑
我们的集成方案采用"GitLab触发Jenkins"的模式:
- 开发人员提交代码到GitLab的指定分支(如
main) - GitLab通过Webhook通知Jenkins有新的代码变更
- Jenkins拉取代码,执行预定义的流水线(构建、测试、打包、部署)
- 流水线执行结果反馈到GitLab的Pipeline页面
这种组合既利用了GitLab的代码管理能力,又发挥了Jenkins插件生态丰富的优势。
三、配置代码:从零搭建集成环境
3.1 基础环境准备
bash
# 1. 用Docker部署Jenkins(LTS版本)
docker run -d --name jenkins \
-p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts
# 2. 部署GitLab(如果还没部署的话)
docker run -d --name gitlab \
-p 443:443 -p 80:80 -p 22:22 \
-v gitlab_config:/etc/gitlab \
-v gitlab_logs:/var/log/gitlab \
-v gitlab_data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
3.2 Jenkins配置GitLab集成
- 在GitLab生成Access Token:进入
User Settings → Access Tokens,勾选api权限,生成token后保存 - 在Jenkins安装
GitLab Plugin,进入Manage Jenkins → Configure System → GitLab- 填写GitLab host URL:
http://your-gitlab-url - Credentials选择
GitLab API Token,填入上一步生成的token - 点击
Test Connection验证连通性
- 填写GitLab host URL:
- 配置Webhook:在GitLab项目的
Settings → Webhooks中- URL填写:
http://your-jenkins-url/gitlab-webhook/ - Secret Token:在Jenkins的GitLab配置中生成的Webhook Secret
- 勾选
Push events,分支过滤填写main
- URL填写:
3.3 核心流水线配置(Jenkinsfile)
我们在项目根目录创建Jenkinsfile(声明式流水线):
groovy
pipeline {
agent any
// 环境变量配置
environment {
DOCKER_REGISTRY = "harbor.example.com"
PROJECT_NAME = "coupon-service"
KUBECONFIG = credentials('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 {
sh 'mvn clean package -DskipTests'
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}
// 阶段3:单元测试
stage('单元测试') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
publishCoverage adapters: [jacocoAdapter('target/site/jacoco/jacoco.xml')], sourceFileResolver: sourceFiles('STORE_LAST_BUILD')
}
}
}
// 阶段4:代码质量检查(集成SonarQube)
stage('代码质量检查') {
steps {
sh 'mvn sonar:sonar -Dsonar.projectKey=coupon-service -Dsonar.host.url=http://sonarqube.example.com -Dsonar.login=${SONAR_TOKEN}'
}
}
// 阶段5:构建Docker镜像
stage('构建镜像') {
steps {
script {
def imageName = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER}"
sh "docker build -t ${imageName} ."
sh "docker push ${imageName}"
}
}
}
// 阶段6:部署到测试环境
stage('部署测试环境') {
steps {
sh "kubectl set image deployment/${PROJECT_NAME} ${PROJECT_NAME}=${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER} -n test"
sh "kubectl rollout status deployment/${PROJECT_NAME} -n test"
}
}
// 阶段7:自动化测试(可选)
stage('自动化测试') {
steps {
sh 'curl -X POST http://test.example.com/api/health-check'
}
}
}
// 流水线后置处理
post {
success {
echo '流水线执行成功!'
// 发送成功通知到企业微信
sh 'curl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"优惠券服务部署成功,版本:${BUILD_NUMBER}\"}}"'
}
failure {
echo '流水线执行失败!'
// 发送失败通知
sh 'curl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"优惠券服务部署失败,请查看控制台日志\"}}"'
}
}
}
3.4 GitLab CI补充配置(可选)
如果不需要Jenkins的复杂能力,也可以直接用GitLab CI,在项目根目录创建.gitlab-ci.yml:
yaml
stages:
- build
- test
- deploy
build_job:
stage: build
image: maven:3.8-openjdk-17
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
test_job:
stage: test
image: maven:3.8-openjdk-17
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/*.xml
deploy_job:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/coupon-service coupon-service=harbor.example.com/coupon-service:${CI_PIPELINE_ID} -n prod
only:
- main
四、实战案例:优惠券服务的CI/CD全流程
我们的优惠券服务是一个Spring Boot项目,部署在K8s集群中,完整流程如下:
- 开发人员完成需求开发,提交代码到
main分支,触发GitLab Webhook - Jenkins自动拉取代码,执行流水线:
- 编译打包:耗时2分钟,生成
coupon-service-1.0.0.jar - 单元测试:耗时1分钟,覆盖率85%,无失败用例
- 代码质量检查:SonarQube评分A,无阻断性问题
- 构建Docker镜像:基于
openjdk:17-alpine,镜像大小280MB,推送到Harbor仓库 - 部署到测试环境:K8s滚动更新,耗时30秒,无downtime
- 编译打包:耗时2分钟,生成
- 测试人员验证测试环境功能,确认通过后,手动点击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:json// /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角色:yamlapiVersion: v1 kind: ServiceAccount metadata: name: jenkins-sa namespace: test --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: jenkins-rolebinding namespace: test subjects: - kind: ServiceAccount name: jenkins-sa namespace: test roleRef: kind: Role name: edit apiGroup: rbac.authorization.k8s.io
5.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 核心总结
Jenkins+GitLab CI/CD的组合完美覆盖了从代码提交到生产部署的全流程,核心价值在于:
- 效率提升:原来30分钟的部署流程缩短到8分钟
- 质量保障:自动化测试+代码质量检查,提前拦截低质量代码
- 可追溯性:所有部署记录都在Jenkins和GitLab中留存,出现问题可快速回滚
- 减少人为错误:零手动操作,避免漏打包、错配置等问题
6.2 思考题
- 如果要在CI/CD流程中加入"蓝绿部署"或"金丝雀发布",应该如何修改流水线配置?
- 如何优化流水线的执行速度?比如缓存Maven依赖、并行执行测试等?
- 对于多环境(开发、测试、预发、生产)的部署,如何复用流水线配置?
6.3 个人观点
CI/CD不是银弹,需要根据团队规模和项目特点灵活调整:
- 小团队(5人以下):直接用GitLab CI即可,无需引入Jenkins,减少维护成本
- 中大型团队:Jenkins的插件生态更丰富,适合复杂场景的定制化需求
- 核心是"适合自己",不要为了用工具而用工具,解决团队的痛点才是关键
另外,CI/CD只是DevOps的一部分,还要配合监控、告警、可观测性体系,才能构建完整的研发运维闭环。