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现代化界面。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







