You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkins加载YAML后执行Shell报NotSerializableException错误求助

解决Jenkins流水线中YAML解析后的序列化异常问题

这个问题我之前也碰到过,核心原因很明确:Jenkins的流水线基于CPS(Continuations Passing Style)执行模型,要求所有在流水线作用域内的对象必须是可序列化的,而你创建的org.yaml.snakeyaml.Yaml实例是不可序列化的。当流水线执行到sh步骤需要保存执行状态时,这个残留的Yaml对象就触发了NotSerializableException。

下面给你几个可行的解决方案,按推荐程度排序:

1. 把YAML解析逻辑封装到@NonCPS方法中(最推荐)

@NonCPS注解修饰的方法会以普通Groovy模式执行,不会参与CPS的序列化流程。方法内部的不可序列化对象(比如Yaml实例)在方法执行完毕后就会被销毁,不会留在流水线的状态里。

修改你的Jenkinsfile:

// 封装YAML加载逻辑到@NonCPS方法
@NonCPS
def loadYamlFromFile(String filePath) {
    def fileContent = new File(filePath).text
    Yaml yamlParser = new Yaml()
    def parsedData = yamlParser.load(fileContent)
    // 方法结束后,yamlParser会被自动回收,不会进入流水线序列化
    return parsedData
}

// 调用方法加载YAML
def helmSvcData = loadYamlFromFile('/jenkins/workspace/helm/helm_svc.yml')
ret = getHelmSvc(helmSvcData, mservice)
println ret

// 现在执行sh命令不会再触发序列化异常
sh(""" echo "$ret" > "/jenkins/workspace/helm/map.txt" """)

2. 手动置空Yaml对象(快速临时修复)

如果不想封装方法,也可以在解析完成后立即把Yaml实例置为null,让它脱离流水线作用域的引用,这样垃圾回收就能清理掉它,避免被序列化:

text1=new File('/jenkins/workspace/helm/helm_svc.yml').text
Yaml yaml = new Yaml()
def obj = yaml.load(text1)
// 解析完成后立即置空,切断引用
yaml = null
ret=getHelmSvc(obj, mservice)
println ret

sh(""" echo "$ret" > "/jenkins/workspace/helm/map.txt" """)

3. 整合到共享库中(长期复用最佳实践)

既然你已经有了读取变量的共享库,不如把YAML加载逻辑也整合进去,让流水线代码更简洁,也统一管理相关逻辑:

修改共享库的代码:

@NonCPS
def loadHelmSvcConfig(String filePath) {
    def fileContent = new File(filePath).text
    Yaml yamlParser = new Yaml()
    return yamlParser.load(fileContent)
}

def getHelmSvc(Map helmSvc,String mservice){
    def microservice_name = helmSvc[mservice].microservice_name
    def ingress_required = helmSvc[mservice].ingress_required
    def var_map = [:]
    var_map['microservice_name'] = microservice_name
    var_map['ingress_required'] = ingress_required
    return var_map
}

然后在Jenkinsfile中调用:

def helmSvcData = loadHelmSvcConfig('/jenkins/workspace/helm/helm_svc.yml')
ret = getHelmSvc(helmSvcData, mservice)
println ret

sh(""" echo "$ret" > "/jenkins/workspace/helm/map.txt" """)

额外优化:让输出格式更适合Ansible

直接echo Map的字符串格式可能不是Ansible最友好的,你可以把Map转换成键值对格式写入文件:

// 把Map转换成每行一个键值对的格式
def ansibleFriendlyContent = ret.collect { key, value -> "${key}=${value}" }.join('\n')
// 写入文件
sh """ echo '${ansibleFriendlyContent}' > "/jenkins/workspace/helm/map.txt" """

这样生成的map.txt内容会是:

microservice_name=some-processor
ingress_required=false

Ansible可以直接通过include_vars读取这个文件。

内容的提问来源于stack exchange,提问作者Vijaya Krishna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:17:16