Skip to content
清晨的一缕阳光
返回

Spring Boot CI/CD 流水线实战

前言

CI/CD(持续集成/持续部署)是现代软件开发的核心实践。Spring Boot 应用可以通过 Jenkins、GitLab CI、GitHub Actions 等工具实现自动化构建和部署。本文将介绍 Spring Boot CI/CD 的完整方案。

CI/CD 流程

1. 典型流程

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│  Code   │────▶│  Build  │────▶│  Test   │────▶│ Package │────▶│ Deploy  │
│  Push   │     │  Maven  │     │  Unit   │     │  Docker │     │  K8s    │
└─────────┘     └─────────┘     └─────────┘     └─────────┘     └─────────┘
     │               │               │               │               │
     ▼               ▼               ▼               ▼               ▼
  Git Push      Compile         Run Tests      Build Image     kubectl apply

2. 阶段说明

阶段说明工具
Code代码提交Git
Build编译构建Maven/Gradle
Test单元测试JUnit/Mockito
Package打包镜像Docker/Jib
Deploy部署应用K8s/Helm

GitHub Actions

1. 基础配置

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: maven
      
      - name: Build with Maven
        run: mvn -B clean package -DskipTests
      
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: demo-jar
          path: target/*.jar

2. 完整流水线

# .github/workflows/deploy.yml
name: Deploy Pipeline

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: maven
      
      - name: Run tests
        run: mvn -B test
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: target/site/jacoco/jacoco.xml
  
  build:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
  
  deploy-dev:
    needs: build
    runs-on: ubuntu-latest
    environment: development
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.28.0'
      
      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=kubeconfig
      
      - name: Deploy to K8s
        run: |
          kubectl set image deployment/demo demo=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          kubectl rollout status deployment/demo
  
  deploy-prod:
    needs: deploy-dev
    runs-on: ubuntu-latest
    environment: production
    if: startsWith(github.ref, 'refs/tags/v')
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
        with:
          version: 'v3.13.0'
      
      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBE_CONFIG_PROD }}" | base64 -d > kubeconfig
          export KUBECONFIG=kubeconfig
      
      - name: Deploy with Helm
        run: |
          helm upgrade demo ./helm/demo \
            --install \
            --namespace production \
            --set image.tag=${{ github.ref_name }} \
            --wait --timeout 5m

3. 环境变量和密钥

# 在 GitHub Settings → Secrets and variables 中配置
# Secrets:
# - KUBE_CONFIG: K8s 配置文件(base64 编码)
# - DOCKER_USERNAME: Docker 用户名
# - DOCKER_PASSWORD: Docker 密码
# - SONAR_TOKEN: SonarQube Token

# Variables:
# - SONAR_HOST_URL: SonarQube 地址
# - DEPLOY_ENV: 部署环境

GitLab CI

1. 基础配置

# .gitlab-ci.yml
stages:
  - build
  - test
  - package
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
  DOCKER_TLS_CERTDIR: ""

cache:
  paths:
    - .m2/repository/
    - target/

build:
  stage: build
  image: maven:3.9-eclipse-temurin-21
  script:
    - mvn clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 week

test:
  stage: test
  image: maven:3.9-eclipse-temurin-21
  script:
    - mvn test
  artifacts:
    reports:
      junit: target/surefire-reports/*.xml
    coverage: '/Total.*?([0-9]{1,3})%/'

package:
  stage: package
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main
    - develop

deploy-dev:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/demo demo=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n dev
  environment:
    name: development
    url: https://dev.example.com
  only:
    - develop

deploy-prod:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/demo demo=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n prod
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

2. 多环境配置

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

.deploy_template: &deploy_template
  image: bitnami/kubectl:latest
  before_script:
    - mkdir -p ~/.kube
    - echo "$KUBE_CONFIG" | base64 -d > ~/.kube/config

deploy-dev:
  <<: *deploy_template
  stage: deploy
  script:
    - kubectl apply -f k8s/dev/ -n dev
  environment:
    name: development
    url: https://dev.example.com
  only:
    - develop

deploy-test:
  <<: *deploy_template
  stage: deploy
  script:
    - kubectl apply -f k8s/test/ -n test
  environment:
    name: test
    url: https://test.example.com
  only:
    - test

deploy-prod:
  <<: *deploy_template
  stage: deploy
  script:
    - kubectl apply -f k8s/prod/ -n prod
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

3. GitLab Runner

# 安装 Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
apt-get install gitlab-runner

# 注册 Runner
gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token YOUR_TOKEN \
  --executor docker \
  --description "My Docker Runner" \
  --docker-image "docker:24" \
  --docker-volumes /var/run/docker.sock:/var/run/docker.sock

Jenkins

1. Jenkinsfile(声明式)

// Jenkinsfile
pipeline {
    agent any
    
    tools {
        maven 'Maven 3.9'
        jdk 'JDK 21'
    }
    
    environment {
        REGISTRY = 'registry.example.com'
        IMAGE_NAME = 'demo'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
            post {
                success {
                    archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                }
            }
        }
        
        stage('Test') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                    publishCoverage adapters: [jacocoAdapter('target/site/jacoco/jacoco.xml')]
                }
            }
        }
        
        stage('Build Docker Image') {
            steps {
                script {
                    docker.build("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_ID}")
                }
            }
        }
        
        stage('Push Docker Image') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'docker-credentials') {
                        docker.image("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_ID}").push()
                        docker.image("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_ID}").push('latest')
                    }
                }
            }
        }
        
        stage('Deploy to Dev') {
            when {
                branch 'develop'
            }
            steps {
                sh '''
                    kubectl set image deployment/demo demo=${REGISTRY}/${IMAGE_NAME}:${BUILD_ID} -n dev
                    kubectl rollout status deployment/demo -n dev
                '''
            }
        }
        
        stage('Deploy to Prod') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Deploy to production?', ok: 'Deploy'
                sh '''
                    kubectl set image deployment/demo demo=${REGISTRY}/${IMAGE_NAME}:${BUILD_ID} -n prod
                    kubectl rollout status deployment/demo -n prod
                '''
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        failure {
            mail to: 'team@example.com',
                 subject: "Build Failed: ${env.JOB_NAME} ${env.BUILD_ID}",
                 body: "Check console output at ${BUILD_URL}"
        }
    }
}

2. 多分支流水线

// Jenkinsfile
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        
        stage('Deploy') {
            steps {
                script {
                    if (env.BRANCH_NAME == 'main') {
                        deploy('prod')
                    } else if (env.BRANCH_NAME == 'develop') {
                        deploy('dev')
                    }
                }
            }
        }
    }
}

def deploy(String env) {
    // 部署逻辑
}

3. 共享库

// vars/springBootBuild.groovy
def call(Map config = [:]) {
    def mavenVersion = config.mavenVersion ?: 'Maven 3.9'
    def jdkVersion = config.jdkVersion ?: 'JDK 21'
    
    tools {
        maven mavenVersion
        jdk jdkVersion
    }
    
    stage('Build') {
        sh 'mvn clean package -DskipTests'
    }
    
    stage('Test') {
        sh 'mvn test'
        junit 'target/surefire-reports/*.xml'
    }
}
// Jenkinsfile
@Library('my-shared-library') _

pipeline {
    agent any
    
    stages {
        stage('Build and Test') {
            steps {
                springBootBuild(mavenVersion: 'Maven 3.9', jdkVersion: 'JDK 21')
            }
        }
    }
}

ArgoCD 持续部署

1. 安装 ArgoCD

# 安装
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 访问 UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

2. 创建应用

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/demo-k8s.git
    targetRevision: HEAD
    path: k8s/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
# 创建应用
kubectl apply -f argocd-app.yaml

# 查看状态
argocd app get demo

# 同步应用
argocd app sync demo

3. GitOps 流程

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│  Code   │────▶│   CI    │────▶│  Git    │────▶│ ArgoCD  │
│  Repo   │     │ Pipeline│     │  Ops    │     │ Sync    │
└─────────┘     └─────────┘     └─────────┘     └─────────┘
                    │               │               │
                    ▼               ▼               ▼
              Build & Test    Update Manifest    Deploy to K8s
              Push Image      k8s/prod/deployment.yaml

最佳实践

1. 流水线优化

# ✅ 推荐
# 并行执行
jobs:
  test:
    # 单元测试
  security-scan:
    # 安全扫描
  lint:
    # 代码检查

# 缓存依赖
cache:
  paths:
    - .m2/repository/

# ❌ 不推荐
# 串行执行所有任务
# 不缓存依赖

2. 安全配置

# ✅ 推荐
# 使用密钥管理
secrets:
  - KUBE_CONFIG
  - DOCKER_PASSWORD

# 扫描镜像
- name: Scan image
  uses: aquasecurity/trivy-action@master

# ❌ 不推荐
# 硬编码密钥
# 不扫描漏洞

3. 通知配置

# Slack 通知
- name: Notify Slack
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}

# 邮件通知
post {
    failure {
        mail to: 'team@example.com',
             subject: "Build Failed: ${env.JOB_NAME}"
    }
}

4. 版本管理

# 语义化版本
tags:
  - 'v1.0.0'
  - 'v1.0.1'
  - 'v1.1.0'

# Git 标签
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

总结

CI/CD 要点:

CI/CD 是 DevOps 实践的核心。


分享这篇文章到:

上一篇文章
网关统一鉴权
下一篇文章
Spring Boot 生产最佳实践