Jenkins pipeline例子

Published on
3 30.2~38.8 min

这个例子展示了一个包含构建、测试、安全扫描、多环境部署(预发/生产)和金丝雀发布的完整流程。

JAVA

pipeline {
    agent {
        kubernetes {
            label 'backend-java-agent'
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.8.6-openjdk-17
    command: ['cat']
    tty: true
    resources:
      requests:
        cpu: "1000m"
        memory: "2Gi"
    volumeMounts:
    - name: maven-cache
      mountPath: /root/.m2
  - name: docker
    image: docker:20.10
    command: ['cat']
    tty: true
    securityContext:
      privileged: true
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
  - name: kubectl
    image: bitnami/kubectl:1.25
    command: ['cat']
    tty: true
  volumes:
  - name: maven-cache
    persistentVolumeClaim:
      claimName: maven-cache-pvc
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
"""
        }
    }

    environment {
        // 镜像仓库配置
        REGISTRY = "harbor.yourcompany.com"
        PROJECT = "myapp"
        SERVICE = "backend"
        IMAGE_NAME = "${REGISTRY}/${PROJECT}/${SERVICE}"
        
        // 环境配置
        K8S_NAMESPACE_DEV = "myapp-dev"
        K8S_NAMESPACE_STAGING = "myapp-staging"
        K8S_NAMESPACE_PROD = "myapp-prod"
        
        // 动态生成的标签
        BRANCH_NAME = "${env.GIT_BRANCH.replace('origin/', '')}"
        COMMIT_SHORT = "${env.GIT_COMMIT.take(8)}"
        BUILD_TIME = sh(script: "date +%Y%m%d-%H%M%S", returnStdout: true).trim()
        IMAGE_TAG = "${BRANCH_NAME}-${COMMIT_SHORT}-${BUILD_TIME}"
    }

    stages {
        // 阶段1: 代码检查与质量门禁
        stage('代码检查') {
            steps {
                container('maven') {
                    script {
                        dir('backend') {
                            // 1.1 代码格式化检查
                            sh '''
                            mvn spotless:check
                            if [ $? -ne 0 ]; then
                                echo "代码格式不符合规范,请运行: mvn spotless:apply"
                                exit 1
                            fi
                            '''
                            
                            // 1.2 依赖漏洞扫描
                            sh 'mvn org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7'
                            
                            // 1.3 代码静态分析 (SonarQube)
                            withSonarQubeEnv('sonar-server') {
                                sh '''
                                mvn clean verify sonar:sonar \
                                    -Dsonar.projectKey=myapp-backend \
                                    -Dsonar.branch.name=${BRANCH_NAME}
                                '''
                            }
                            
                            // 1.4 等待SonarQube质量门禁结果
                            timeout(time: 10, unit: 'MINUTES') {
                                waitForQualityGate abortPipeline: true
                            }
                        }
                    }
                }
            }
        }

        // 阶段2: 构建与测试
        stage('构建与测试') {
            steps {
                container('maven') {
                    script {
                        dir('backend') {
                            // 2.1 运行所有测试(单元测试 + 集成测试)
                            sh '''
                            mvn clean test \
                                -DskipITs=false \
                                -Dtest.parallel=classes \
                                -Dtest.threadCount=4
                            '''
                            
                            // 2.2 生成测试报告和覆盖率
                            sh '''
                            mvn jacoco:report
                            mvn surefire-report:report-only
                            '''
                            
                            // 2.3 构建JAR包(用于归档)
                            sh 'mvn clean package -DskipTests'
                            
                            // 存档构建产物
                            archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                        }
                    }
                }
            }
            post {
                always {
                    // 发布测试报告
                    junit 'backend/target/surefire-reports/*.xml'
                    publishHTML(target: [
                        reportName: '后端测试报告',
                        reportDir: 'backend/target/site',
                        reportFiles: 'surefire-report.html',
                        keepAll: true
                    ])
                }
            }
        }

        // 阶段3: Docker镜像构建与安全扫描
        stage('构建Docker镜像') {
            when {
                anyOf {
                    branch 'main'
                    branch 'develop'
                    expression { return env.CHANGE_ID } // MR/PR触发
                }
            }
            steps {
                script {
                    dir('backend') {
                        // 3.1 构建Docker镜像
                        sh """
                        docker build \
                            --build-arg JAR_FILE=target/*.jar \
                            --build-arg BUILD_TIME=${BUILD_TIME} \
                            --build-arg GIT_COMMIT=${env.GIT_COMMIT} \
                            -t ${IMAGE_NAME}:${IMAGE_TAG} \
                            -t ${IMAGE_NAME}:latest \
                            .
                        """
                        
                        // 3.2 镜像安全扫描 (Trivy)
                        sh """
                        trivy image \
                            --severity HIGH,CRITICAL \
                            --exit-code 1 \
                            --format template \
                            --template "@/usr/local/share/trivy/templates/html.tpl" \
                            -o trivy-report.html \
                            ${IMAGE_NAME}:${IMAGE_TAG}
                        """
                        
                        // 3.3 推送镜像到仓库
                        withCredentials([usernamePassword(
                            credentialsId: 'harbor-credentials',
                            usernameVariable: 'DOCKER_USER',
                            passwordVariable: 'DOCKER_PASSWORD'
                        )]) {
                            sh """
                            docker login -u $DOCKER_USER -p $DOCKER_PASSWORD ${REGISTRY}
                            docker push ${IMAGE_NAME}:${IMAGE_TAG}
                            docker push ${IMAGE_NAME}:latest
                            """
                        }
                    }
                }
            }
        }

        // 阶段4: 部署到开发环境(自动)
        stage('部署到开发环境') {
            when {
                branch 'develop'
            }
            steps {
                container('kubectl') {
                    script {
                        // 使用Kustomize进行环境配置管理
                        sh """
                        cd backend/k8s/overlays/development
                        kustomize edit set image backend=${IMAGE_NAME}:${IMAGE_TAG}
                        kubectl apply -k . -n ${K8S_NAMESPACE_DEV}
                        """
                        
                        // 等待部署完成
                        sh """
                        kubectl rollout status deployment/backend \
                            -n ${K8S_NAMESPACE_DEV} \
                            --timeout=300s
                        """
                        
                        // 运行冒烟测试
                        sh """
                        curl -f http://backend.${K8S_NAMESPACE_DEV}.svc.cluster.local:8080/actuator/health \
                            --retry 10 \
                            --retry-delay 5 \
                            --retry-max-time 60
                        """
                    }
                }
            }
        }

        // 阶段5: 部署到预发环境(手动触发)
        stage('部署到预发环境') {
            when {
                branch 'main'
            }
            input {
                message "是否部署到预发环境?"
                ok "确认部署"
                parameters {
                    string(
                        name: 'DEPLOY_VERSION',
                        defaultValue: "${IMAGE_TAG}",
                        description: '要部署的镜像版本'
                    )
                }
            }
            steps {
                container('kubectl') {
                    script {
                        // 5.1 部署到预发环境
                        sh """
                        cd backend/k8s/overlays/staging
                        kustomize edit set image backend=${IMAGE_NAME}:${DEPLOY_VERSION}
                        kubectl apply -k . -n ${K8S_NAMESPACE_STAGING}
                        kubectl rollout status deployment/backend \
                            -n ${K8S_NAMESPACE_STAGING} \
                            --timeout=300s
                        """
                        
                        // 5.2 执行API集成测试
                        dir('backend') {
                            sh '''
                            mvn test-compile failsafe:integration-test \
                                -Dit.test="*IntegrationTest" \
                                -Dbase.url=http://backend.${K8S_NAMESPACE_STAGING}.svc.cluster.local:8080
                            '''
                        }
                        
                        // 5.3 性能测试(采样)
                        sh """
                        wrk -t4 -c100 -d30s \
                            http://backend.${K8S_NAMESPACE_STAGING}.svc.cluster.local:8080/api/v1/products \
                            --latency
                        """
                    }
                }
            }
        }

        // 阶段6: 金丝雀发布到生产环境
        stage('生产环境金丝雀发布') {
            when {
                branch 'main'
                beforeInput true
            }
            input {
                message "是否开始生产环境金丝雀发布?"
                ok "开始发布"
            }
            steps {
                container('kubectl') {
                    script {
                        // 6.1 创建金丝雀部署(10%流量)
                        sh """
                        cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-canary
  labels:
    app: backend
    version: canary-${IMAGE_TAG}
spec:
  replicas: 1  # 10%的副本数
  selector:
    matchLabels:
      app: backend
      version: canary
  template:
    metadata:
      labels:
        app: backend
        version: canary
    spec:
      containers:
      - name: backend
        image: ${IMAGE_NAME}:${IMAGE_TAG}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
EOF
                        """
                        
                        // 6.2 等待金丝雀版本就绪
                        sleep time: 60  // 等待1分钟让应用启动
                        
                        // 6.3 配置Istio流量分流(10%到金丝雀)
                        sh """
                        cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: backend-vs
spec:
  hosts:
  - backend.prod.yourcompany.com
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: backend-canary
        port:
          number: 8080
  - route:
    - destination:
        host: backend
        port:
          number: 8080
      weight: 90
    - destination:
        host: backend-canary
        port:
          number: 8080
      weight: 10
EOF
                        """
                        
                        echo "金丝雀发布已启动。10%的流量将被导向新版本。"
                        echo "请监控以下指标:"
                        echo "1. 错误率: http://grafana.yourcompany.com/d/backend"
                        echo "2. 响应时间: http://grafana.yourcompany.com/d/latency"
                        echo "3. 业务指标: http://grafana.yourcompany.com/d/business"
                        
                        // 6.4 等待监控验证(15分钟)
                        sleep time: 900
                    }
                }
            }
        }

        // 阶段7: 全量发布或回滚
        stage('全量发布或回滚') {
            steps {
                container('kubectl') {
                    script {
                        // 检查金丝雀版本的健康状态
                        def canaryStatus = sh(
                            script: """
                            kubectl get pods -n ${K8S_NAMESPACE_PROD} \
                                -l version=canary \
                                -o jsonpath='{.items[*].status.containerStatuses[*].ready}' \
                            | grep -c true
                            """,
                            returnStdout: true
                        ).trim()
                        
                        def errorRate = sh(
                            script: """
                            curl -s http://prometheus.yourcompany.com/api/v1/query?query=rate(http_requests_total{status!~\"2..\"}[5m]) | jq '.data.result[0].value[1]'
                            """,
                            returnStdout: true
                        ).trim()
                        
                        if (canaryStatus == "1" && errorRate.toFloat() < 0.01) {
                            // 7.1 金丝雀健康,进行全量发布
                            input {
                                message "监控显示金丝雀版本运行正常。是否进行全量发布?"
                                ok "全量发布"
                            }
                            
                            sh """
                            # 更新主部署
                            kubectl set image deployment/backend \
                                backend=${IMAGE_NAME}:${IMAGE_TAG} \
                                -n ${K8S_NAMESPACE_PROD}
                            
                            kubectl rollout status deployment/backend \
                                -n ${K8S_NAMESPACE_PROD} \
                                --timeout=300s
                            
                            # 清理金丝雀部署
                            kubectl delete deployment backend-canary -n ${K8S_NAMESPACE_PROD}
                            
                            # 恢复流量配置
                            cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: backend-vs
spec:
  hosts:
  - backend.prod.yourcompany.com
  http:
  - route:
    - destination:
        host: backend
        port:
          number: 8080
EOF
                            """
                            
                            echo "✅ 全量发布完成"
                            
                        } else {
                            // 7.2 金丝雀不健康,触发自动回滚
                            echo "⚠️ 金丝雀版本异常,触发自动回滚"
                            
                            sh """
                            # 删除金丝雀部署
                            kubectl delete deployment backend-canary -n ${K8S_NAMESPACE_PROD}
                            
                            # 恢复流量配置
                            cat <<EOF | kubectl apply -n ${K8S_NAMESPACE_PROD} -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: backend-vs
spec:
  hosts:
  - backend.prod.yourcompany.com
  http:
  - route:
    - destination:
        host: backend
        port:
          number: 8080
EOF
                            """
                            
                            // 发送告警通知
                            slackSend(
                                channel: '#prod-alerts',
                                color: 'danger',
                                message: "后端金丝雀发布失败,已自动回滚。版本: ${IMAGE_TAG}。错误率: ${errorRate}"
                            )
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            // 清理Docker镜像节省空间
            sh """
            docker image prune -f --filter \"until=24h\"
            """
            
            // 归档安全扫描报告
            archiveArtifacts artifacts: 'backend/trivy-report.html', allowEmptyArchive: true
        }
        
        success {
            script {
                if (env.BRANCH_NAME == 'main') {
                    slackSend(
                        channel: '#deployments',
                        color: 'good',
                        message: "后端生产部署成功!\n版本: ${IMAGE_TAG}\n构建: ${env.BUILD_URL}"
                    )
                    
                    // 生成发布说明
                    sh """
                    echo "## 后端发布说明 ${IMAGE_TAG}" > release-notes.md
                    echo "构建时间: \$(date)" >> release-notes.md
                    echo "Git提交: ${env.GIT_COMMIT}" >> release-notes.md
                    echo "变更内容:" >> release-notes.md
                    git log --oneline -n 5 >> release-notes.md
                    """
                }
            }
        }
        
        failure {
            slackSend(
                channel: '#build-failures',
                color: 'danger',
                message: "后端Pipeline失败!\nJob: ${env.JOB_NAME}\n构建: ${env.BUILD_NUMBER}\n查看: ${env.BUILD_URL}"
            )
            
            // 自动创建JIRA问题(如果严重失败)
            script {
                if (currentBuild.resultIsWorseOrEqualTo('FAILURE')) {
                    jiraNewIssue(
                        projectKey: 'DEV',
                        issueType: 'Bug',
                        summary: "Pipeline构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                        description: "构建详情: ${env.BUILD_URL}",
                        priorityName: 'High'
                    )
                }
            }
        }
    }
}

前端

pipeline {
    agent {
        docker {
            image 'node:18-alpine'
            args '-p 5173:5173' // Vite开发服务器端口
        }
    }

    environment {
        // 前端专用环境变量
        APP_NAME = "myapp-frontend"
        NPM_REGISTRY = "https://registry.npmmirror.com"
        S3_BUCKET = "myapp-static-assets"
        CLOUDFRONT_ID = "E2ABCDEFGHIJK"
        
        // 构建产物路径
        DIST_DIR = "dist"
        BUILD_MODE = "${env.BRANCH_NAME == 'main' ? 'production' : 'staging'}"
    }

    stages {
        stage('依赖安装与缓存') {
            steps {
                script {
                    dir('frontend') {
                        // 配置npm镜像源
                        sh "npm config set registry ${NPM_REGISTRY}"
                        
                        // 检查package-lock.json变化
                        sh """
                        if [ -f package-lock.json ]; then
                            npm ci --prefer-offline
                        else
                            npm install --frozen-lockfile
                        fi
                        """
                        
                        // 缓存node_modules
                        stash name: 'node_modules', includes: 'node_modules/**'
                    }
                }
            }
        }

        stage('代码质量检查') {
            steps {
                script {
                    dir('frontend') {
                        // ESLint代码检查
                        sh """
                        npx eslint "src/**/*.{js,jsx,vue,ts,tsx}" \
                            --format html \
                            -o eslint-report.html || true
                        """
                        
                        // TypeScript类型检查
                        sh 'npx tsc --noEmit'
                        
                        // 单元测试(Jest/Vitest)
                        sh """
                        npm test -- \
                            --coverage \
                            --coverageReporters=html \
                            --passWithNoTests
                        """
                        
                        // 样式检查(Stylelint)
                        sh 'npx stylelint "src/**/*.{css,scss,vue}"'
                    }
                }
            }
            post {
                always {
                    // 发布代码质量报告
                    publishHTML(target: [
                        reportName: 'ESLint报告',
                        reportDir: 'frontend/',
                        reportFiles: 'eslint-report.html',
                        keepAll: true
                    ])
                    
                    // 发布测试覆盖率报告
                    publishHTML(target: [
                        reportName: '测试覆盖率',
                        reportDir: 'frontend/coverage/',
                        reportFiles: 'index.html',
                        keepAll: true
                    ])
                }
            }
        }

        stage('构建与优化') {
            steps {
                script {
                    dir('frontend') {
                        // 根据环境变量生成配置
                        sh """
                        cat > .env.${BUILD_MODE} << EOF
                        VITE_APP_VERSION=${env.BUILD_ID}
                        VITE_APP_BUILD_TIME=$(date +%Y-%m-%dT%H:%M:%S)
                        VITE_API_BASE_URL=${env.BRANCH_NAME == 'main' ? 'https://api.prod.yourcompany.com' : 'https://api.staging.yourcompany.com'}
                        VITE_ENABLE_ANALYTICS=${env.BRANCH_NAME == 'main' ? 'true' : 'false'}
                        EOF
                        """
                        
                        // 执行构建
                        sh """
                        npm run build:${BUILD_MODE} || \
                        npm run build -- --mode ${BUILD_MODE}
                        """
                        
                        // 构建产物分析
                        sh 'npx vite-bundle-analyzer dist/stats.html'
                        
                        // 检查构建产物
                        sh """
                        echo "构建产物大小:"
                        du -sh dist/
                        echo "文件数量:"
                        find dist/ -type f | wc -l
                        """
                    }
                }
            }
        }

        stage('Docker镜像构建') {
            when {
                anyOf {
                    branch 'main'
                    branch 'develop'
                }
            }
            steps {
                script {
                    // 多阶段Docker构建
                    sh """
                    docker build \
                        --target=${BUILD_MODE} \
                        --build-arg BUILD_MODE=${BUILD_MODE} \
                        -t frontend:${env.BUILD_ID} \
                        -f frontend/Dockerfile \
                        frontend/
                    """
                    
                    // 镜像扫描
                    sh 'trivy image --severity HIGH,CRITICAL frontend:${env.BUILD_ID}'
                    
                    // 推送到仓库
                    withCredentials([usernamePassword(
                        credentialsId: 'docker-hub',
                        usernameVariable: 'DOCKER_USER',
                        passwordVariable: 'DOCKER_PASSWORD'
                    )]) {
                        sh """
                        docker tag frontend:${env.BUILD_ID} ${REGISTRY}/frontend:${IMAGE_TAG}
                        docker push ${REGISTRY}/frontend:${IMAGE_TAG}
                        """
                    }
                }
            }
        }

        stage('部署到CDN(静态资源)') {
            when {
                branch 'main'
            }
            steps {
                script {
                    withAWS(credentials: 'aws-jenkins', region: 'us-east-1') {
                        // 上传到对象存储并设置缓存策略,以AWS S3为例,可换成阿里云/腾讯云等
                        sh """
                        aws s3 sync frontend/${DIST_DIR}/ s3://${S3_BUCKET}/v${env.BUILD_ID}/ \
                            --delete \
                            --cache-control "public, max-age=31536000"
                        
                        # 上传index.html(无缓存)
                        aws s3 cp frontend/${DIST_DIR}/index.html s3://${S3_BUCKET}/v${env.BUILD_ID}/ \
                            --cache-control "no-cache"
                        """
                        
                        // 创建CloudFront失效
                        sh """
                        aws cloudfront create-invalidation \
                            --distribution-id ${CLOUDFRONT_ID} \
                            --paths "/v${env.BUILD_ID}/*"
                        """
                    }
                }
            }
        }

        stage('部署到K8s') {
            steps {
                container('kubectl') {
                    script {
                        dir('frontend/k8s') {
                            // 使用Kustomize更新镜像标签
                            sh """
                            kustomize edit set image frontend=${REGISTRY}/frontend:${IMAGE_TAG}
                            
                            # 部署到对应环境
                            if [ "${env.BRANCH_NAME}" = "main" ]; then
                                kubectl apply -k overlays/production
                                kubectl rollout status deployment/frontend -n production
                            else
                                kubectl apply -k overlays/staging
                                kubectl rollout status deployment/frontend -n staging
                            fi
                            """
                        }
                    }
                }
            }
        }

        stage('端到端测试') {
            when {
                branch 'main'
            }
            steps {
                script {
                    dir('frontend') {
                        // 使用Cypress进行E2E测试
                        sh """
                        npx cypress run \
                            --config baseUrl=https://staging.yourcompany.com \
                            --record \
                            --key \$CYPRESS_KEY \
                            --parallel \
                            --ci-build-id ${env.BUILD_ID}
                        """
                    }
                }
            }
        }
    }

    post {
        always {
            // 清理工作空间
            cleanWs()
            
            // 存档构建产物
            archiveArtifacts artifacts: "frontend/${DIST_DIR}/**", fingerprint: true
        }
        
        success {
            // 发送构建成功通知
            slackSend(
                color: 'good',
                message: "前端${BUILD_MODE}构建成功!\n版本: v${env.BUILD_ID}\n预览: https://${env.BRANCH_NAME == 'main' ? 'prod' : 'staging'}.yourcompany.com"
            )
        }
    }
}


0