Jenkins Helm 升级 2.516→2.568 后流水线失败与 Agent 无法连接的问题排查
记录时间:2026-07-20
环境:Kubernetes 1.28.15(arm64)/ Jenkins Helm chartjenkins/jenkins5.8.83 → 5.9.39(core 2.516.2 → 2.568.1)/ kubernetes 插件动态 agent / 私有 Harborreg-hub.gzeport.com
一、问题现象
Helm chart 从 5.8.83(core 2.516.2)升级到 5.9.39(core 2.568.1)后,所有共享库流水线(pipelinePublicLibrary)构建失败。表面报错出现在 post { failure } 阶段:
org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException:
Could not find credentials entry with ID 'null'
这个报错具有迷惑性——凭证 ID 是字面量 null,说明 environment {} 块根本没有生效,真实故障点在 stages 开始之前,post 阶段的凭证错误只是次生现象。
本次升级实际上是一条链上的三个独立问题,每修复一个才暴露下一个:
- Agent 镜像 JDK 版本与新 controller 不兼容(
UnsupportedClassVersionError) - JCasC 在
helm upgrade时重置了 cloud 配置(工作空间卷丢失) - UI 手工重建 Pod 模板时,jnlp 容器被误填了
sleep 9999999保活命令,agent 进程永远不启动
二、排查过程
2.0 前置:升级触发与 helm upgrade 未生效
触发原因:Jenkins 控制台报告”部分插件由于缺少依赖无法加载”。当前核心 2.516.2 不满足已安装插件的最低要求(Favorite 需 2.562+、Plugin Utilities API/Font Awesome/Bootstrap5 需 2.555.1+、Credentials 需 2.541.1+ 等),Pipeline/Credentials/Git/Kubernetes/Blue Ocean 等数十个插件连锁加载失败。
升级决策:Helm chart 5.8.83 → 5.9.39(appVersion 2.568.1,满足所有插件最低核心要求),controller 镜像本地化到 Harbor reg-hub.gzeport.com/library/jenkins/jenkins:2.568.1-lts-jdk21。
helm upgrade 执行后镜像未变:
helm upgrade jenkins ./jenkins-5.9.39.tgz -n devops -f values.yaml
命令执行成功(helm list 显示 chart 版本已变为 5.9.39),但 kubectl get sts jenkins -n devops -o jsonpath='{.spec.template.spec.containers[0].image}' 仍显示旧镜像 2.516.2。
原因:Jenkins chart 使用 StatefulSet 部署 controller。helm upgrade 会更新 StatefulSet 的 Pod 模板,但 StatefulSet 不会像 Deployment 那样自动触发滚动重建——模板变更后需要手动删除 Pod,StatefulSet 才会按新模板重建。
delete pod 卡在优雅关闭:
kubectl delete pod -n devops jenkins-0
命令卡住不返回。Jenkins controller 的 terminationGracePeriodSeconds 默认较长,且 preStop 钩子会等待正在执行的构建安全结束、将队列和构建状态持久化到 PVC。在日志确认当前无关键运行中构建后,强制跳过优雅关闭:
kubectl delete pod -n devops jenkins-0 --grace-period=0 --force
注意:
--grace-period=0 --force立即 SIGKILL 容器进程,跳过队列持久化。已落盘到 PVC 的 Job 配置和构建历史不会丢失,仅 in-flight(正在运行但尚未写盘)的构建状态可能丢失。执行前确认不在构建窗口期,且已备份 PVC 数据。
新 Pod 重建后,controller 正常启动,插件全部加载成功,依赖错误消失。但随后流水线构建暴露 agent 相关问题,进入后续排查。
2.1 定位次生错误的真实层级
失败日志只有 post 阶段的 CredentialNotFoundException。对比升级前同一 Job 的成功日志,确认:
WEBHOOK_URL等变量定义在流水线骨架的全局environment {}块- post 阶段取到
null,说明流水线在 environment 生效前就已失败 - 结论:先找升级后日志里第一个报错,不要被 post 阶段的报错带偏
2.2 第一个真实报错:agent 端类加载失败
Pod 能拉起、jnlp 能连接,但 agent 端抛出:
Caused by: java.lang.UnsupportedClassVersionError:
hudson/slaves/SlaveComputer$SlaveVersion has been compiled by ...
controller(2.568.1,JDK 21 编译)通过 remoting 下发的类,agent 侧的旧 JVM(JDK 17)拒绝加载。chart 5.9.x 起 controller 基线是 JDK 21,而共享库 podTemplate 固定的 inbound-agent:3327.v868139a_d00e0-6 是 5.8.x 时代的 JDK 17 运行时镜像。
2.3 试错 1(引入回归):升级镜像时在 YAML 块里写了 Groovy 注释
将 4 个流水线文件的 jnlp 镜像升级为 3383.vc8881d4b_0e76-1 时,在 podTemplate 的 yaml """...""" 多行字符串内加了 // 注释,导致 snakeyaml 解析失败:
could not find expected ':'
in 'reader', line 14, column 21:
// JDK 21 运行时,适配 controller 2.56 ...
注意:
yaml """..."""块内是纯 YAML 语境,注释必须用#;Groovy 的//注释只能写在字符串外。与「全角引号」同类——嵌入式语言块的语法跟宿主 Groovy 不同。
2.4 第二个问题:Pod 卡在 ContainerCreating,agent offline
YAML 修复后,Pod 创建但四个容器全部卡住,Jenkins 日志停在 is offline。检查 Jenkins 的 kubernetes cloud 配置发现:「工作空间卷」被重置为默认的 Generic Ephemeral Volume,而升级前用的是 PVC(jenkins-data-pvc)。
原因:该 chart 默认由 Configuration as Code(JCasC)管理配置,每次 helm upgrade 都会重新应用 values 里的 JCasC 配置,UI 手工修改的 cloud 配置会被覆盖重置。
2.5 第三个问题:卷改回后 agent 仍连不上——jnlp 被 sleep 占用
工作空间卷改回 PVC 后 Pod 变为 4/4 Running,但 controller 日志持续:
Waiting for agent to connect (270/300): dyck-jar-gzeport-anchorage-17-...
java.lang.IllegalStateException: Agent is not connected after 300 seconds, status: Running
用 kubectl 直查 Pod 的 jnlp 容器定义,发现:
command: ["sleep"]
args: ["9999999"]
再查 Jenkins 存活配置 config.xml,确认 UI 重建的 Pod 模板里 jnlp 容器被填了 sleep / 9999999。Jenkins UI 新增 Container Template 时,「运行的命令 / 命令参数」输入框会预填 sleep 9999999(这是给 maven、buildkit 等工具容器用的保活默认值)。jnlp 容器被它覆盖了镜像自带的 jenkins-agent 入口命令,容器只在睡觉,remoting 永远不会发起连接,5 分钟超时后 Pod 被杀重建,无限循环。
三、根因分析
| # | 根因 | 触发条件 | 表象 |
|---|---|---|---|
| 1 | chart 5.9.x 的 controller 为 JDK 21 基线,agent 镜像仍是 JDK 17 | Helm chart 跨 5.8→5.9 升级 | agent 端 UnsupportedClassVersionError |
| 2 | JCasC 在 helm upgrade 时重新套用 values,UI 手工配置被重置 |
cloud 配置只在 UI 里改过、未固化到 values | 工作空间卷变回默认值,Pod 卡 ContainerCreating |
| 3 | UI 新增容器模板预填 sleep 9999999,覆盖了 jnlp 入口命令 |
在 UI 手工重建 Pod 模板 | Pod Running 但 agent 300 秒连接超时 |
三者环环相扣:升级触发 1;修 1 需要动配置,暴露 2;在 UI 修 2 时引入 3。
四、解决方案
4.0 升级 chart 及本地化 controller 镜像
仓库配置改动(广州电子口岸-k8s部署yaml/k8s-jenkins/helm-jenkins/):
values.yaml 中 controller 镜像启用本地 Harbor 仓库:
controller:
image:
registry: "reg-hub.gzeport.com"
repository: "library/jenkins/jenkins"
tag: 2.568.1-lts-jdk21
| 参数 | 值 | 说明 |
|---|---|---|
registry |
reg-hub.gzeport.com |
避免信创节点直连 docker.io 拉取受限 |
repository |
library/jenkins/jenkins |
Harbor project=library,repo=jenkins/jenkins |
tag |
2.568.1-lts-jdk21 |
满足所有插件最低核心要求(最高需 2.562+),chart 5.9.39 配套版本 |
run.sh 中 chart 包引用由 5.8.83 改为 5.9.39,tag 注释同步更新。
新 chart 包 jenkins-5.9.39.tgz 通过 helm pull jenkins/jenkins --version 5.9.39 下载。执行前需 helm lint + helm template 渲染验证镜像地址和 values 兼容性:
helm lint ./jenkins-5.9.39.tgz -f values.yaml
# 预期:1 chart(s) linted, 0 chart(s) failed
helm template jenkins ./jenkins-5.9.39.tgz -f values.yaml -n devops \
| grep -E "reg-hub|jenkins/jenkins" | grep -v inbound-agent | sort -u
# 预期输出含 reg-hub.gzeport.com/library/jenkins/jenkins:2.568.1-lts-jdk21
旧 chart 包 jenkins-5.8.83.tgz 保留作回滚备份。
4.1 同步 JDK 21 的 agent 镜像到 Harbor
skopeo copy --insecure-policy docker://d.ajunyunwei.xyz/jenkins/inbound-agent:3383.vc8881d4b_0e76-1 docker://reg-hub.gzeport.com/cicd/jenkins/inbound-agent:3383.vc8881d4b_0e76-1
注意:
3383.vc8881d4b_0e76-1无 JDK 后缀的 tag 默认即 JDK 21 运行时;JDK 17 变体带显式-jdk17后缀。跨架构同步优先用skopeo copy --all保留完整多架构 manifest。
4.2 升级共享库 4 处 jnlp 镜像引用
改动文件(注释使用 YAML 的 #,不能用 Groovy 的 //):
| 文件 | 说明 |
|---|---|
vars/helmDevopsMultByk8sPipeline.groovy |
Helm JAR/WAR 统一骨架 |
vars/vueDevopsByk8s.groovy |
Vue 前端流水线 |
vars/jarDevopsMultbyDockerCompose.groovy |
JAR Docker Compose 流水线 |
vars/warDevopsMultbyDockerCompose.groovy |
WAR Docker Compose 流水线 |
# JDK 21 运行时,适配 controller 2.568.1(chart 5.9.x 起 controller 为 JDK 21 基线,旧 JDK 17 agent 会报 UnsupportedClassVersionError)
image: reg-hub.gzeport.com/cicd/jenkins/inbound-agent:3383.vc8881d4b_0e76-1
maven 容器的 openjdk-11 不需要动——它只是构建工具,业务代码继续用 JDK 11 编译;需要 JDK 21 的只有 jnlp(agent 本体运行时)。
4.3 恢复 kubernetes cloud 配置
Manage Jenkins → Clouds → kubernetes 中确认/恢复以下项:
| 配置项 | 值 | 说明 |
|---|---|---|
| 工作空间卷 | Persistent Volume Claim Workspace Volume,claim jenkins-data-pvc,Read Only 不勾 |
升级后被 JCasC 重置为 Generic Ephemeral Volume,集群无默认 StorageClass 时 Pod 会永远 Pending |
| 凭据 | – 无 – | Jenkins 在集群内运行,走 Pod 自身 ServiceAccount,连接测试显示 Connected to Kubernetes v1.28.15 即正常 |
| jnlp 容器「运行的命令 / 命令参数」 | 全部清空 | UI 预填的 sleep 9999999 会覆盖镜像入口命令;只有 maven/buildkit 等工具容器才需要 sleep 保活 |
| jnlp 容器「总是拉取镜像」 | 取消勾选 | tag 是固定版本号,IfNotPresent 即可,与升级前一致 |
五、验证
# 1. Pod 事件确认镜像拉取与挂卷正常
kubectl -n devops describe pod <agent-pod> | sed -n '/Events:/,$p'
# 预期:Scheduled → Pulling → Pulled → Created → Started,无 FailedMount/ErrImagePull
# 2. 确认 jnlp 容器未被 sleep 覆盖
kubectl -n devops get pod <agent-pod> \
-o jsonpath='{.spec.containers[?(@.name=="jnlp")].command}'
# 预期:空输出(使用镜像默认入口 jenkins-agent)
# 3. controller 日志确认 agent 连接
kubectl -n devops logs jenkins-0 -c jenkins --tail=100 | grep -i "launch\|connect"
# 预期:不再出现 "Waiting for agent to connect (N/300)" 循环
验证结论:修复后构建 #18 的 agent Pod 正常连接,构建全流程(Maven 编译 → BuildKit 镜像推送 → ArgoCD 更新与同步)恢复,Pod 在构建完成后正常回收;后续其他 Job(Vue 流水线)也已恢复调度。
六、注意事项
- 排查顺序:
post.failure里credentialsId 'null'类报错 = 全局environment {}未生效 = 故障在 stages 之前,先去找日志里第一个真实报错,不要修表象。 - Controller 与 agent 的 JDK 基线必须配套:升级 chart 大版本前,先查 controller 镜像的 JDK 基线(
5.9.x起为 JDK 21),agent 的inbound-agent镜像随之升级。业务构建容器(maven 等)的 JDK 与此无关。 - JCasC 会覆盖 UI 配置:chart 默认每次
helm upgrade重新套用 JCasC。cloud 配置(Pod 模板、工作空间卷、tunnel 地址)应固化到 chart values 的controller.JCasC.configScripts,只在 UI 里改的配置迟早会丢。 - UI 新建容器模板的
sleep 9999999预填值:只适用于工具容器;jnlp 容器的命令/参数必须留空,否则 agent 永远 offline 且 Pod 状态是正常的 Running,极具迷惑性。 - 镜像同步用 tag 不用 digest:
docker pull image@sha256:...拿到的是单架构 manifest,arm64 集群从 Harbor 拉取时可能架构不匹配;用skopeo copy --all或按 tag 同步保留多架构索引。 yaml """..."""块内注释只能用#:Groovy//会被 snakeyaml 当作语法错误,podTemplate步骤直接失败。- StatefulSet 升级不自动滚动重建:
helm upgrade更新 StatefulSet 模板后,Pod 不会自动重建——需手动kubectl delete pod触发 StatefulSet 按新模板拉起新 Pod。这与 Deployment 的自动滚动更新行为不同,是 StatefulSet 的设计特性。升级完成后务必kubectl get pod -o jsonpath='{.spec.containers[0].image}'确认镜像已更新。 - Jenkins controller Pod 的优雅关闭可能卡很久:
terminationGracePeriodSeconds+ preStop 钩子会等待运行中构建结束。日常重启可用kubectl delete pod --grace-period=0 --force跳过等待(仅丢失 in-flight 构建状态,已落盘数据不受影响),但执行前需确认 PVC 已备份、不在构建窗口期。删除卡住时按Ctrl+C中断本地 kubectl 等待不会影响 server 端删除操作。