多分支流水线 #

多分支流水线(Multibranch Pipeline)是Jenkins的一个强大特性,能够自动发现Git仓库中的分支并创建对应的Pipeline任务。

什么是多分支流水线? #

多分支流水线可以:

  • 自动发现Git仓库中的所有分支
  • 为每个分支创建独立的Pipeline任务
  • 支持Pull Request构建
  • 自动清理已删除分支的任务
text
┌─────────────────────────────────────────────────────────────┐
│                   Multibranch Pipeline                       │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │    main     │  │   develop   │  │ feature/xxx │         │
│  │             │  │             │  │             │         │
│  │  Pipeline   │  │  Pipeline   │  │  Pipeline   │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                              │
│  ┌─────────────┐  ┌─────────────┐                           │
│  │ release/1.0 │  │    PR #42   │                           │
│  │             │  │             │                           │
│  │  Pipeline   │  │  Pipeline   │                           │
│  └─────────────┘  └─────────────┘                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

创建多分支流水线 #

步骤1:创建任务 #

  1. 点击 New Item
  2. 输入任务名称
  3. 选择 Multibranch Pipeline
  4. 点击 OK

步骤2:配置分支源 #

GitHub #

text
Branch Sources:
  Add source: GitHub
  Owner: organization-name
  Repository: repo-name
  Credentials: github-token

Git #

text
Branch Sources:
  Add source: Git
  Repository URL: https://github.com/user/repo.git
  Credentials: github-creds

GitLab #

text
Branch Sources:
  Add source: GitLab
  Server: GitLab Server
  Owner: group-name
  Project: project-name
  Credentials: gitlab-token

Bitbucket #

text
Branch Sources:
  Add source: Bitbucket
  Server: Bitbucket Server
  Owner: team-name
  Repository: repo-name
  Credentials: bitbucket-creds

步骤3:配置构建策略 #

text
Build Configuration:
  Mode: by Jenkinsfile
  Script Path: Jenkinsfile

步骤4:配置扫描策略 #

text
Scan Multibranch Pipeline Triggers:
  ☑ Periodically if not otherwise run
  Interval: 1 minute

分支发现策略 #

发现分支 #

groovy
properties([
    pipelineTriggers([
        branchDiscovery: [
            strategyId: 1  // 排除也是PR的分支
        ]
    ])
])

策略选项:

策略 说明
1 排除也是PR的分支
2 只有被PR的分支
3 所有分支

发现PR #

text
Discover pull requests from origin:
  Strategy: Merging the pull request with the current target branch revision
  
Discover pull requests from forks:
  Strategy: Merging the pull request with the current target branch revision
  Trust: Trusted users

PR策略:

策略 说明
Merging 合并PR后构建
Head only 只构建PR源分支
Both 两种方式都构建

过滤分支 #

text
Filter by name (with regular expressions):
  Include: (feature|release|hotfix)/.*
  Exclude: (test|temp)/.*
text
Filter by name:
  Include: main, develop
  Exclude: test-*

Jenkinsfile配置 #

基本结构 #

groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                echo "Building branch: ${env.BRANCH_NAME}"
                sh 'mvn clean package'
            }
        }
        
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        
        stage('Deploy') {
            when {
                anyOf {
                    branch 'main'
                    branch 'master'
                }
            }
            steps {
                sh 'deploy-to-prod.sh'
            }
        }
    }
}

分支条件判断 #

groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        
        stage('Deploy to Dev') {
            when {
                branch 'develop'
            }
            steps {
                sh 'deploy-to-dev.sh'
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch pattern: 'release/*', comparator: 'GLOB'
            }
            steps {
                sh 'deploy-to-staging.sh'
            }
        }
        
        stage('Deploy to Prod') {
            when {
                anyOf {
                    branch 'main'
                    branch 'master'
                }
            }
            steps {
                input message: 'Deploy to production?'
                sh 'deploy-to-prod.sh'
            }
        }
    }
}

PR条件判断 #

groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        
        stage('Integration Test') {
            when {
                changeRequest()
            }
            steps {
                sh 'mvn verify -P integration'
            }
        }
        
        stage('Deploy') {
            when {
                allOf {
                    changeRequest target: 'main'
                    branch 'feature/*'
                }
            }
            steps {
                sh 'deploy-to-preview.sh'
            }
        }
    }
}

changeRequest条件 #

groovy
when {
    changeRequest()
}

when {
    changeRequest id: '42'
}

when {
    changeRequest target: 'main'
}

when {
    changeRequest branch: 'feature/*'
}

when {
    changeRequest author: 'john'
}

when {
    changeRequest title: 'fix:.*'
}

when {
    changeRequest url: 'https://github.com/*'
}

环境变量 #

多分支流水线提供额外的环境变量:

变量 说明 示例
BRANCH_NAME 分支名称 main, feature/xxx
CHANGE_ID PR编号 42
CHANGE_URL PR URL https://github.com/
CHANGE_TITLE PR标题 Fix bug
CHANGE_AUTHOR PR作者 john
CHANGE_TARGET PR目标分支 main
CHANGE_BRANCH PR源分支 feature/xxx

使用示例 #

groovy
pipeline {
    agent any
    
    stages {
        stage('Info') {
            steps {
                echo "Branch: ${env.BRANCH_NAME}"
                
                script {
                    if (env.CHANGE_ID) {
                        echo "PR #${env.CHANGE_ID}"
                        echo "Title: ${env.CHANGE_TITLE}"
                        echo "Author: ${env.CHANGE_AUTHOR}"
                        echo "Target: ${env.CHANGE_TARGET}"
                    }
                }
            }
        }
    }
}

分支命名约定 #

常见分支模型 #

Git Flow #

text
main        - 生产分支
develop     - 开发分支
feature/*   - 功能分支
release/*   - 发布分支
hotfix/*    - 热修复分支
groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        
        stage('Deploy to Dev') {
            when {
                branch 'develop'
            }
            steps {
                sh 'deploy.sh dev'
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch pattern: 'release/*', comparator: 'GLOB'
            }
            steps {
                sh 'deploy.sh staging'
            }
        }
        
        stage('Deploy to Prod') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Deploy to production?'
                sh 'deploy.sh prod'
            }
        }
        
        stage('Hotfix Deploy') {
            when {
                branch pattern: 'hotfix/*', comparator: 'GLOB'
            }
            steps {
                sh 'deploy-hotfix.sh'
            }
        }
    }
}

GitHub Flow #

text
main        - 主分支
feature/*   - 功能分支
groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        
        stage('Deploy Preview') {
            when {
                changeRequest()
            }
            steps {
                sh "deploy-preview.sh ${env.CHANGE_ID}"
            }
        }
        
        stage('Deploy Production') {
            when {
                branch 'main'
            }
            steps {
                sh 'deploy-production.sh'
            }
        }
    }
}

组织文件夹 #

GitHub组织 #

自动发现GitHub组织下的所有仓库:

  1. New ItemGitHub Organization
  2. 配置GitHub访问:
text
GitHub:
  API URL: https://api.github.com
  Credentials: github-token
  Owner: organization-name
  1. 配置项目识别:
text
Project Recognizers:
  ☑ Pipeline Jenkinsfile

GitLab组 #

text
GitLab:
  Server: GitLab Server
  Credentials: gitlab-token
  Group: group-name

Bitbucket团队 #

text
Bitbucket:
  Server: Bitbucket Server
  Credentials: bitbucket-creds
  Team: team-name

高级配置 #

并行分支构建 #

groovy
options {
    disableConcurrentBuilds()
}

分支特定配置 #

groovy
pipeline {
    agent any
    
    environment {
        DEPLOY_ENV = 'dev'
    }
    
    stages {
        stage('Set Environment') {
            steps {
                script {
                    switch(env.BRANCH_NAME) {
                        case 'main':
                        case 'master':
                            env.DEPLOY_ENV = 'prod'
                            break
                        case ~/release\/.*/:
                            env.DEPLOY_ENV = 'staging'
                            break
                        case 'develop':
                            env.DEPLOY_ENV = 'dev'
                            break
                        default:
                            env.DEPLOY_ENV = 'preview'
                    }
                }
            }
        }
        
        stage('Deploy') {
            steps {
                echo "Deploying to ${env.DEPLOY_ENV}"
                sh "deploy.sh ${env.DEPLOY_ENV}"
            }
        }
    }
}

通知配置 #

groovy
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
    }
    
    post {
        success {
            script {
                if (env.CHANGE_ID) {
                    // PR构建成功,通知PR作者
                    emailext(
                        subject: "PR #${env.CHANGE_ID} Build Passed",
                        to: "${env.CHANGE_AUTHOR}@example.com",
                        body: "Your PR build passed!"
                    )
                } else {
                    // 分支构建成功
                    slackSend(
                        channel: '#builds',
                        color: 'good',
                        message: "Branch ${env.BRANCH_NAME} build passed"
                    )
                }
            }
        }
    }
}

分支清理 #

自动清理 #

text
Orphaned Item Strategy:
  ☑ Discard old items
  Max # of old items to keep: 10
  Days to keep old items: 7

手动清理 #

groovy
pipeline {
    agent any
    
    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }
    
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }
    }
}

Webhook触发 #

GitHub Webhook #

  1. 在GitHub仓库设置中添加Webhook
  2. URL: http://jenkins-url/github-webhook/
  3. Content type: application/json
  4. 选择触发事件

GitLab Webhook #

  1. 在GitLab项目设置中添加Webhook
  2. URL: http://jenkins-url/project/your-job
  3. 选择触发事件

Bitbucket Webhook #

  1. 在Bitbucket仓库设置中添加Webhook
  2. URL: http://jenkins-url/bitbucket-hook/

完整示例 #

完整的多分支流水线 #

groovy
pipeline {
    agent any
    
    tools {
        maven 'Maven 3.8'
        jdk 'JDK 11'
    }
    
    environment {
        APP_NAME = 'myapp'
        VERSION = "${BUILD_NUMBER}"
        DOCKER_REGISTRY = 'registry.example.com'
    }
    
    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
        timestamps()
    }
    
    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 package -DskipTests'
            }
            post {
                success {
                    archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                }
            }
        }
        
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'mvn test'
                    }
                    post {
                        always {
                            junit '**/target/surefire-reports/*.xml'
                        }
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'mvn verify -P integration'
                    }
                    post {
                        always {
                            junit '**/target/failsafe-reports/*.xml'
                        }
                    }
                }
            }
        }
        
        stage('SonarQube') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh 'mvn sonar:sonar'
                }
            }
        }
        
        stage('Quality Gate') {
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
        
        stage('Build Image') {
            steps {
                script {
                    def imageTag = "${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}"
                    if (env.CHANGE_ID) {
                        imageTag = "${DOCKER_REGISTRY}/${APP_NAME}:pr-${env.CHANGE_ID}"
                    }
                    
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
                        def image = docker.build(imageTag)
                        image.push()
                    }
                }
            }
        }
        
        stage('Deploy Preview') {
            when {
                changeRequest()
            }
            steps {
                script {
                    sh """
                        kubectl create namespace preview-${env.CHANGE_ID} || true
                        kubectl apply -f k8s/ -n preview-${env.CHANGE_ID}
                        kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:pr-${env.CHANGE_ID} -n preview-${env.CHANGE_ID}
                    """
                }
            }
        }
        
        stage('Deploy to Dev') {
            when {
                branch 'develop'
            }
            steps {
                sh """
                    kubectl apply -f k8s/ -n dev
                    kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} -n dev
                """
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch pattern: 'release/*', comparator: 'GLOB'
            }
            steps {
                sh """
                    kubectl apply -f k8s/ -n staging
                    kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} -n staging
                """
            }
        }
        
        stage('Deploy to Production') {
            when {
                anyOf {
                    branch 'main'
                    branch 'master'
                }
            }
            steps {
                input message: 'Deploy to production?',
                       ok: 'Deploy',
                       submitter: 'admin,release-team'
                
                sh """
                    kubectl apply -f k8s/ -n production
                    kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} -n production
                """
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            script {
                def message = "Build ${BUILD_NUMBER} succeeded"
                if (env.CHANGE_ID) {
                    message = "PR #${env.CHANGE_ID} build succeeded"
                }
                
                slackSend(
                    channel: '#builds',
                    color: 'good',
                    message: "${message}\nBranch: ${env.BRANCH_NAME}\nURL: ${BUILD_URL}"
                )
            }
        }
        failure {
            script {
                def message = "Build ${BUILD_NUMBER} failed"
                if (env.CHANGE_ID) {
                    message = "PR #${env.CHANGE_ID} build failed"
                }
                
                slackSend(
                    channel: '#builds',
                    color: 'danger',
                    message: "${message}\nBranch: ${env.BRANCH_NAME}\nURL: ${BUILD_URL}"
                )
            }
        }
    }
}

下一步学习 #

小结 #

  • 多分支流水线自动发现分支和PR
  • 支持多种Git平台
  • 可以配置分支过滤和命名策略
  • 提供丰富的环境变量
  • 支持组织级别的仓库发现
  • 适合现代分支开发模式
最后更新:2026-03-28