【架构实战】Jenkins+GitLab CI/CD:持续集成与持续部署实践

【架构实战】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"的模式:

  1. 开发人员提交代码到GitLab的指定分支(如main
  2. GitLab通过Webhook通知Jenkins有新的代码变更
  3. Jenkins拉取代码,执行预定义的流水线(构建、测试、打包、部署)
  4. 流水线执行结果反馈到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集成

  1. 在GitLab生成Access Token:进入User Settings → Access Tokens,勾选api权限,生成token后保存
  2. 在Jenkins安装GitLab Plugin,进入Manage Jenkins → Configure System → GitLab
    • 填写GitLab host URL:http://your-gitlab-url
    • Credentials选择GitLab API Token,填入上一步生成的token
    • 点击Test Connection验证连通性
  3. 配置Webhook:在GitLab项目的Settings → Webhooks
    • URL填写:http://your-jenkins-url/gitlab-webhook/
    • Secret Token:在Jenkins的GitLab配置中生成的Webhook Secret
    • 勾选Push events,分支过滤填写main

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集群中,完整流程如下:

  1. 开发人员完成需求开发,提交代码到main分支,触发GitLab Webhook
  2. Jenkins自动拉取代码,执行流水线:
    • 编译打包:耗时2分钟,生成coupon-service-1.0.0.jar
    • 单元测试:耗时1分钟,覆盖率85%,无失败用例
    • 代码质量检查:SonarQube评分A,无阻断性问题
    • 构建Docker镜像:基于openjdk:17-alpine,镜像大小280MB,推送到Harbor仓库
    • 部署到测试环境:K8s滚动更新,耗时30秒,无downtime
  3. 测试人员验证测试环境功能,确认通过后,手动点击Jenkins的"部署生产"按钮
  4. 生产环境滚动更新,健康检查通过后,流水线结束
  5. 总耗时:从代码提交到生产部署完成,约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文件权限不足,没有testprod命名空间的部署权限

  • 解决 :在K8s中创建ServiceAccount,绑定edit角色:

    yaml 复制代码
    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 eventsMerge Request events,且合并请求时也会触发push事件
  • 解决 :在Jenkins的流水线中增加分支过滤,只处理main分支的push事件

六、总结与思考

6.1 核心总结

Jenkins+GitLab CI/CD的组合完美覆盖了从代码提交到生产部署的全流程,核心价值在于:

  • 效率提升:原来30分钟的部署流程缩短到8分钟
  • 质量保障:自动化测试+代码质量检查,提前拦截低质量代码
  • 可追溯性:所有部署记录都在Jenkins和GitLab中留存,出现问题可快速回滚
  • 减少人为错误:零手动操作,避免漏打包、错配置等问题

6.2 思考题

  1. 如果要在CI/CD流程中加入"蓝绿部署"或"金丝雀发布",应该如何修改流水线配置?
  2. 如何优化流水线的执行速度?比如缓存Maven依赖、并行执行测试等?
  3. 对于多环境(开发、测试、预发、生产)的部署,如何复用流水线配置?

6.3 个人观点

CI/CD不是银弹,需要根据团队规模和项目特点灵活调整:

  • 小团队(5人以下):直接用GitLab CI即可,无需引入Jenkins,减少维护成本
  • 中大型团队:Jenkins的插件生态更丰富,适合复杂场景的定制化需求
  • 核心是"适合自己",不要为了用工具而用工具,解决团队的痛点才是关键

另外,CI/CD只是DevOps的一部分,还要配合监控、告警、可观测性体系,才能构建完整的研发运维闭环。

相关推荐
blue_dou2 小时前
2026主流CRM对比:工贸业财融合一体化选型解析
架构·逻辑回归·流程图
小新同学^O^2 小时前
简单学习 --> Transformer架构
学习·架构·transformer
fengxin_rou2 小时前
【Feed 高并发架构实战】:雪花 ID + 三级缓存 + 计数旁路设计详解
数据库·redis·缓存·架构·事务·并发
春天花会开13116 小时前
Kubernetes 高可用架构实战指南
架构
码云之上17 小时前
万星入坞·其三:SDK 轻量组件如何优雅地"点亮"
性能优化·架构·前端框架
枫叶林FYL17 小时前
【强化学习】3 双系统持续强化学习:快速迁移与元知识整合架构手册
人工智能·机器学习·架构
AI科技星17 小时前
哥德巴赫猜想1+1基于平行素数对等腰梯形网格拓扑与素数渐近密度的大偶数满填充完备性证明
人工智能·线性代数·架构·概率论·学习方法
小短腿的代码世界17 小时前
信号路由风暴:Qt算法交易系统的高频信号分发架构
qt·算法·架构
2301_7807896618 小时前
手游遇到攻击为什么要用SDK游戏盾手游遇到攻击为什么要用 SDK 游戏盾?
安全·web安全·游戏·架构·kubernetes·ddos