第六章:Pipeline 实战

常见场景的 Pipeline 模板和最佳实践,包括 Java、Node.js、Python、Go 等项目。

最后更新: 2024-01-15
页面目录

Pipeline 实战

本章提供多种常见场景的 Pipeline 模板,帮助您快速构建适合自己项目的流水线。

Java/Maven 项目

基础 Maven Pipeline

// Jenkinsfile for Maven Project
@Library('shared-library') _

pipeline {
    agent {
        docker {
            image 'maven:3.8-openjdk-11'
            args '-v $HOME/.m2:/root/.m2'
            reuseNode true
        }
    }
    
    options {
        timestamps()
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
    }
    
    parameters {
        choice(name: 'MAVEN_GOAL', choices: ['package', 'install', 'deploy'], description: 'Maven 目标')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
    }
    
    environment {
        MAVEN_OPTS = '-Xmx512m'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_COMMIT_SHORT = sh(
                        script: 'git rev-parse --short HEAD',
                        returnStdout: true
                    ).trim()
                }
            }
        }
        
        stage('Build') {
            steps {
                sh 'mvn clean ${MAVEN_GOAL} ${SKIP_TESTS ? "-DskipTests" : ""}'
            }
            post {
                success {
                    archiveArtifacts artifacts: 'target/*.jar', fingerprint: true, onlyIfSuccessful: true
                }
            }
        }
        
        stage('Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/**/*.xml'
                    jacoco execPattern: 'target/jacoco.exec'
                }
            }
        }
        
        stage('Code Quality') {
            steps {
                sh 'mvn verify -DskipTests -Dspotbugs.skip=true -Dcheckstyle.skip=true'
                recordIssues(
                    enabledForFailure: true,
                    tools: [java(), javaDoc()],
                    qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
                )
            }
        }
        
        stage('Docker Build & Push') {
            when {
                branch 'main'
            }
            steps {
                script {
                    def imageTag = "${env.REGISTRY}/${env.IMAGE_NAME}:${env.GIT_COMMIT_SHORT}"
                    def imageLatest = "${env.REGISTRY}/${env.IMAGE_NAME}:latest"
                    
                    withCredentials([usernamePassword(
                        credentialsId: 'docker-hub',
                        usernameVariable: 'DOCKER_USER',
                        passwordVariable: 'DOCKER_PASS'
                    )]) {
                        sh """
                            echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin ${env.REGISTRY}
                            docker build -t ${imageTag} -t ${imageLatest} .
                            docker push ${imageTag}
                            docker push ${imageLatest}
                        """
                    }
                }
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    kubectl config use-context staging
                    kubectl set image deployment/app app=${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT_SHORT}
                    kubectl rollout status deployment/app
                '''
            }
        }
    }
    
    post {
        success {
            emailext(
                subject: "✅ 构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: """
                    项目: ${env.JOB_NAME}
                    构建号: ${env.BUILD_NUMBER}
                    分支: ${env.BRANCH_NAME}
                    Commit: ${env.GIT_COMMIT_SHORT}
                    状态: 成功
                """,
                recipientProviders: [developers(), requestor()]
            )
        }
        failure {
            emailext(
                subject: "❌ 构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: """
                    项目: ${env.JOB_NAME}
                    构建号: ${env.BUILD_NUMBER}
                    分支: ${env.BRANCH_NAME}
                    状态: 失败
                    日志: ${env.BUILD_URL}console
                """,
                recipientProviders: [developers(), requestor()]
            )
        }
        always {
            cleanWs()
        }
    }
}

多模块 Maven 项目

pipeline {
    agent { label 'maven' }
    
    stages {
        stage('Build All Modules') {
            steps {
                sh 'mvn clean install -DskipTests'
            }
        }
        
        stage('Test') {
            parallel {
                stage('Module A Tests') {
                    steps {
                        dir('module-a') {
                            sh 'mvn test'
                        }
                    }
                }
                stage('Module B Tests') {
                    steps {
                        dir('module-b') {
                            sh 'mvn test'
                        }
                    }
                }
            }
        }
    }
}

Node.js/JavaScript 项目

NPM 项目 Pipeline

// Jenkinsfile for Node.js Project
pipeline {
    agent {
        docker {
            image 'node:18-alpine'
            args '-v $HOME/.npm:/root/.npm'
        }
    }
    
    environment {
        NODE_ENV = 'test'
        REGISTRY = 'registry.example.com'
    }
    
    stages {
        stage('Install Dependencies') {
            steps {
                sh '''
                    npm config set registry https://registry.npmmirror.com
                    npm ci --legacy-peer-deps
                '''
                stash 'node_modules'
            }
        }
        
        stage('Lint') {
            steps {
                unstash 'node_modules'
                sh '''
                    npm run lint || true
                    npm run lint:style
                '''
                recordIssues(
                    enabledForFailure: true,
                    tools: [eslint()],
                    qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
                )
            }
        }
        
        stage('Build') {
            steps {
                unstash 'node_modules'
                sh '''
                    npm run build
                '''
                stash name: 'build-output', includes: 'dist/**/*'
            }
        }
        
        stage('Test') {
            steps {
                unstash 'node_modules'
                sh '''
                    npm run test:unit
                    npm run test:e2e
                '''
            }
            post {
                always {
                    junit 'coverage/junit/*.xml'
                    publishHTML([
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepOnNewChange: true,
                        reportDir: 'coverage/html',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                unstash 'build-output'
                script {
                    def imageName = "${env.REGISTRY}/frontend:${env.BUILD_NUMBER}"
                    sh """
                        docker build -t ${imageName} -t ${env.REGISTRY}/frontend:latest .
                        docker push ${imageName}
                    """
                }
            }
        }
    }
    
    post {
        cleanup {
            sh 'npm cache clean --force'
        }
    }
}

Python 项目

Django/Python Pipeline

// Jenkinsfile for Python/Django Project
pipeline {
    agent {
        docker {
            image 'python:3.11-slim'
            args '-v $HOME/.cache/pip:/root/.cache/pip'
        }
    }
    
    environment {
        PYTHONUNBUFFERED = '1'
        DATABASE_URL = credentials('database-url')
        REDIS_URL = credentials('redis-url')
    }
    
    stages {
        stage('Setup') {
            steps {
                sh '''
                    python -m venv venv
                    . venv/bin/activate
                    pip install --upgrade pip
                    pip install -r requirements.txt
                    pip install -r requirements-dev.txt
                '''
            }
        }
        
        stage('Lint') {
            steps {
                sh '''
                    . venv/bin/activate
                    flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
                    black --check --diff .
                    isort --check-only --diff .
                '''
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    . venv/bin/activate
                    pytest --cov=myapp --cov-report=xml --cov-report=html
                '''
            }
            post {
                always {
                    junit 'test-results/*.xml'
                    cobertura coberturaReportFile: 'coverage.xml'
                }
            }
        }
        
        stage('Security Scan') {
            steps {
                sh '''
                    . venv/bin/activate
                    safety check
                    bandit -r myapp
                '''
            }
        }
        
        stage('Build Docker') {
            steps {
                sh '''
                    docker build -t myapp:${BUILD_NUMBER} .
                    docker tag myapp:${BUILD_NUMBER} myapp:latest
                '''
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    docker stop myapp || true
                    docker rm myapp || true
                    docker run -d --name myapp \
                        -e DATABASE_URL=${DATABASE_URL} \
                        -e REDIS_URL=${REDIS_URL} \
                        -p 8000:8000 \
                        myapp:latest
                '''
            }
        }
    }
}

Go 项目

Go 应用 Pipeline

// Jenkinsfile for Go Project
pipeline {
    agent {
        docker {
            image 'golang:1.21-alpine'
            args '-v $HOME/go:/go -w /workspace'
        }
    }
    
    environment {
        CGO_ENABLED = '0'
        GOOS = 'linux'
        GOARCH = 'amd64'
        REGISTRY = 'registry.example.com'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Dependencies') {
            steps {
                sh '''
                    go mod download
                    go mod verify
                '''
            }
        }
        
        stage('Lint') {
            steps {
                sh '''
                    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
                    golangci-lint run ./...
                '''
            }
        }
        
        stage('Build') {
            steps {
                sh '''
                    go build -ldflags="-s -w" -o bin/app .
                    file bin/app
                '''
            }
            post {
                success {
                    archiveArtifacts artifacts: 'bin/app', fingerprint: true
                }
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    go test -v -race -coverprofile=coverage.out ./...
                    go tool cover -html=coverage.out -o coverage.html
                '''
            }
            post {
                always {
                    junit '**/test-results/*.xml'
                    publishHTML([
                        reportDir: '.',
                        reportFiles: 'coverage.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
        
        stage('Build Docker Image') {
            steps {
                script {
                    def image = "${env.REGISTRY}/app:${env.BUILD_NUMBER}"
                    sh """
                        docker build -t ${image} .
                        docker push ${image}
                    """
                }
            }
        }
        
        stage('Helm Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    helm upgrade --install myapp ./chart \
                        --set image.tag=${BUILD_NUMBER} \
                        --wait --timeout 5m
                '''
            }
        }
    }
}

多环境部署 Pipeline

GitOps 风格部署

// GitOps Pipeline
pipeline {
    agent any
    
    parameters {
        choice(
            name: 'ENV',
            choices: ['dev', 'staging', 'production'],
            description: '部署环境'
        )
        string(
            name: 'VERSION',
            defaultValue: '',
            description: '版本号(留空使用最新)'
        )
    }
    
    environment {
        KUBECONFIG = credentials('kubeconfig')
    }
    
    stages {
        stage('Validate Environment') {
            steps {
                script {
                    assert env.ENV : "ENV 参数不能为空"
                    def allowedEnvs = ['dev', 'staging', 'production']
                    assert env.ENV in allowedEnvs : "不支持的环境: ${env.ENV}"
                    
                    if (env.ENV == 'production' && env.BRANCH_NAME != 'main') {
                        error "生产环境部署必须使用 main 分支"
                    }
                }
            }
        }
        
        stage('Prepare') {
            steps {
                script {
                    env.IMAGE_TAG = params.VERSION ?: env.GIT_COMMIT_SHORT
                    env.HELM_VALUES_FILE = "values-${env.ENV}.yaml"
                }
                
                checkout scm
            }
        }
        
        stage('Deploy to ${ENV}') {
            steps {
                sh '''
                    kubectl config use-context ${ENV}
                    
                    helm upgrade --install myapp ./charts/myapp \
                        --namespace ${ENV} \
                        --create-namespace \
                        --values ${HELM_VALUES_FILE} \
                        --set image.tag=${IMAGE_TAG} \
                        --wait \
                        --timeout 10m \
                        --debug
                '''
            }
        }
        
        stage('Verify Deployment') {
            steps {
                script {
                    def timeout = 300 // 5 minutes
                    def interval = 10
                    def elapsed = 0
                    
                    while (elapsed < timeout) {
                        def status = sh(
                            script: """
                                kubectl get pod -n ${ENV} -l app=myapp \
                                    -o jsonpath='{.items[0].status.phase}'
                            """,
                            returnStdout: true
                        ).trim()
                        
                        if (status == 'Running') {
                            echo "应用已成功启动"
                            break
                        }
                        
                        sleep interval
                        elapsed += interval
                    }
                    
                    if (elapsed >= timeout) {
                        error "应用启动超时"
                    }
                }
            }
        }
        
        stage('Health Check') {
            steps {
                sh '''
                    curl -f http://myapp.${ENV}.example.com/health || exit 1
                '''
            }
        }
    }
    
    post {
        success {
            echo "部署成功!环境: ${ENV}, 版本: ${IMAGE_TAG}"
        }
        failure {
            script {
                def rollbackCmd = "helm rollback myapp -n ${ENV}"
                echo "部署失败,可执行回滚: ${rollbackCmd}"
            }
        }
    }
}

微服务 Pipeline

Monorepo 微服务架构

// Jenkinsfile for Microservices
@Library('shared-library') _

def services = [
    [name: 'user-service', port: 8081, healthPath: '/actuator/health'],
    [name: 'order-service', port: 8082, healthPath: '/actuator/health'],
    [name: 'payment-service', port: 8083, healthPath: '/health'],
    [name: 'notification-service', port: 8084, healthPath: '/health']
]

def changedServices = []

pipeline {
    agent none
    
    options {
        timestamps()
        timeout(time: 2, unit: 'HOURS')
        disableConcurrentBuilds()
    }
    
    stages {
        stage('Detect Changed Services') {
            steps {
                script {
                    def changedFiles = sh(
                        script: 'git diff --name-only $COMMIT_RANGE',
                        returnStdout: true
                    ).split('\n')
                    
                    services.each { svc ->
                        def svcName = svc.name
                        if (changedFiles.any { it.startsWith(svcName) || it == "services/${svcName}/Dockerfile" }) {
                            changedServices.add(svc)
                            echo "检测到变更: ${svcName}"
                        }
                    }
                    
                    if (changedServices.isEmpty()) {
                        echo "没有检测到服务变更,跳过构建"
                    }
                }
            }
        }
        
        stage('Build Changed Services') {
            when {
                expression { !changedServices.isEmpty() }
            }
            steps {
                script {
                    def builds = [:]
                    
                    changedServices.each { svc ->
                        def service = svc
                        builds[service.name] = {
                            node('docker') {
                                stage("${service.name} - Build") {
                                    dir("services/${service.name}") {
                                        sh """
                                            docker build -t ${service.name}:${BUILD_NUMBER} .
                                            docker tag ${service.name}:${BUILD_NUMBER} ${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
                                            docker push ${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
                                        """
                                    }
                                }
                            }
                        }
                    }
                    
                    parallel builds
                }
            }
        }
        
        stage('Integration Tests') {
            when {
                expression { !changedServices.isEmpty() }
            }
            steps {
                script {
                    def tests = [:]
                    
                    changedServices.each { svc ->
                        def service = svc
                        tests[service.name] = {
                            node('docker') {
                                stage("${service.name} - Test") {
                                    sh """
                                        docker-compose -f docker-compose.test.yml up -d ${service.name}
                                        sleep 10
                                        curl -f http://localhost:${service.port}${service.healthPath}
                                        docker-compose -f docker-compose.test.yml down
                                    """
                                }
                            }
                        }
                    }
                    
                    parallel tests
                }
            }
        }
        
        stage('Deploy') {
            when {
                allOf {
                    branch 'main'
                    expression { !changedServices.isEmpty() }
                }
            }
            steps {
                script {
                    def deploys = [:]
                    
                    changedServices.each { svc ->
                        def service = svc
                        deploys[service.name] = {
                            sh """
                                kubectl set image deployment/${service.name} \
                                    ${service.name}=${env.REGISTRY}/${service.name}:${BUILD_NUMBER}
                                kubectl rollout status deployment/${service.name}
                            """
                        }
                    }
                    
                    parallel deploys
                }
            }
        }
    }
}

下一步

接下来让我们学习如何管理 Jenkins 节点和代理。

👉 节点管理


💡 提示:建议将这些 Pipeline 模板保存到共享库中,方便团队复用。