【架构实战】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的代码管理能力,又发挥了Jenkins插件生态丰富的优势。
三、配置代码:从零搭建集成环境
3.1 基础环境准备
# 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 host URL:http://your-gitlab-url
- Credentials选择GitLab API Token,填入上一步生成的token
- 点击Test Connection验证连通性
- URL填写:http://your-jenkins-url/gitlab-webhook/
- Secret Token:在Jenkins的GitLab配置中生成的Webhook Secret
- 勾选Push events,分支过滤填写main
3.3 核心流水线配置(Jenkinsfile)
我们在项目根目录创建Jenkinsfile(声明式流水线):
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:
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集群中,完整流程如下:
- 编译打包:耗时2分钟,生成coupon-service-1.0.0.jar
- 单元测试:耗时1分钟,覆盖率85%,无失败用例
- 代码质量检查:SonarQube评分A,无阻断性问题
- 构建Docker镜像:基于openjdk:17-alpine,镜像大小280MB,推送到Harbor仓库
- 部署到测试环境:K8s滚动更新,耗时30秒,无downtime
对比原来的手动部署流程(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: 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 思考题
6.3 个人观点
CI/CD不是银弹,需要根据团队规模和项目特点灵活调整:
- 小团队(5人以下):直接用GitLab CI即可,无需引入Jenkins,减少维护成本
- 中大型团队:Jenkins的插件生态更丰富,适合复杂场景的定制化需求
- 核心是"适合自己",不要为了用工具而用工具,解决团队的痛点才是关键
另外,CI/CD只是DevOps的一部分,还要配合监控、告警、可观测性体系,才能构建完整的研发运维闭环。


