Jenkins Git Parameter The default value has been returned 排查与修复

Jenkins Git Parameter The default value has been returned 排查与修复

Jenkins Git Parameter "The default value has been returned" 排查与修复

某天测试同学在 Jenkins 上构建 Android 包,参数页一打开就跳出一连串红色错误,分支下拉框是空的,只剩一个默认值 origin/master

The default value has been returned
An error occurred while download data
Error performing git command: git ls-remote -h git@git.example.com:org/mobile-app.git
Please look at the Log
Please check the configuration

构建本身还能跑——只要硬着头皮选默认 origin/master 提交,git fetch 是成功的。但分支选择没了,等于每次都得在备注里手填要打的分支,谁都不能接受。

这篇记录一次跑歪两次才找到真凶的排查。结论很反直觉:根因跟 git、跟 SSH 凭据、跟仓库授权全都没关系,是 Java 主进程内存里的旧二进制跟磁盘上被 apt 静默升级过的 jspawnhelper 对不齐。

环境

组件 版本
OS Ubuntu 22.04 LTS
OpenJDK 17.0.19+10-1~22.04.2
Jenkins 主进程 同一 PID 自 3 月 20 日运行至今(事发是 5 月 29 日,跑了 70 天没重启)
Git plugin git@5.2.1
Git Parameter plugin git-parameter@0.9.19
Job SCM URL git@git.example.com:org/mobile-app.git(SSH 协议)
远端 Gitea 自建

公司内部域名/IP 已替换成示意值,读者可对号入座。

第一反应:怀疑 SSH / 仓库授权(错的方向)

Error performing git command: git ls-remote 这条最显眼,本能先排查 SSH。登到 Jenkins controller 上以 jenkins 用户 sanity check:

# su - jenkins 切到 jenkins 用户的登录态
$ ssh -o BatchMode=yes -T git@git.example.com
Hi there, root! You've successfully authenticated with the key named jenkins-test,
but Gitea does not provide shell access.$ git ls-remote -h git@git.example.com:org/mobile-app.git
5fcd08eb861062181b981bee3a4d431307e9a721    refs/heads/ch_1.12.0
ebc6fd0195bcfd484e5cf063f3d8b33e79cc86a4    refs/heads/dev
... (一长串分支)

双双跑通。 SSH key 是在 Gitea 上、对应 repo 也有权限,命令行 ls-remote 没问题。

排除 SSH。继续往下。

拐弯:拉 Job 的 config.xml 看 SCM 配置(自圆其说也是错的)

走过这条弯路要写出来——它是后面真正定位的反面对照。

走 Jenkins REST API 把 job 配置抠出来:

curl -sS --user "$USER:$TOKEN" \"http://jenkins.example.com:8080/job/mobile-android-build/config.xml"

里面的 SCM 节点是这样的:

<scm class="hudson.plugins.git.GitSCM" plugin="git@5.2.1"><userRemoteConfigs><hudson.plugins.git.UserRemoteConfig><url>git@git.example.com:org/mobile-app.git</url><!-- 注意:没有 <credentialsId> 这个子节点 --></hudson.plugins.git.UserRemoteConfig></userRemoteConfigs>...
</scm>

一眼瞟到没 <credentialsId>,就脑补了一套很顺的解释:

Git Parameter 插件的 ls-remote 是在 Jenkins controller 上跑的(不是在 agent 上),而 SCM 没有 credentialsId,所以它没凭据可用,所以 ls-remote 失败 → 退回默认值。

这套解释挺自洽,但完全是错的。因为前面已经验证了:以 jenkins 用户身份命令行跑 ls-remote 一切正常——不带任何凭据声明,靠的就是 jenkins 用户 ~/.ssh/id_rsa 的隐式认证。Git Parameter 插件的 git-client 子进程是 fork 自 Jenkins JVM,环境跟登录态 shell 一致,它本来也能用同一把 key

这个错误推断我特意保留下来——它提醒一件事:配置文件里看到一处"明显的缺失",不等于那处缺失就是当前症状的因。 得有动态证据。

真正的根因:扫 Jenkins 日志

Jenkins 系统日志走 systemd journal:

$ journalctl -u jenkins --since "1 hour ago" \| grep -E "git.?param|exception|severe"

第一条 SEVERE 直接打脸:

SEVERE  n.u.l.h.p.g.GitParameterDefinition#generateContents:[ mobile-android-build ]  Unexpected error!java.io.IOException: error=0, Failed to exec spawn helper: pid: 2052812, exit value: 1
Caused: java.io.IOException: Cannot run program "git": error=0,Failed to exec spawn helper: pid: 2052812, exit value: 1
Caused: hudson.plugins.git.GitException:Error performing git command: git ls-remote -h git@git.example.com:org/mobile-app.gitat net.uaznia.lukanus.hudson.plugins.gitparameter.GitParameterDefinition.getBranch(...)at net.uaznia.lukanus.hudson.plugins.gitparameter.GitParameterDefinition.generateContents(...)

关键字:Failed to exec spawn helper, exit value: 1。这不是 git 在抱怨,是 JVM 想 fork 一个子进程,但 fork 出来的「spawn helper」起不来。

继续在 journal 里翻:

jenkins[2054134]: jspawnhelper version 17.0.19+10-1-22.04.2-Ubuntu
jenkins[2054134]: This command is not for general use and should only be runas the result of a call to ProcessBuilder.start() orRuntime.exec() in a java application

jspawnhelper 这个二进制是 JDK 自带的一个小工具,专门给 JVM 在 Linux 上启动子进程用。它正常情况下绝对不会自己打日志——它接 stdin/stdout 的文件描述符,干完活就退。日志里看到它说"这个命令不是给一般人用的",意味着它被启动时 fd 没有按预期接好,于是它把 usage 打到 stderr 然后退出 1

另一个反常对得上:还能注意到事故时段连 mobile-ios-build 同样的报错都有——不是 Android job 独有,不是这个 git 仓库独有。任何 job 只要走 git-client 插件 fork 子进程 git ls-remote,都会撞。

到这里已经基本能锁定方向:JVM 启动子进程的这条路径整个坏了。

解释:长跑 JVM 撞 apt 静默升级

ps -ef | grep jenkins 看主进程:

jenkins   948661       1  1 Mar20 ?        18:21:05 /usr/bin/java -jar /usr/share/java/jenkins.war ...

Mar20 起的,到 5 月 29 日整整 70 天没重启过 JVM

而 Ubuntu 22.04 默认装了 unattended-upgrades,每天自动跑安全补丁。openjdk-17 是 main 仓库的"重要安全更新"白名单包,发版就升。这 70 天里 OpenJDK 至少被升级过一次到现在的 17.0.19

但是——升级 openjdk-17-jre-headless 这个 .deb 包,apt 不会重启正在跑的 java 进程。 结果就是:

  • Jenkins JVM 进程在内存里跑的,还是 70 天前那个版本的 java 二进制(Linux 允许打开后被 unlink,旧文件镜像跟着进程的虚拟内存映射继续存活)。
  • 磁盘上 /usr/lib/jvm/java-17-openjdk-amd64/lib/jspawnhelper 已经被换成新版
  • JVM 内部 fork 子进程时,走的代码路径里写死了 jspawnhelper 的路径,用旧版 JVM 去 exec 新版 jspawnhelper,两者交换 fd 的握手协议对不齐 → helper 收不到正确的 fd → 直接 print usage 退 1。

这就是 error=0, Failed to exec spawn helper, exit value: 1 的完整因果链。

这个坑不限 Jenkins,任何 long-running JVM(GitLab、Nexus、ES、Kafka 都中过)都会撞——只要:

  1. 跑在 OpenJDK 上
  2. 用 systemd 起,但服务 unit 没有响应 apt 触发自动重启
  3. 用到 Runtime.exec() / ProcessBuilder 启动子进程

修复:重启 JVM

修复只有一句:让 Jenkins 主进程整体换掉,重新加载新版 java + jspawnhelper

systemctl restart jenkins

注意是 restart,不是 reload。reload 只让 Jenkins 重新解析 config.xmlJVM 进程不换,治不了。Jenkins UI 上的 Safe Restart / Restart 同样能修,本质都是 fork 出新 JVM。

操作前确认队列里没 in-flight build:

curl -sS -G --user "$USER:$TOKEN" \--data-urlencode 'tree=computer[displayName,executors[currentExecutable[url]]]' \http://jenkins.example.com:8080/computer/api/json

所有 currentExecutable 都是 null 就能放心 restart。

重启完,进 Mobile-Android job 的 build 参数页,分支下拉框秒出全量分支列表。journalctl -u jenkins -f 也再无 Failed to exec spawn helper 出现。

防护:把 openjdk-17 钉死,不让 apt 偷升

要根治"以后还会再撞"这个隐患,最直接的办法是把 openjdk-17-* 这几个包从自动升级里排除

# 先看实际装了哪些
$ dpkg -l 'openjdk-*' | awk '/^ii/ {print $2, $3}'
openjdk-17-jdk:amd64           17.0.19+10-1~22.04.2
openjdk-17-jdk-headless:amd64  17.0.19+10-1~22.04.2
openjdk-17-jre:amd64           17.0.19+10-1~22.04.2
openjdk-17-jre-headless:amd64  17.0.19+10-1~22.04.2# 全部 hold
$ apt-mark hold openjdk-17-jdk openjdk-17-jdk-headless \openjdk-17-jre openjdk-17-jre-headless# 验证
$ apt-mark showhold
openjdk-17-jdk
openjdk-17-jdk-headless
openjdk-17-jre
openjdk-17-jre-headless

为什么不直接关掉整个 unattended-upgrades?因为这台机器还跑着别的服务,kernel/openssl/sudo 之类的 CVE 自动补丁该打还得打——只把 JDK 钉死,影响范围最小。

以后想主动升 JDK:

apt-mark unhold 'openjdk-17-*'
apt update && apt upgrade
systemctl restart jenkins   # 关键:升完一定要重启 JVM
apt-mark hold openjdk-17-jdk openjdk-17-jdk-headless \openjdk-17-jre openjdk-17-jre-headless

把 "升级 JDK 后必须立即 restart jenkins" 写进 runbook,是这套方案的关键配套——不写就是把同一个雷又埋回去。

配套监控:早发现一次告警都不亏

Failed to exec spawn helper 是个非常明确的字符串,加一条 journalctl 监听足以兜底——万一以后哪天因为 hold 漏了某个包又中招,告警先到运维手里,而不是测试同学先在 UI 上撞红:

journalctl -u jenkins -f \| grep --line-buffered 'Failed to exec spawn helper' \| while read line; docurl -X POST ... # 推企业 IMdone

复盘:为什么会走两次弯路

排查这条问题花的时间,大头都不在"找堆栈"那一步——而在前面两次自圆其说

  1. 第一次:看到 git ls-remote 字样就锁定 SSH/凭据方向。命令行验证 SSH 通了之后,没有第一时间放弃这个方向,反而开始想"是不是 controller 端某种特殊执行路径绕过了 SSH"。
  2. 第二次:从 config.xml 里看到 SCM 缺 <credentialsId>,立刻脑补了一套"Git Parameter 跑在 controller 没凭据所以失败"的解释——这套理论既能解释当前症状,又指向一个看得见的缺失,人会本能停下不再查

真正的破局点是 journalctl。 错误堆栈里"Failed to exec spawn helper"这一行跟 git/SSH/凭据毫无关联——它指向的是更下层的 JVM 进程管理。如果第一时间扫日志,省下 30 分钟全在用错的模型解释现象。

教训写成两条:

命令行手动复现一切正常时,别再在"命令本身"上反复找原因。症状必然在被命令上层的某个调用者——它的环境跟你手动 shell 不一样。

看 config 文件看出"明显缺失"时,先验证那处缺失是不是当前症状的因。配置缺失常年存在却今天才暴露,多半不是它本身的事。

快速参考

报错对照

现场 关键字
UI 红字 The default value has been returned / An error occurred while download data / Please check the configuration
Jenkins 日志堆栈 Failed to exec spawn helper, exit value: 1 / Cannot run program "git"
systemd journal 旁路 jspawnhelper[...]: This command is not for general use ...

修复命令

# 1. 确认没有 in-flight build
curl -sS -G --user "$USER:$TOKEN" \--data-urlencode 'tree=computer[executors[currentExecutable[url]]]' \http://jenkins.example.com:8080/computer/api/json# 2. 重启 JVM(不是 reload)
systemctl restart jenkins

防护命令

# 钉死 OpenJDK 自动升级
apt-mark hold openjdk-17-jdk openjdk-17-jdk-headless \openjdk-17-jre openjdk-17-jre-headless
apt-mark showhold

人工升级 JDK 流程

apt-mark unhold 'openjdk-17-*'
apt update && apt upgrade
systemctl restart jenkins     # 不能省
apt-mark hold openjdk-17-jdk openjdk-17-jdk-headless \openjdk-17-jre openjdk-17-jre-headless

铁律

  • reload 不能修——只有换 JVM 进程才能修。
  • 升 JDK 必 restart——把这条写进 runbook,违反就是埋雷。
  • unattended-upgrades 默认不会重启你的服务——长跑 JVM 全部都要面对这个责任。
  • 以后再撞 "Failed to exec spawn helper" 不要再先怀疑业务层——这五个字直接指向 JVM 进程 + JDK 包版本错配。