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

CI/CD 流水线设计实战

CI/CD 流水线设计实战

CI/CD(持续集成/持续部署)是现代软件交付的核心实践。通过自动化构建、测试、部署流程,可以快速、可靠地将代码变更交付到生产环境。本文详解 Jenkins、GitHub Actions、GitLab CI 等主流 CI/CD 工具的配置与最佳实践。

一、CI/CD 基础概念

1.1 核心概念

graph LR
    Git[代码提交<br/>Git] --> Build[构建<br/>Maven]
    Build --> Test[测试<br/>JUnit]
    Test --> Deploy[部署<br/>K8s]
    Deploy --> Monitor[监控<br/>Prometheus]
概念说明目标
CI(持续集成)频繁合并代码到主干,自动构建测试尽早发现问题
CD(持续交付)代码随时可部署到生产环境快速交付价值
CD(持续部署)代码自动部署到生产环境完全自动化

1.2 流水线设计原则

mindmap
  root((优秀流水线特征))
    快速反馈
      构建时间<10 分钟
      测试时间<5 分钟
    可靠性
      流水线成功率>95%
    可重复性
      任何环境都能<br/>复现相同结果
    可视化
      清晰展示每个<br/>阶段状态
    容错性
      失败后能<br/>快速恢复

二、Jenkins 流水线

2.1 Jenkins 安装

Docker 安装

# 启动 Jenkins
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_data:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts-jdk11

# 获取初始密码
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

# 访问 http://localhost:8080 完成安装

插件安装

必需插件:
├── Pipeline(流水线)
├── Pipeline: Stage View(阶段视图)
├── Git Plugin(Git 集成)
├── Maven Integration(Maven 集成)
├── Docker Plugin(Docker 集成)
├── Kubernetes Plugin(K8s 集成)
└── Blue Ocean(现代化 UI)

2.2 基础流水线

Jenkinsfile(声明式语法)

pipeline {
    agent any
    
    tools {
        maven 'Maven 3.8'  // 需在 Jenkins 配置
        jdk 'JDK 11'       // 需在 Jenkins 配置
    }
    
    environment {
        DOCKER_IMAGE = "myapp"
        DOCKER_REGISTRY = "registry.example.com"
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                echo "代码版本:${GIT_COMMIT}"
            }
        }
        
        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'
                    publishHTML(target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'target/site/jacoco',
                        reportFiles: 'index.html',
                        reportName: '代码覆盖率报告'
                    ])
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                script {
                    docker.build("${DOCKER_IMAGE}:${BUILD_ID}")
                }
            }
        }
        
        stage('Push') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
                        docker.image("${DOCKER_IMAGE}:${BUILD_ID}").push()
                        docker.image("${DOCKER_IMAGE}:${BUILD_ID}").push('latest')
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    kubectl set image deployment/myapp myapp=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${BUILD_ID}
                    kubectl rollout status deployment/myapp
                '''
            }
        }
    }
    
    post {
        always {
            cleanWs()
            echo '流水线完成'
        }
        success {
            echo '构建成功!'
        }
        failure {
            echo '构建失败!请检查日志'
            // 发送通知
            // mail to: 'team@example.com', subject: "构建失败:${env.JOB_NAME}", body: "请查看:${env.BUILD_URL}"
        }
    }
}

2.3 并行阶段

pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        
        stage('Test') {
            parallel {
                stage('单元测试') {
                    steps {
                        sh 'mvn test -Dtest=*Test'
                    }
                }
                
                stage('集成测试') {
                    steps {
                        sh 'mvn test -Dtest=*IT'
                    }
                }
                
                stage('代码质量') {
                    steps {
                        sh 'mvn sonar:sonar'
                    }
                }
            }
        }
        
        stage('Deploy') {
            steps {
                echo '部署到环境'
            }
        }
    }
}

2.4 多环境部署

pipeline {
    agent any
    
    environment {
        DOCKER_IMAGE = "myapp"
    }
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
                script {
                    docker.build("${DOCKER_IMAGE}:${BUILD_ID}")
                }
            }
        }
        
        stage('Deploy to Dev') {
            when {
                branch 'develop'
            }
            steps {
                deployToEnvironment('dev')
            }
        }
        
        stage('Deploy to Test') {
            when {
                branch 'test'
            }
            steps {
                deployToEnvironment('test')
            }
        }
        
        stage('Deploy to Prod') {
            when {
                branch 'main'
            }
            steps {
                input message: '确认部署到生产环境?', ok: '确认部署'
                deployToEnvironment('prod')
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
    }
}

// 自定义函数
def deployToEnvironment(String env) {
    echo "部署到 ${env} 环境"
    
    sh """
        kubectl --context=${env} set image deployment/myapp myapp=${DOCKER_IMAGE}:${BUILD_ID}
        kubectl --context=${env} rollout status deployment/myapp
    """
}

2.5 共享库

目录结构

jenkins-shared-library/
├── vars/
│   ├── commonPipeline.groovy
│   ├── deployToK8s.groovy
│   └── sendNotification.groovy
├── src/
│   └── com/
│       └── example/
│           └── JenkinsUtils.groovy
└── resources/
    └── config.yaml

共享库代码

// vars/commonPipeline.groovy
def call(Map config = [:]) {
    pipeline {
        agent any
        
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
            
            stage('Build') {
                steps {
                    sh config.buildCommand ?: 'mvn clean package -DskipTests'
                }
            }
            
            stage('Test') {
                steps {
                    sh config.testCommand ?: 'mvn test'
                }
            }
            
            stage('Push') {
                steps {
                    script {
                        docker.withRegistry(config.registry ?: 'https://registry.example.com', config.credentials ?: 'docker-credentials') {
                            docker.image("${config.image}:${BUILD_ID}").push()
                        }
                    }
                }
            }
        }
    }
}

使用共享库

@Library('jenkins-shared-library') _

commonPipeline(
    image: 'myapp',
    registry: 'https://registry.example.com',
    credentials: 'docker-credentials',
    buildCommand: 'mvn clean package -DskipTests',
    testCommand: 'mvn test'
)

三、GitHub Actions

3.1 基础配置

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

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

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: maven
      
      - name: Build with Maven
        run: mvn clean package -DskipTests
      
      - name: Run tests
        run: mvn test
      
      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: test-results
          path: target/surefire-reports/

3.2 Docker 构建与推送

# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [main, develop]
    tags: ['v*']
  pull_request:
    branches: [main]

jobs:
  docker:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Docker metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: myapp
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=sha,prefix=sha-
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=myapp:buildcache
          cache-to: type=registry,ref=myapp:buildcache,mode=max

3.3 多环境部署

# .github/workflows/deploy.yml
name: Deploy to Kubernetes

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'dev'
        type: choice
        options:
          - dev
          - test
          - prod

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment || 'dev' }}
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.26.0'
      
      - name: Configure kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
      
      - name: Deploy to Kubernetes
        run: |
          kubectl set image deployment/myapp myapp=myapp:${{ github.sha }}
          kubectl rollout status deployment/myapp
      
      - name: Verify deployment
        run: |
          kubectl get pods -l app=myapp
          kubectl get svc myapp-service

3.4 矩阵构建

# .github/workflows/matrix-build.yml
name: Matrix Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        java-version: [11, 17, 21]
        os: [ubuntu-latest, windows-latest, macos-latest]
      fail-fast: false
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up JDK ${{ matrix.java-version }}
        uses: actions/setup-java@v3
        with:
          java-version: ${{ matrix.java-version }}
          distribution: 'temurin'
          cache: maven
      
      - name: Build and test
        run: mvn clean verify
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-${{ matrix.java-version }}-${{ matrix.os }}
          path: target/*.jar

3.5 自定义 Action

# action.yml
name: 'Maven Build'
description: '执行 Maven 构建'
inputs:
  java-version:
    description: 'Java 版本'
    required: true
    default: '11'
  maven-args:
    description: 'Maven 参数'
    required: false
    default: 'clean package'
outputs:
  artifact-path:
    description: '构建产物路径'
    value: ${{ steps.build.outputs.artifact }}
runs:
  using: "composite"
  steps:
    - name: Set up JDK
      uses: actions/setup-java@v3
      with:
        java-version: ${{ inputs.java-version }}
        distribution: 'temurin'
        cache: maven
    
    - name: Build
      id: build
      shell: bash
      run: |
        mvn ${{ inputs.maven-args }}
        echo "artifact=target/*.jar" >> $GITHUB_OUTPUT

# 使用自定义 Action
- name: Build application
  uses: ./.github/actions/maven-build
  with:
    java-version: '11'
    maven-args: 'clean package -DskipTests'

四、GitLab CI

4.1 基础配置

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

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .m2/repository/
    - target/

build:
  stage: build
  image: maven:3.8-openjdk-11
  script:
    - mvn clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 week

test:
  stage: test
  image: maven:3.8-openjdk-11
  script:
    - mvn test
  artifacts:
    reports:
      junit: target/surefire-reports/*.xml
    paths:
      - target/site/jacoco/
    expire_in: 1 week

docker-build:
  stage: package
  image: docker:20.10
  services:
    - docker:20.10-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - main
    - develop

deploy-dev:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/myapp myapp=$DOCKER_IMAGE
    - kubectl rollout status deployment/myapp
  environment:
    name: development
    url: https://dev.example.com
  only:
    - develop

deploy-prod:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/myapp myapp=$DOCKER_IMAGE
    - kubectl rollout status deployment/myapp
  environment:
    name: production
    url: https://www.example.com
  when: manual
  only:
    - main

4.2 多环境配置

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

include:
  - local: '/.gitlab/build.yml'
  - local: '/.gitlab/test.yml'
  - local: '/.gitlab/deploy.yml'

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG
# .gitlab/deploy.yml
deploy:dev:
  stage: deploy
  script:
    - echo "部署到开发环境"
  environment:
    name: development
    url: https://dev.example.com
    on_stop: stop:dev
    auto_stop_in: 1 week
  only:
    - develop

deploy:test:
  stage: deploy
  script:
    - echo "部署到测试环境"
  environment:
    name: testing
    url: https://test.example.com
  only:
    - test

deploy:prod:
  stage: deploy
  script:
    - echo "部署到生产环境"
  environment:
    name: production
    url: https://www.example.com
  when: manual
  only:
    - main

stop:dev:
  stage: deploy
  script:
    - echo "停止开发环境"
  environment:
    name: development
    action: stop
  when: manual
  only:
    - develop

五、最佳实践

5.1 流水线优化

问题:构建时间长

解决方案

# 1. 缓存依赖
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .m2/repository/

# 2. 并行执行
test:
  parallel:
    matrix:
      - TEST_GROUP: [unit, integration, e2e]

# 3. 增量构建
build:
  script:
    - mvn clean package -DskipTests -T 1C  # 多线程构建

# 4. 只构建变更部分
build:
  rules:
    - changes:
        - src/**/*
        - pom.xml

5.2 安全实践

# 1. 使用 Secret 管理敏感信息
# GitHub Secrets / GitLab Variables / Jenkins Credentials

# 2. 最小权限原则
permissions:
  contents: read
  packages: write

# 3. 依赖扫描
dependency-check:
  script:
    - mvn org.owasp:dependency-check-maven:check

# 4. 镜像扫描
scan:
  script:
    - docker scan $DOCKER_IMAGE

# 5. 签名验证
sign:
  script:
    - cosign sign $DOCKER_IMAGE

5.3 监控与告警

// Jenkins 监控
post {
    always {
        script {
            // 发送构建指标到 Prometheus
            def duration = currentBuild.duration
            def status = currentBuild.result ?: 'SUCCESS'
            
            // 发送到监控系统
            httpRequest url: "http://prometheus-pushgateway:9091/metrics",
                httpMode: 'POST',
                contentType: 'TEXT',
                requestBody: "build_duration{job='${JOB_NAME}'} ${duration}\nbuild_status{job='${JOB_NAME}',status='${status}'} 1"
        }
    }
    
    failure {
        // 发送告警通知
        slackSend channel: '#alerts',
            color: 'danger',
            message: "构建失败:${JOB_NAME} #${BUILD_NUMBER}\n${BUILD_URL}"
    }
}

5.4 流水线即代码

mindmap
  root((流水线即代码项目结构))
    .github/workflows
      ci.yml 持续集成
      cd.yml 持续部署
      security.yml 安全扫描
    Jenkinsfile Jenkins 流水线
    .gitlab-ci.yml GitLab CI
    .drone.yml Drone CI

六、总结

6.1 工具选择

场景推荐工具理由
GitHub 项目GitHub Actions原生集成、免费额度
GitLab 项目GitLab CI原生集成、功能完整
企业自建Jenkins灵活、插件丰富
云原生Tekton/ArgoKubernetes 原生

6.2 核心要点

  1. 快速反馈:构建时间控制在 10 分钟内
  2. 可靠性:流水线成功率 > 95%
  3. 安全性:Secret 管理、依赖扫描、镜像签名
  4. 可维护性:流水线即代码、共享库、模板化
  5. 可观测性:日志、指标、告警完善

CI/CD 不是一蹴而就的,持续改进才能发挥最大价值。


分享这篇文章到:

上一篇文章
高并发系统设计实战
下一篇文章
Docker 容器化部署实战