Jenkins 流水线共享库从零搭建:把重复脚本沉淀成团队资产
团队项目一多,Jenkinsfile 就开始复制粘贴:每个仓库里都有一份几乎一样的「拉代码、构建镜像、推送、通知」脚本,改一处要改十几个仓库。本文介绍 Jenkins 流水线共享库(Shared Library):把通用步骤沉淀成一个独立的 Git 仓库,项目侧只留一行引用,统一升级、统一修复。
适用环境:Jenkins 2.4xx 及以上版本,已安装 Pipeline 系列插件;共享库存放在内网 Git 服务上(下文以 git.example.com 占位),使用者具备 Jenkins 系统配置或文件夹配置权限之一。
前置条件
- 一台可用的 Jenkins,已能正常跑通声明式或脚本式流水线。
- 一个空的 Git 仓库用于存放共享库,例如 devops/jenkins-shared-library。
- Jenkins 已配置好访问该 Git 仓库的凭据(用户名密码或 SSH 私钥)。
- 本地装有 Git 与 JDK,方便先写好再推送;可选安装 Jenkins Pipeline Linter 做语法校验。
第一步:理解共享库的目录结构
共享库的目录是约定死的,Jenkins 按目录名识别三类内容,放错位置直接不加载:
jenkins-shared-library/
├── vars/ # 全局步骤:每个 groovy 文件就是一个可在流水线里直接调用的步骤
│ ├── buildAndPublish.groovy
│ └── notify.groovy
├── src/ # 正式的 Groovy 类(包名目录),可被 vars 和 Jenkinsfile 引用
│ └── com/example/ci/DockerHelper.groovy
└── resources/ # 非 Groovy 资源文件:脚本、模板、配置文件,用 libraryResource 读取
└── com/example/ci/deploy.sh
简单逻辑写在 vars 里最方便;逻辑复杂、需要复用和单元测试的代码应沉淀到 src 的类里,vars 只做一层薄封装。resources 里的脚本通过 libraryResource('com/example/ci/deploy.sh') 读出内容再写到工作区执行,不要把大段 shell 直接硬编码进 Groovy 字符串。
第二步:配置共享库(全局与文件夹级)
全局配置:进入「系统管理 → 系统配置」,找到 Global Pipeline Libraries,点击新增:填写库名(如 ci-lib)、默认版本(分支名或 Tag,如 main / v1.2.0)、检索方式选 Modern SCM,填仓库地址并选择凭据。勾选「Allow default version to be overridden」允许项目侧指定版本;「Load implicitly」不建议勾选,隐式加载会让所有流水线默认引入该库,出问题时排查成本高。配置等价的 JCasC 片段如下:
unclassified:
globalLibraries:
libraries:
- name: "ci-lib"
defaultVersion: "v1.2.0"
implicit: false
allowVersionOverride: true
retriever:
modernSCM:
scm:
git:
remote: "https://git.example.com/devops/jenkins-shared-library.git"
credentialsId: "git-cred-id"
文件夹级配置:在某个文件夹(Folder)的「配置 → Pipeline Libraries」里按同样方式添加,只对该文件夹下的任务生效。适合某个业务线先行试点新版本库、或不同团队维护各自库的场景。查找顺序上,文件夹级库与全局库同名时以文件夹级为准,利用这一点可以做小范围灰度。
第三步:写一个可复用的 vars 步骤
以「通用构建发布」为例:vars/buildAndPublish.groovy 的文件名即步骤名,call 方法是入口。参数用 Map 传入并给出默认值,调用方只写关心的字段:
// vars/buildAndPublish.groovy
def call(Map cfg = [:]) {
def image = cfg.get('image', "${env.JOB_NAME}".replaceAll('/', '-'))
def tag = cfg.get('tag', "${env.BUILD_NUMBER}")
def registry = cfg.get('registry', 'registry.example.com')
stage('Build & Push Image') {
sh "docker build -t ${registry}/${image}:${tag} ."
withCredentials([usernamePassword(credentialsId: 'registry-cred',
usernameVariable: 'REG_USER',
passwordVariable: 'REG_PASS')]) {
sh 'echo $REG_PASS | docker login -u $REG_USER --password-stdin ' + registry
sh "docker push ${registry}/${image}:${tag}"
}
}
echo "published: ${registry}/${image}:${tag}"
}
项目侧 Jenkinsfile 因此瘦身为:
@Library('ci-lib@v1.2.0') _
pipeline {
agent any
stages {
stage('CI') {
steps {
buildAndPublish(image: 'order-service', tag: "${env.GIT_COMMIT[0..7]}")
}
}
}
}
第四步:@Library 引用与版本锁定
@Library('ci-lib@v1.2.0') _ 里的 @ 后面可以是 Tag、分支或 commit 号。生产环境强烈建议锁定到 Tag 或 commit:锁分支意味着库一提交,所有流水线下一次构建就跑新代码,一次坏提交全线飘红。发版流程建议:库的变更先走自己的测试流水线,合并后打 Tag(如 v1.3.0),通知各项目自行升级引用版本。需要全量紧急修复时,再考虑临时把默认版本切到修复 Tag。
第五步:调试方法
最快的调试通道是流水线的「Replay(重放)」功能:在已完成构建的页面里点 Replay,可以临时修改 Jenkinsfile 和所引用的库代码(重放视图里会列出库文件)再跑一次,验证通过再把修改正式提交回仓库。注意重放的修改不落库,只对当次构建有效。其次是日志:库代码里的 println/echo 输出会混进构建日志,复杂对象用 JsonOutput.toJson 打印结构。更稳妥的做法是给 src 里的类写单元测试(配合 JenkinsPipelineUnit 框架在本地跑),把回归挡在提交之前。
版本管理与向后兼容
共享库是团队资产,变更纪律比代码本身更重要。三条经验:一是步骤参数只增不改,已有参数不改名、不改语义,废弃参数先保留并打印警告,过渡一两个版本再删;二是每次发版写 CHANGELOG,标注破坏性变更;三是新功能先在试点项目上以新 Tag 验证,再发正式版。确实要做不兼容升级时,新增一个 v2 风格的步骤名与旧步骤并存,比强迫所有项目同一天改造稳妥得多。
验证
在示例项目里跑一次构建:控制台日志开头应出现「Loading library ci-lib@v1.2.0」一行,阶段视图里能看到 Build & Push Image 阶段且镜像成功推送到仓库。再把引用版本改成一个不存在的 Tag 重跑,构建应在初始化阶段报找不到版本,确认版本锁定确实生效后改回。
常见问题
构建报「Scripts not permitted to use method」。这是 CPS 脚本沙盒在拦截:vars 和 Jenkinsfile 运行在沙盒里,调用某些 Groovy/Java 方法需要管理员在「系统管理 → In-process Script Approval」里批准。频繁触发说明代码把逻辑写得太「Java 化」,能下沉到 src 类的尽量下沉,或改用沙盒允许的写法。
报「java.io.NotSerializableException」。流水线每步之间会把状态序列化以支持暂停恢复,局部变量里存了不可序列化的对象(如 Matcher、连接对象)就会在下一步炸掉。对策:这类对象用完即弃、不要跨步骤保存,需要跨步骤的数据只留字符串和基本类型;脚本式流水线里可用 @NonCPS 标注纯计算方法,但它治标不治本。
改了库代码但构建还是旧行为。先确认流水线引用的是分支还是 Tag:引用 Tag 时推新代码没有用,必须打新 Tag 并改引用。其次看日志里实际 Loading 的版本。最后检查是否开了隐式加载导致同名步骤被另一个库覆盖。
resources 里的脚本执行报权限或换行问题。libraryResource 读出的内容写入工作区后要显式 chmod +x 再执行;脚本在 Windows 上编辑过可能带 CRLF,统一用 LF 并在仓库加 .gitattributes 约束。
小结
共享库的价值不在第一天,而在第三个月:镜像构建规范、通知格式、安全扫描这类横切逻辑只维护一份。落地节奏建议先抽三个最高频步骤、锁定 Tag 发版、试点两三个项目,跑顺了再推广。库本身当正式产品维护——有版本、有变更记录、有测试,团队才敢把流水线放心交给它。







