Jenkins Shared Library 开发

目录

  1. [什么是Shared Library](#什么是Shared Library)
  2. 目录结构
  3. vars目录开发(全局方法)
  4. src目录开发(类库)
  5. Library加载方式
  6. 实战模板
  7. 最佳实践
  8. 常见问题

一、什么是Shared Library

1.1 定义

Shared Library是Jenkins Pipeline的代码复用机制,允许您将常用的Pipeline逻辑抽取到独立的代码库中,供多个项目共享使用。

复制代码
┌────────────────────────────────────────────────────────────────┐
│                      Shared Library                            │
│  ┌────────────────────────────────────────────────────────┐    │
│  │  vars/          │  src/                                │    │
│  │  ├── build.groovy  │  com/company/                     │    │
│  │  ├── deploy.groovy │  │   ├── util/                    │    │
│  │  └── notify.groovy │  │   │   ├── DateUtils.groovy     │    │
│  │                    │  │   │   └── StringUtils.groovy   │    │
│  │                    │  │   └── service/                 │    │
│  │                    │  │       └── DeployService.groovy │    │
│  └────────────────────────────────────────────────────────┘    │
└────────────────────────────────────────────────────────────────┘
                            │
            ┌───────────────┼───────────────┐
            ▼               ▼               ▼
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │  Project A  │ │  Project B  │ │  Project C  │
    │ Jenkinsfile │ │ Jenkinsfile │ │ Jenkinsfile │
    └─────────────┘ └─────────────┘ └─────────────┘

1.2 为什么需要Shared Library

问题 解决方案
重复代码 相同逻辑抽取到一处,统一维护
维护困难 修改一处,所有项目生效
标准化难 统一构建、部署、通知流程
协作困难 团队共享通用工具

1.3 使用场景

复制代码
常见使用场景:

1. 构建标准化
   ├── buildJava()      → Java构建
   ├── buildNode()      → Node.js构建
   └── buildDocker()    → Docker镜像构建

2. 部署自动化
   ├── deployToDev()    → 部署到开发环境
   ├── deployToStaging()→ 部署到测试环境
   └── deployToProd()   → 部署到生产环境

3. 通知统一
   ├── notifySuccess()  → 成功通知
   ├── notifyFailure()  → 失败通知
   └── notifySlack()    → Slack通知

4. 代码审查
   ├── runSonarQube()   → SonarQube扫描
   └── runSecurityScan()→ 安全扫描

二、目录结构

2.1 标准目录结构

复制代码
my-shared-library/
│
├── vars/                          # 全局变量/方法(必须)
│   ├── build.groovy
│   ├── deploy.groovy
│   ├── notify.groovy
│   ├── gitCheckout.groovy
│   └── sonarScan.groovy
│
├── src/                           # 源代码目录(可选)
│   └── com/
│       └── company/
│           ├── util/               # 工具类
│           │   ├── DateUtils.groovy
│           │   ├── StringUtils.groovy
│           │   └── FileUtils.groovy
│           │
│           ├── service/            # 服务类
│           │   ├── DeployService.groovy
│           │   └── BuildService.groovy
│           │
│           └── model/              # 模型类
│               └── BuildResult.groovy
│
├── resources/                     # 资源文件(可选)
│   ├── templates/
│   │   └── email-template.html
│   └── scripts/
│       └── setup.sh
│
└── test/                          # 测试目录(可选)
    └── com/company/util/
        └── DateUtilsTest.groovy

2.2 目录说明

目录 必须 说明
vars/ ✅ 必须 存放.groovy文件,每个文件定义一个全局方法
src/ ⭐推荐 存放Packaged Groovy/Java类,需import
resources/ ○可选 存放非Groovy资源文件,如模板、脚本
test/ ○可选 存放测试代码

三、vars目录开发(全局方法)

3.1 基本语法

vars目录下的每个.groovy文件会成为一个全局方法,方法名与文件名相同。

文件名、使用规则:

复制代码
buildJar.groovy  → buildJar() 方法
deploy.groovy    → deploy() 方法
git-checkout.groovy → git-checkout() 方法(下划线转驼峰)

3.2 简单方法定义

groovy 复制代码
// vars/echo.groovy
// 无参数方法
def call() {
    echo 'Hello from Shared Library'
}
groovy 复制代码
// vars/greet.groovy
// 单参数方法
def call(String name) {
    echo "Hello, ${name}!"
}
groovy 复制代码
// vars/build.groovy
// 多参数方法
def call(String type = 'jar', Boolean skipTest = false) {
    echo "Building ${type}..."
    def testFlag = skipTest ? '-DskipTests' : ''
    sh "mvn clean package ${testFlag}"
}

3.3 闭包参数方法

什么叫闭包:闭包(Closure)是一种在Groovy中可以作为参数传递的匿名函数。它可以在定义时捕获外部变量,形成一个闭包环境。

vars/withEnv.groovy 文件,类似Jenkins的withEnv,但更灵活,下面是实现代码

groovy 复制代码
def call(Map config, Closure body) {
    def originalEnv = env.getEnvironment()
    try {
        config.each { key, value ->
            env[key] = value
        }
        body()
    } finally {
        originalEnv.each { key, value ->
            env[key] = value
        }
    }
}

使用方式:

groovy 复制代码
withEnv(['MAVEN_HOME=/opt/maven', 'JAVA_HOME=/opt/java']) {
    sh 'mvn package'
}

3.4 返回值

// vars/getVersion.groovy

groovy 复制代码
def call() {
    def versionFile = readFile 'version.txt'
    return versionFile.trim()
}

使用方式:

groovy 复制代码
def version = getVersion()
echo "Building version: ${version}"

3.5 完整示例

vars/buildJar.groovy:

groovy 复制代码
def call(String type = 'jar', Map options = [:]) {
    echo "=========================================="
    echo "Starting ${type} build..."
    echo "=========================================="

    def skipTest = options.skipTest ?: false
    def profile = options.profile ?: 'default'

    try {
        stage('Clean') {
            sh 'mvn clean'
        }

        stage('Compile') {
            sh "mvn compile -P${profile}"
        }

        if (!skipTest) {
            stage('Test') {
                sh 'mvn test'
                junit 'target/surefire-reports/*.xml'
            }
        }

        stage('Package') {
            sh "mvn package -DskipTests -P${profile}"
        }

        echo "=========================================="
        echo "Build completed successfully!"
        echo "=========================================="

    } catch (Exception e) {
        currentBuild.result = 'FAILURE'
        echo "Build failed: ${e.message}"
        throw e
    }
}

四、src目录开发(类库)

4.1 类定义规范

src目录下的类必须按照Java包结构组织,需要使用package声明包名。

src/com/company/util/DateUtils.groovy:

groovy 复制代码
package com.company.util

class DateUtils {
    static String getTimestamp() {
        return new Date().format('yyyyMMdd-HHmmss')
    }

    static String getDate() {
        return new Date().format('yyyy-MM-dd')
    }

    static String getBuildId() {
        return "build-${getTimestamp()}"
    }
}

4.2 服务类

src/com/company/service/DeployService.groovy:

groovy 复制代码
package com.company.service

class DeployService {
    def steps

    DeployService(steps) {
        this.steps = steps
    }

    void deployToDev(String appName, String version) {
        steps.echo "Deploying ${appName}:${version} to DEV..."
        steps.sh "kubectl apply -f k8s/dev/${appName}.yaml"
        steps.sh "kubectl set image deployment/${appName} ${appName}=registry.example.com/${appName}:${version}"
    }

    void deployToProd(String appName, String version, Boolean approved = false) {
        if (!approved) {
            throw new Exception("Production deployment requires approval!")
        }
        steps.echo "Deploying ${appName}:${version} to PRODUCTION..."
        steps.sh "kubectl apply -f k8s/prod/${appName}.yaml"
        steps.sh "kubectl rollout status deployment/${appName}"
    }
}

4.3 模型类

src/com/company/model/BuildResult.groovy:

groovy 复制代码
package com.company.model

class BuildResult implements Serializable {
    String jobName
    int buildNumber
    String result
    String branch
    String commitId
    Date startTime
    Date endTime
    long duration

    long getDurationSeconds() {
        return (endTime.time - startTime.time) / 1000
    }

    String getDurationString() {
        def seconds = getDurationSeconds()
        def hours = seconds / 3600
        def minutes = (seconds % 3600) / 60
        def secs = seconds % 60
        return String.format("%02d:%02d:%02d", hours, minutes, secs)
    }
}

五、Library加载方式

5.1 全局配置(推荐)

通过Jenkins系统配置添加共享库,所有Pipeline都可以使用。

配置路径:

复制代码
Manage Jenkins → Configure System → Global Pipeline Libraries

配置参数:

参数 说明 示例
Name Library名称 my-library
Default version 默认版本 mainv1.0.0
Retrieval method 获取方式 Modern SCM
SCM 源码管理 Git

5.2 Jenkinsfile中加载

方式1:@Library注解(最常用)
groovy 复制代码
// 加载默认版本的library
@Library('my-library') _

pipeline {
    stages {
        stage('Build') {
            steps {
                buildJar('war')
            }
        }
    }
}
groovy 复制代码
// 加载指定版本
@Library('my-library@v1.0.0') _

pipeline {
    // ...
}
groovy 复制代码
// 加载多个library
@Library(['my-library', 'company-utils']) _

pipeline {
    // ...
}
方式2: libraries() step
groovy 复制代码
pipeline {
    stages {
        stage('Build') {
            steps {
                libraries {
                    lib('my-library')
                    lib('company-utils@v2.0.0')
                }
                // 然后调用方法
                buildJar()
            }
        }
    }
}
方式3:全限定名称调用
groovy 复制代码
// 不使用 _ 导入,直接使用全限定名
@Library('my-library')

pipeline {
    stages {
        stage('Build') {
            steps {
                com.company.util.DateUtils.getTimestamp()
            }
        }
    }
}

5.3 加载配置详解

groovy 复制代码
@Library('my-library@main') _
//             ↑      ↑     ↑
//            名称    版本  下划线表示导入所有

@Library注解参数:

参数 说明 示例
value Library名称 'my-library'
changelog 是否生成变更日志 changelog: false
pool 加载限制 @Library(name='x', pool=...)

六、实战模板

6.1 完整的Shared Library项目结构

复制代码
jenkins-shared-library/
│
├── vars/
│   ├── buildJava.groovy
│   ├── buildNode.groovy
│   ├── deploy.groovy
│   ├── notify.groovy
│   ├── gitCheckout.groovy
│   └── runTests.groovy
│
├── src/
│   └── com/
│       └── company/
│           ├── util/
│           │   ├── DateUtils.groovy
│           │   ├── GitUtils.groovy
│           │   └── StringUtils.groovy
│           │
│           └── service/
│               ├── DockerService.groovy
│               ├── K8sService.groovy
│               └── NotificationService.groovy
│
└── resources/
    └── templates/
        └── build-notification.html

6.2 vars方法实现

vars/buildJava.groovy:

groovy 复制代码
def call(Map config = [:]) {
    def type = config.type ?: 'jar'
    def skipTest = config.skipTest ?: false
    def profile = config.profile ?: 'dev'
    def javaVersion = config.javaVersion ?: '8'

    pipeline {
        agent { label 'java' }

        stages {
            stage('Checkout') {
                steps {
                    script {
                        def gitUtils = new com.company.util.GitUtils()
                        gitUtils.checkout()
                    }
                }
            }

            stage('Build') {
                steps {
                    script {
                        echo "Building ${type} with Java ${javaVersion}"

                        def mvnCmd = "mvn clean package -P${profile}"
                        if (skipTest) {
                            mvnCmd += ' -DskipTests'
                        }

                        sh mvnCmd
                    }
                }
            }

            stage('Archive') {
                steps {
                    archiveArtifacts artifacts: "target/*.${type}", fingerprint: true
                }
            }
        }
    }
}

vars/deploy.groovy:

groovy 复制代码
import com.company.service.K8sService

def call(String environment, Map config = [:]) {
    def appName = config.appName ?: env.JOB_NAME
    def imageTag = config.imageTag ?: env.BUILD_NUMBER

    echo "=========================================="
    echo "Deploying to ${environment}"
    echo "App: ${appName}"
    echo "Tag: ${imageTag}"
    echo "=========================================="

    def k8s = new K8sService(this)

    switch (environment) {
        case 'dev':
            k8s.deployToDev(appName, imageTag)
            break
        case 'staging':
            k8s.deployToStaging(appName, imageTag)
            break
        case 'prod':
            if (!config.approved) {
                error 'Production deployment requires approval!'
            }
            k8s.deployToProd(appName, imageTag)
            break
        default:
            error "Unknown environment: ${environment}"
    }

    echo "Deployment to ${environment} completed!"
}

vars/notify.groovy:

groovy 复制代码
import com.company.service.NotificationService

def call(String status) {
    def notification = new NotificationService(this)

    switch (status) {
        case 'success':
            notification.sendSuccess()
            break
        case 'failure':
            notification.sendFailure()
            break
        case 'always':
            notification.sendAlways()
            break
        default:
            echo "Unknown notification status: ${status}"
    }
}

6.3 服务类实现

src/com/company/service/K8sService.groovy:

groovy 复制代码
package com.company.service

class K8sService implements Serializable {
    def steps

    K8sService(steps) {
        this.steps = steps
    }

    void deployToDev(String appName, String tag) {
        steps.echo "Deploying to DEV: ${appName}:${tag}"
        steps.sh """
            kubectl config use-context dev-cluster
            kubectl set image deployment/${appName} ${appName}=registry.example.com/${appName}:${tag}
            kubectl rollout status deployment/${appName} --timeout=120s
        """
    }

    void deployToStaging(String appName, String tag) {
        steps.echo "Deploying to STAGING: ${appName}:${tag}"
        steps.sh """
            kubectl config use-context staging-cluster
            kubectl set image deployment/${appName} ${appName}=registry.example.com/${appName}:${tag}
            kubectl rollout status deployment/${appName} --timeout=180s
        """
    }

    void deployToProd(String appName, String tag) {
        steps.echo "Deploying to PRODUCTION: ${appName}:${tag}"
        steps.sh """
            kubectl config use-context prod-cluster
            kubectl set image deployment/${appName} ${appName}=registry.example.com/${appName}:${tag}
            kubectl rollout status deployment/${appName} --timeout=300s
        """
    }
}

src/com/company/service/NotificationService.groovy:

groovy 复制代码
package com.company.service

class NotificationService implements Serializable {
    def steps

    NotificationService(steps) {
        this.steps = steps
    }

    void sendSuccess() {
        def message = """
            ✅ Build Success!
            Job: ${steps.env.JOB_NAME}
            Build: #${steps.env.BUILD_NUMBER}
            URL: ${steps.env.BUILD_URL}
        """
        steps.echo message
        // 实际发送逻辑(Slack/Email/DingTalk)
    }

    void sendFailure() {
        def message = """
            ❌ Build Failed!
            Job: ${steps.env.JOB_NAME}
            Build: #${steps.env.BUILD_NUMBER}
            URL: ${steps.env.BUILD_URL}
        """
        steps.echo message
    }

    void sendAlways() {
        steps.echo "Build completed with result: ${steps.currentBuild.result ?: 'SUCCESS'}"
    }
}

6.4 Jenkinsfile使用示例

groovy 复制代码
@Library('jenkins-shared-library@main') _

pipeline {
    agent any

    parameters {
        choice(name: 'ENV', choices: ['dev', 'staging', 'prod'], description: 'Deploy environment')
        booleanParam(name: 'SKIP_TEST', defaultValue: false, description: 'Skip tests')
    }

    stages {
        stage('Build') {
            steps {
                script {
                    buildJava(
                        type: 'jar',
                        skipTest: params.SKIP_TEST,
                        profile: params.ENV
                    )
                }
            }
        }

        stage('Deploy') {
            when {
                expression { params.ENV != null }
            }
            steps {
                script {
                    deploy(params.ENV, [
                        appName: 'my-application',
                        approved: params.ENV == 'prod'
                    ])
                }
            }
        }
    }

    post {
        always {
            script {
                notify(currentBuild.result ?: 'SUCCESS')
            }
        }
    }
}

七、最佳实践

7.1 命名规范

复制代码
vars/ 命名规范:
├── 使用动词命名方法
│   ├── buildJar        → 构建动作
│   ├── deployTo        → 部署动作
│   └── notifyTeam      → 通知动作
│
├── 使用下划线分隔(会被转换为驼峰)
│   ├── git_checkout.groovy → gitCheckout()
│   └── run_sonar_scan.groovy → runSonarScan()
│
└── 避免过于通用名称
    └── 不好: util.groovy, common.groovy
    └── 好:   buildJava.groovy, deployK8s.groovy

7.2 方法设计原则

复制代码
设计原则:

1. 单一职责
   ✅ 好: buildJava(), deployK8s(), notifySlack()
   ❌ 不好: buildAndDeployAndNotify()

2. 合理参数
   ✅ 好: buildJava(type: 'war', skipTest: true)
   ❌ 不好: buildJava('war', true, false, null, 'dev')

3. 提供默认值
   def call(String type = 'jar', Boolean skipTest = false)

4. 使用Map参数(可选参数)
   def call(Map options = [:]) {
       def timeout = options.timeout ?: 60
       def retries = options.retries ?: 3
   }

7.3 错误处理

groovy 复制代码
// vars/deploy.groovy
def call(String environment) {
    try {
        echo "Deploying to ${environment}..."
        sh "./deploy.sh ${environment}"

    } catch (Exception e) {
        currentBuild.result = 'FAILURE'
        echo "Deployment failed: ${e.message}"
        throw e  // 重新抛出,让Pipeline感知失败
    }
}

7.4 文档和注释

groovy 复制代码
/**
 * Build Java application
 *
 * Usage:
 *   buildJava(type: 'war', skipTest: false, profile: 'dev')
 *
 * @param type     Build type: 'jar' or 'war'
 * @param skipTest Whether to skip tests
 * @param profile  Maven profile to use
 *
 * @return void
 */
def call(Map config = [:]) {
    def type = config.type ?: 'jar'
    // ...
}

7.5 版本管理

复制代码
版本策略:

1. 语义化版本
   ├── v1.0.0  → 初始版本
   ├── v1.1.0  → 新增功能
   └── v2.0.0  → 破坏性变更

2. 分支策略
   ├── main    → 稳定版本
   ├── develop → 开发版本
   └── v1.x    → 特定版本维护

3. Jenkinsfile指定版本
   @Library('my-lib@v1.0.0') _

4. 全局配置默认版本
   Global Pipeline Libraries → Default version: main

八、常见问题

8.1 找不到方法

复制代码
问题:Method not found: xxx

排查步骤:
1. 确认Library已正确配置
2. 确认使用 @Library('name') _
3. 确认方法名与文件名匹配
4. 确认Groovy文件语法正确
5. 查看Jenkins日志确认Library加载成功

8.2 类找不到

复制代码
问题:Class not found: com.company.xxx

排查步骤:
1. 确认src目录下有正确的包结构
2. 确认类文件以 .groovy 结尾
3. 确认使用 import 导入类
4. 确认类定义为 class(非interface或enum)
5. 如果是内部类,确认是static的

8.3 闭包上下文问题

复制代码
问题:env is null or something is undefined

原因:闭包中的this指向问题

解决方案:
1. 在构造器中传入steps引用
2. 使用 delegate 明确指向
3. 避免在闭包中直接访问steps

8.4 序列化问题

复制代码
问题:groovy.lang.GroovyRuntimeException

原因:闭包中引用了非Serializable对象

解决方案:
1. 服务类实现 Serializable 接口
2. 在构造器中传入steps引用保存为字段
3. 避免在闭包中直接使用非序列化对象

class MyService implements Serializable {
    def steps
    MyService(steps) { this.steps = steps }
}

附录:Cheat Sheet

groovy 复制代码
// ============================================
// Shared Library 快速参考
// ============================================

// 1. vars/ 简单方法
// vars/hello.groovy
def call(String name) {
    echo "Hello, ${name}"
}

// 2. vars/ 多参数方法
// vars/build.groovy
def call(String type = 'jar', Boolean skipTest = false) {
    // ...
}

// 3. src/ 类定义
// src/com/company/Util.groovy
package com.company
class Util implements Serializable {
    static String getVersion() { '1.0' }
}

// 4. Jenkinsfile加载
@Library('my-lib@main') _
build('jar')  // 调用vars方法

// 5. src类使用
import com.company.Util
def v = Util.getVersion()

// 6. 多个Library
@Library(['lib1', 'lib2@1.0.0']) _
相关推荐
乘云数字DATABUFF2 天前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
荣--4 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森4 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜5 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB6 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode7 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220708 天前
如何搭建本地yum源(上)
运维
大树8811 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠11 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质11 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务