目录
- [什么是Shared Library](#什么是Shared Library)
- 目录结构
- vars目录开发(全局方法)
- src目录开发(类库)
- Library加载方式
- 实战模板
- 最佳实践
- 常见问题
一、什么是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 | 默认版本 | main 或 v1.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']) _