Jenkins Pipeline 手记(3)—— 自定义Checkout的陷阱

本文探讨了在Jenkins Pipeline中遇到的自定义Checkout时的`java.io.NotSerializableException`问题。问题源于在Multi-branch pipeline中尝试覆盖默认的scm配置,尤其是当涉及CloneOptions和Extensions时。解决方案包括显式声明scm的各个选项,以避免使用不可序列化的类。文章还提到了直接在Jenkinsfile中调用checkout与在Shared Library中调用的区别,并提供了相关插件的参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

最近在pipeline中checkout代码时遇到了“不可序列化”(java.io.NotSerializableException)的问题。这个问题只在特定的场景下能重现,虽然影响不大,但如果深入研究一下,可以加深对Jenkins Pipeline的理解。

问题重现

我们知道,在使用Multi-branch pipeline时,可以在job的配置中指定源代码的来源,如git url,credentials,clone options,submodule options等等。然后在pipeline中,可以直接调用:

checkout scm

下载代码。这个scm对象对象由Jenkins的Git插件提供,里面包含了之前配置的源代码的所有信息。然而有的时候,我们也需要对配置好的scm进行订制:

checkout(
	[
		$class: 'GitSCM', 
	    branches: scm.branches,
	    extensions: scm.extensions + [
	        [$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: false]
	    ],
	    userRemoteConfigs:scm.userRemoteConfigs
	]
)

注意,scm.extensions这个字段后面又连接了一个SubmoduleOption的配置,作为对默认job配置的覆盖。像这样,我们借助默认的scm对象,重新定义一个scm对象,传递给checkout方法,就可能报序列化的问题:

java.io.NotSerializableException: hudson.plugins.git.extensions.impl.CloneOption

原因分析

我通过尝试发现两个问题:

  • 直接checkout scm不会有问题,一旦读取scm,并重新传入,就出问题(哪怕没有改动读取的scm)
  • 我的问题出现在Shared Library中,即在我的library内部调用了checkout方法。如果直接在Jenkinsfile里调用,不会有问题

第一个问题还有个前提,就是我的job配置里指定了Advanced Behaviors,并且设置其中的了CloneOptions。这样自动生成的scm对象的extensions字段,才会包含CloneOptions的内容。
CloneOptions
我查了一下GitClient Plugin的API文档,CloneOptions相关的类确实是不能序列化的,没有实现Serializable接口,而且scm.extensions这个列表本身也是不能序列化的。我们知道,Jenkins Pipeline要求所执行的代码是可序列化的,方便进行CSP风格的转换(详细见:另一篇文章),因此就会报错。

如果没有配置CloneOptions,那么序列化问题变为:

ERROR: java.io.NotSerializableException: hudson.util.DescribableList

这就是extensions列表本身的问题。再次强调,这两个序列化错误只有在读取默认的scm对象再传入checkout方法时才会出现。

根据这些现象,我认为Jenkins在处理scm对象时,如果是通过plugin默认生成的,它知道如何序列化,或者绕开序列化检查机制(类似@NonCPS的作用);如果scm被读取,则采用真实的extensions类型,并进行序列化检查,导致异常。事实上,在scm的各个字段中,只有extensions是无法序列化的。

对于上面说的第二个问题,使用场景如下:

library "shared-lib"
def scmSpec = [
  branches: scm.branches,
  ...
]
myFunc(scmSpec)
# myFunc defined in shared-lib

至于为什么直接调用checkout scm就不会出错,而在shared library中使用就会出错,我还没有答案。一个猜想是,Jenkins在处理Jenkinsfile和处理嵌套调用的方法时,处理checkout的方式不一样,前者不会进行序列化检查。

关于以上猜想,需要仔细研究Jenkins的CPS Plugin以及GitClient Plugin才能得出结论,目前我还没有着手做。

解决方案

值得注意的是,使用 @NonCPS 标记并不能解决本文遇到的两个序列化问题。

对于CloneOptions的问题,解决方法是显式地声明所有extensions内部的选项例如:

def scmSpec = [
    $class: 'GitSCM', 
    branches: scm.branches,
    extensions: [
        [$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: false],
        [$class: 'CloneOption', shallow: false, noTags: false, reference: '', timeout: null, depth: 0, honorRefspec: false]
    ],
    userRemoteConfigs: scm.userRemoteConfigs
]
checkout scmSpec

注意,其他scm的字段可以直接读取(例如branches)。这样声明也许会让Jenkins将scm的字段解析成可序列化的方式。

对于DescribableList的问题,可以直接在extensions后面附加一个空的列表,相当于重新显式声明了extensions列表,问题消失。

extensions: scm.extensions + []

结论

本文讨论了在pipeline中使用checkout时可能遇到的序列化问题陷阱,并给出了workaround的解决方案。如果想彻底解决这类问题,还要研究插件的源代码,理解Jenkins pipeline的运行过程。
有关插件的资料:

  • https://github.com/jenkinsci/workflow-cps-plugin/
  • https://plugins.jenkins.io/git-client/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值