Jenkins入门教程(十六):Jenkins共享库开发

当多个Pipeline有相同的逻辑时,共享库可以避免代码重复。本文将详细介绍Jenkins共享库的开发和使用,包括完整的示例代码。

什么是共享库

共享库(Shared Library)是存储在版本控制系统中的Groovy代码集合,可以在多个Pipeline中复用。它实现了DRY(Don't Repeat Yourself)原则,便于维护和更新。

共享库目录结构

jenkins-shared-library/
├── vars/                    # 全局变量和函数
│   ├── log.groovy          # 日志函数
│   ├── buildApp.groovy     # 构建函数
│   ├── deployApp.groovy    # 部署函数
│   └── notify.groovy       # 通知函数
├── src/                     # Groovy类
│   └── com/
│       └── example/
│           ├── Docker.groovy
│           └── Kubernetes.groovy
├── resources/               # 资源文件
│   └── templates/
│       └── email-template.html
└── README.md

创建全局函数

vars/log.groovy - 日志函数

#!/usr/bin/env groovy

// 默认调用方法
def call(String message) {
    info(message)
}

def info(String message) {
    echo "[INFO] ${new Date().format('yyyy-MM-dd HH:mm:ss')} - ${message}"
}

def warning(String message) {
    echo "[WARNING] ${new Date().format('yyyy-MM-dd HH:mm:ss')} - ${message}"
}

def error(String message) {
    echo "[ERROR] ${new Date().format('yyyy-MM-dd HH:mm:ss')} - ${message}"
}

def debug(String message) {
    if (env.DEBUG == 'true') {
        echo "[DEBUG] ${new Date().format('yyyy-MM-dd HH:mm:ss')} - ${message}"
    }
}

vars/buildApp.groovy - 构建函数

#!/usr/bin/env groovy

def call(Map config = [:]) {
    // 默认配置
    def defaults = [
        buildTool: 'maven',
        javaVersion: '17',
        skipTests: false
    ]
    
    // 合并配置
    config = defaults + config
    
    log.info "开始构建应用..."
    log.info "构建工具: ${config.buildTool}"
    log.info "Java版本: ${config.javaVersion}"
    
    switch(config.buildTool) {
        case 'maven':
            buildWithMaven(config)
            break
        case 'gradle':
            buildWithGradle(config)
            break
        case 'npm':
            buildWithNpm(config)
            break
        default:
            error "不支持的构建工具: ${config.buildTool}"
    }
    
    log.info "构建完成!"
}

def buildWithMaven(Map config) {
    def mvnCmd = "mvn clean package"
    
    if (config.skipTests) {
        mvnCmd += " -DskipTests"
    }
    
    if (config.profile) {
        mvnCmd += " -P${config.profile}"
    }
    
    sh mvnCmd
}

def buildWithGradle(Map config) {
    def gradleCmd = "./gradlew build"
    
    if (config.skipTests) {
        gradleCmd += " -x test"
    }
    
    sh gradleCmd
}

def buildWithNpm(Map config) {
    sh 'npm ci'
    sh 'npm run build'
}

vars/deployApp.groovy - 部署函数

#!/usr/bin/env groovy

def call(Map config) {
    // 必需参数检查
    if (!config.environment) {
        error "必须指定部署环境 (environment)"
    }
    
    log.info "部署到 ${config.environment} 环境"
    
    switch(config.environment) {
        case 'dev':
            deployToDev(config)
            break
        case 'staging':
            deployToStaging(config)
            break
        case 'prod':
            deployToProduction(config)
            break
        default:
            error "未知环境: ${config.environment}"
    }
}

def deployToDev(Map config) {
    log.info "部署到开发环境..."
    sh "kubectl apply -f k8s/dev/ -n dev"
}

def deployToStaging(Map config) {
    log.info "部署到预发布环境..."
    sh "kubectl apply -f k8s/staging/ -n staging"
}

def deployToProduction(Map config) {
    log.info "部署到生产环境..."
    
    // 生产环境需要确认
    timeout(time: 30, unit: 'MINUTES') {
        input message: '确认部署到生产环境?', ok: '部署'
    }
    
    sh "kubectl apply -f k8s/prod/ -n production"
    
    // 验证部署
    sh "kubectl rollout status deployment/${config.appName} -n production --timeout=300s"
}

vars/notify.groovy - 通知函数

#!/usr/bin/env groovy

def call(String status) {
    def color = status == 'SUCCESS' ? 'good' : 'danger'
    def emoji = status == 'SUCCESS' ? '✅' : '❌'
    
    def message = """
        ${emoji} *构建${status == 'SUCCESS' ? '成功' : '失败'}*
        *项目:* ${env.JOB_NAME}
        *构建号:* #${env.BUILD_NUMBER}
        *分支:* ${env.GIT_BRANCH ?: 'N/A'}
        *耗时:* ${currentBuild.durationString.replace(' and counting', '')}
        *链接:* ${env.BUILD_URL}
    """
    
    // Slack通知
    try {
        slackSend(color: color, message: message)
    } catch (Exception e) {
        log.warning "Slack通知发送失败: ${e.message}"
    }
    
    // 邮件通知(仅失败时)
    if (status != 'SUCCESS') {
        emailext(
            subject: "${emoji} 构建${status == 'SUCCESS' ? '成功' : '失败'}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: message,
            to: 'team@example.com'
        )
    }
}

def success() {
    call('SUCCESS')
}

def failure() {
    call('FAILURE')
}

vars/dockerBuild.groovy - Docker构建

#!/usr/bin/env groovy

def call(Map config = [:]) {
    def imageName = config.imageName ?: env.JOB_NAME.toLowerCase().replaceAll('/', '-')
    def imageTag = config.tag ?: env.BUILD_NUMBER
    def registry = config.registry ?: 'docker.io'
    def dockerfile = config.dockerfile ?: 'Dockerfile'
    
    def fullImageName = "${registry}/${imageName}:${imageTag}"
    
    log.info "构建Docker镜像: ${fullImageName}"
    
    // 构建镜像
    sh "docker build -t ${fullImageName} -f ${dockerfile} ."
    
    // 推送镜像
    if (config.push != false) {
        withCredentials([usernamePassword(
            credentialsId: config.credentialsId ?: 'docker-registry',
            usernameVariable: 'DOCKER_USER',
            passwordVariable: 'DOCKER_PASS'
        )]) {
            sh "echo \$DOCKER_PASS | docker login ${registry} -u \$DOCKER_USER --password-stdin"
            sh "docker push ${fullImageName}"
            
            // 同时打latest标签
            if (config.tagLatest) {
                def latestImage = "${registry}/${imageName}:latest"
                sh "docker tag ${fullImageName} ${latestImage}"
                sh "docker push ${latestImage}"
            }
        }
    }
    
    // 清理本地镜像
    if (config.cleanup != false) {
        sh "docker rmi ${fullImageName} || true"
    }
    
    return fullImageName
}

配置共享库

# 系统管理 > 系统配置 > Global Pipeline Libraries

名称: my-shared-library
默认版本: main  # 分支名或tag

# 获取方式
现代SCM:
  Git:
    项目仓库: https://github.com/example/jenkins-shared-library.git
    凭据: github-token

# 选项
允许默认版本被覆盖: 勾选
包含@Library更改的修订: 勾选
缓存获取的版本以提高性能: 勾选

使用共享库

基本用法

@Library('my-shared-library') _  // 下划线表示导入所有

pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                log.info '开始构建'
                
                buildApp(
                    buildTool: 'maven',
                    javaVersion: '17',
                    skipTests: false
                )
            }
        }
        
        stage('Docker') {
            steps {
                script {
                    def image = dockerBuild(
                        imageName: 'my-app',
                        registry: 'registry.example.com',
                        tagLatest: true
                    )
                    log.info "镜像构建完成: ${image}"
                }
            }
        }
        
        stage('Deploy') {
            steps {
                deployApp(
                    environment: 'staging',
                    appName: 'my-app'
                )
            }
        }
    }
    
    post {
        success {
            notify.success()
        }
        failure {
            notify.failure()
        }
    }
}

// 执行结果
[Pipeline] Start of Pipeline
[Pipeline] library
Loading library my-shared-library@main
...
[Pipeline] echo
[INFO] 2026-02-08 12:00:00 - 开始构建
[Pipeline] echo
[INFO] 2026-02-08 12:00:01 - 开始构建应用...
[Pipeline] echo
[INFO] 2026-02-08 12:00:01 - 构建工具: maven
[Pipeline] echo
[INFO] 2026-02-08 12:00:01 - Java版本: 17
[Pipeline] sh
+ mvn clean package
...

指定版本

// 使用特定分支
@Library('my-shared-library@develop') _

// 使用特定tag
@Library('my-shared-library@v1.0.0') _

// 使用特定commit
@Library('my-shared-library@abc123') _

动态加载

pipeline {
    agent any
    
    stages {
        stage('Load Library') {
            steps {
                script {
                    // 动态加载共享库
                    def lib = library(
                        identifier: 'my-shared-library@main',
                        retriever: modernSCM([
                            $class: 'GitSCMSource',
                            remote: 'https://github.com/example/jenkins-shared-library.git',
                            credentialsId: 'github-token'
                        ])
                    )
                }
            }
        }
        
        stage('Use Library') {
            steps {
                log.info 'Library loaded dynamically'
            }
        }
    }
}

创建Pipeline模板

// vars/javaPipeline.groovy - 完整的Java项目Pipeline模板

def call(Map config = [:]) {
    pipeline {
        agent any
        
        options {
            timeout(time: config.timeout ?: 30, unit: 'MINUTES')
            buildDiscarder(logRotator(numToKeepStr: '10'))
            timestamps()
        }
        
        environment {
            APP_NAME = config.appName ?: env.JOB_NAME
            DOCKER_REGISTRY = config.registry ?: 'docker.io'
        }
        
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
            
            stage('Build') {
                steps {
                    buildApp(
                        buildTool: config.buildTool ?: 'maven',
                        skipTests: config.skipTests ?: false
                    )
                }
            }
            
            stage('Test') {
                when {
                    expression { !(config.skipTests ?: false) }
                }
                steps {
                    sh 'mvn test'
                }
                post {
                    always {
                        junit 'target/surefire-reports/*.xml'
                    }
                }
            }
            
            stage('Docker Build') {
                when {
                    expression { config.docker != false }
                }
                steps {
                    script {
                        dockerBuild(
                            imageName: env.APP_NAME,
                            registry: env.DOCKER_REGISTRY
                        )
                    }
                }
            }
            
            stage('Deploy') {
                when {
                    anyOf {
                        branch 'main'
                        branch 'develop'
                    }
                }
                steps {
                    script {
                        def targetEnv = env.BRANCH_NAME == 'main' ? 'prod' : 'dev'
                        deployApp(
                            environment: targetEnv,
                            appName: env.APP_NAME
                        )
                    }
                }
            }
        }
        
        post {
            success {
                notify.success()
            }
            failure {
                notify.failure()
            }
            cleanup {
                cleanWs()
            }
        }
    }
}

// Jenkinsfile使用模板 - 只需几行代码
@Library('my-shared-library') _

javaPipeline(
    appName: 'my-java-app',
    buildTool: 'maven',
    registry: 'registry.example.com'
)

单元测试共享库

// test/vars/BuildAppTest.groovy
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.*

class BuildAppTest {
    def buildApp
    
    @Before
    void setUp() {
        buildApp = new buildApp()
    }
    
    @Test
    void testDefaultConfig() {
        // 测试默认配置
        def config = [:]
        def merged = buildApp.mergeConfig(config)
        
        assertEquals('maven', merged.buildTool)
        assertEquals('17', merged.javaVersion)
        assertFalse(merged.skipTests)
    }
}

// 使用Jenkins Pipeline Unit测试框架
// https://github.com/jenkinsci/JenkinsPipelineUnit

最佳实践

  • 版本控制:使用语义化版本号(v1.0.0)标记稳定版本
  • 文档:为每个函数编写清晰的注释和使用示例
  • 向后兼容:修改已发布的接口时保持兼容性
  • 单元测试:为共享库编写测试
  • 代码审查:共享库变更需要审查

总结

本文详细介绍了Jenkins共享库的开发和使用,包括目录结构、全局函数、配置方法和实战示例。共享库是大规模Jenkins使用的最佳实践。

下一篇我们将学习Blue Ocean现代化界面。

发表回复

后才能评论