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']) _
相关推荐
xlq223222 小时前
37 内核与用户_信号
linux·运维·服务器
dajun1811234562 小时前
信息系统运维管理全流程详解 在线画图工具绘制运维流程图表技巧
运维·数据库·信息可视化·流程图·旅游·论文笔记
PyHaVolask2 小时前
Linux实用工具与技巧
linux·运维·chrome
不才小强2 小时前
Linux开发环境搭建指南
linux·运维·服务器
syjy22 小时前
(含下载)WP Mail SMTP Pro WordPress插件使用教程
运维·服务器·wordpress·wordpress插件
信创DevOps先锋3 小时前
中国企业DevOps工具链选型趋势:本土化与安全可控成核心指标
运维·安全·devops
真心喜欢你吖3 小时前
CentOS 安装部署OpenClaw实战教程(SELinux+防火墙配置)
linux·运维·centos·大模型·智能体·openclaw·小龙虾
wuhui21003 小时前
Kali Linux 输入法问题排查与解决记录
linux·运维·服务器
L1624763 小时前
FreeFileSync使用教程(windows与windows,windows与linux)
linux·运维·服务器