Skip to content

Java ci cache #184

@aibangjuxin

Description

@aibangjuxin

下面这个问题本质上是一个典型的 Java / Spring Boot 依赖冲突 + 构建产物不一致问题,在 CI/CD(尤其是多 pipeline、GKE 部署)场景里非常常见。

我按你要求给你一个可落地的排查路径 + 快速止血方案。

一、问题分析(核心原因)

1️⃣ 报错本质

java.lang.NoSuchMethodError:
org.yaml.snakeyaml.representer.Representer.()V not found

这是 典型的运行期依赖版本不兼容错误:
• 编译期:用的是新版本 snakeyaml
• 运行期:Classpath 里实际加载的是旧版本 snakeyaml
• Spring Boot 2.6.x / 2.7.x 对 SnakeYAML 的构造函数要求不同

❗ NoSuchMethodError ≠ 编译错误
❗ 100% 是 Jar 冲突 / 老依赖被加载

2️⃣ 关键异常点(非常重要)

日志中明确显示:

[spring-boot-2.6.6.jar!/:2.6.6]

⚠️ 这说明你的最终运行产物里,仍然存在 spring-boot-2.6.6

即使你本地看到:

spring-boot 2.7.10

部署到 GCP 的 Jar / Image 并不是你以为的那个版本

二、最常见的 5 类原因(按概率排序)

✅ 原因 1(90%):CI Pipeline 使用了缓存的旧构建产物

典型场景
• Maven / Gradle cache
• Docker layer cache
• Artifact Registry / GCR 镜像 tag 复用(latest)

表现
• 本地 OK
• CI OK
• Runtime 报旧版本

✅ 原因 2:Dockerfile 构建阶段没有触发重新打包

示例问题:

COPY target/app.jar app.jar

但:
• target/app.jar 实际是上一次 pipeline 的产物
• Maven 没有真正执行 repackage

✅ 原因 3:多 Module 项目中父 POM / BOM 版本未统一

org.springframework.boot spring-boot-starter-parent 2.6.6

但子模块里:

<spring-boot.version>2.7.10</spring-boot.version>

👉 父 POM 优先生效

✅ 原因 4:snakeyaml 被其他依赖显式或隐式拉低版本

常见“元凶”:
• spring-cloud
• swagger
• kafka
• 老的 commons-*

✅ 原因 5:GKE 使用了旧 Image / Rollout 没发生
• Deployment 没有变更 image tag
• Pod 实际未重建
• 节点上拉的是旧 Image

三、快速止血方案(立刻可用)

✅ 方案 1:强制清理 + 重新构建(最推荐)

Pipeline 中必须加入:

mvn clean package -U

或(Gradle):

./gradlew clean build --refresh-dependencies

✅ 方案 2:禁用 Docker 缓存(一次性验证)

docker build --no-cache -t your-image:dev .

✅ 方案 3:明确 pin SnakeYAML 版本(短期兜底)

org.yaml snakeyaml 1.33

⚠️ 这是 止血方案,不是根因修复

四、标准排查流程(强烈建议你按顺序做)

Step 1️⃣ 确认最终 Jar 内部到底是什么版本(关键)

jar tf app.jar | grep spring-boot

或:

jar tf app.jar | grep snakeyaml

你会直接看到 2.6.6 还是 2.7.10

Step 2️⃣ 在容器内确认运行期 Classpath

kubectl exec -it pod-name -- sh

ls /app/libs | grep yaml

或:

java -verbose:class -jar app.jar | grep snakeyaml

Step 3️⃣ Maven 依赖反查(重点)

mvn dependency:tree | grep snakeyaml

如果看到多个版本:

snakeyaml:1.26
snakeyaml:1.33

👉 100% 会炸

Step 4️⃣ 确认 Spring Boot 真正来源

mvn help:effective-pom | grep spring-boot

你会看到:
• 到底是 parent
• 还是 BOM
• 还是被 dependencyManagement 覆盖

Step 5️⃣ GKE 层面确认是否真的 Rollout

kubectl rollout status deploy your-app
kubectl describe pod your-pod | grep Image

五、最佳实践(防止再次发生)

✅ 1️⃣ CI 强制 fail fast

mvn -DskipTests=false clean verify

✅ 2️⃣ Image Tag 禁止使用 latest

app:dev-20260102-commitSHA

✅ 3️⃣ Spring Boot / Cloud 版本矩阵检查

Spring Boot 推荐 SnakeYAML
2.6.x 1.30
2.7.x 1.33

✅ 4️⃣ 构建产物校验(非常重要)

在 pipeline 增加一步:

java -jar app.jar --version

或:

unzip -p app.jar META-INF/MANIFEST.MF

六、结论一句话总结

不是 GCP 缓存问题,而是构建产物与运行产物不一致

Spring Boot 2.6.6 的 Jar 被实际运行,导致 SnakeYAML 构造函数不匹配

如果你愿意,下一步我可以直接帮你:
• 审一次 POM(parent + BOM)
• 看 Dockerfile 是否存在缓存陷阱
• 给你一个“防依赖漂移”的 CI 模板

你只需要贴其中一个即可。

下面给你一套**“可直接落地 + 可长期治理”的完整解决方案**,专门针对
👉 CI Pipeline 使用缓存的旧构建产物
👉 本地 OK / CI OK / Runtime 报旧版本
👉 Maven / Docker / GCR(latest) 三重缓存叠加

我会按 问题 → 修复 → 规范 → 校验 四个层级来给你。

一、问题本质回顾(先统一认知)

CI 的成功 ≠ Runtime 使用的是新构建产物

你的场景本质是 “构建链路中任意一层缓存失效”:

源码更新

Maven / Gradle 缓存(未刷新)

Docker layer cache(COPY 复用)

Image tag 复用(latest)

GKE Pod 未真正使用新 Image

只要中间任一层复用了旧内容,就会导致:
• Jar 里仍是 spring-boot-2.6.6
• Runtime 直接抛 NoSuchMethodError

二、完整修复方案(分 4 层)

第一层:构建工具层(Maven / Gradle)

✅ 目标

确保每次 CI 构建出来的 Jar 一定是新的

✅ Maven 标准修复方案(推荐)

Pipeline 强制使用:

mvn clean package -U

说明:

参数 作用
clean 删除 target,避免旧 jar
-U 强制刷新 SNAPSHOT / metadata
package 触发 spring-boot repackage

🚫 错误示例(非常常见)

mvn package

问题:
• 不清 target
• 不刷新依赖
• CI Workspace 复用时 极易中招

✅ Gradle 对应方案

./gradlew clean build --refresh-dependencies

🔒 推荐增强(防止隐性失败)

在 CI 中加入:

mvn help:effective-pom | grep spring-boot

如果看到不是期望版本,直接 fail pipeline

第二层:Docker 构建层(最容易踩坑)

1️⃣ Dockerfile 结构标准化(强烈推荐)

❌ 高风险 Dockerfile

COPY target/app.jar app.jar

问题:
• 只要 target/app.jar 文件时间戳不变
• Docker 会直接复用 layer

✅ 正确的多阶段构建(最佳实践)

FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]

优势:

优点 说明
Maven 构建在容器内 不依赖 CI Workspace
target 不复用 彻底切断旧 jar
Docker cache 可控 pom 变才缓存

2️⃣ CI 强制验证阶段(非常重要)

docker run --rm your-image java -jar /app/app.jar --version

在 push 之前,验证 Jar 真实版本

3️⃣ 紧急止血(一次性)

docker build --no-cache -t your-image:dev .

⚠️ 仅用于验证,不建议长期使用

第三层:镜像仓库层(Artifact Registry / GCR)

❌ 高风险行为(必须禁止)

image: your-app:latest

latest = 不可控 + 不可审计 + 不可回滚

✅ 标准镜像 Tag 策略(强烈推荐)

your-app:
dev-20260102-
prod-20260102-

示例:

IMAGE_TAG=dev-$(date +%Y%m%d)-$(git rev-parse --short HEAD)
docker build -t us-docker.pkg.dev/proj/repo/app:$IMAGE_TAG .
docker push us-docker.pkg.dev/proj/repo/app:$IMAGE_TAG

✅ GKE Deployment 强制使用新 Tag

spec:
containers:

  • name: app
    image: us-docker.pkg.dev/proj/repo/app:dev-20260102-a1b2c3
    imagePullPolicy: IfNotPresent

🚫 不要依赖

imagePullPolicy: Always

👉 它不能解决“Jar 本身是旧的”问题

第四层:GKE 运行时校验(最后防线)

1️⃣ 确认 Pod 实际使用的 Image

kubectl describe pod | grep Image

2️⃣ 在 Pod 内确认 Jar 内容(推荐)

kubectl exec -it -- sh

jar tf /app/app.jar | grep spring-boot

或:

java -verbose:class -jar /app/app.jar | grep snakeyaml

3️⃣ 强制重建(必要时)

kubectl rollout restart deploy your-app

三、CI Pipeline 标准模板(可直接用)

steps:

  • name: build
    script:

    • mvn clean package -U
    • jar tf target/app.jar | grep spring-boot
  • name: docker-build
    script:

    • IMAGE_TAG=dev-${CI_COMMIT_SHORT_SHA}
    • docker build -t app:$IMAGE_TAG .
    • docker run --rm app:$IMAGE_TAG java -jar /app/app.jar --version
  • name: docker-push
    script:

    • docker push app:$IMAGE_TAG
  • name: deploy
    script:

    • kubectl set image deploy/app app=app:$IMAGE_TAG

四、长期治理建议(防止再次发生)

层级 规则
Maven 必须 clean
Docker 多阶段构建
Image 禁用 latest
CI 构建后校验 Jar
GKE Tag 变更触发 rollout

五、一句话总结(给你对外用)

这是一个典型的 CI 构建缓存污染问题

根因不是 GCP,而是 Jar 构建、Docker layer、Image tag 三层缓存叠加

解决方案必须从构建、镜像、部署三端同时收紧

如果你愿意,下一步我可以:
• 🔍 直接帮你审一个 Dockerfile
• 🔍 给你现有 CI Pipeline 打“防缓存补丁”
• 🔍 输出一份《平台级 Java 服务构建规范》

你选一个即可。

下面我给你一套**“可直接发给用户 / 团队”的标准化排查信息收集方案**,目标只有一个:

用最少来回,把 CI 是否使用了缓存、是否产出了旧 Jar,一次性确认清楚

这不是技术实现方案,而是 你作为平台 / 基础设施方,如何“正确地向用户要信息”。

一、定位策略(先给你思路)

你现在已经做了正确的一步判断:
• ❌ 不纠结 Spring / SnakeYAML 细节
• ✅ 初步定位到 CI 构建产物 ≠ 运行产物

所以你向用户要信息时,必须围绕这三点:

  1. Jar 是什么时候、在哪一步生成的?
  2. Docker 是不是复用了旧 layer?
  3. GKE 实际跑的是不是新 Image?

二、给用户的【信息收集清单】— 直接可用

✅ 建议你 原样发给用户

这是平台视角、非常专业、不会引起对方反感

1️⃣ CI Pipeline 构建阶段信息(最关键)

请用户提供 完整构建日志 中以下关键点(不是截图,是原始 log):

A. Maven / Gradle 执行命令

✔️ 完整的 mvn / gradle 命令
✔️ 是否包含 clean / -U / --refresh-dependencies

示例(正确):

mvn clean package -U

示例(高风险):

mvn package

B. 构建产物确认(必须)

请用户在 CI 中增加并贴出以下输出:

jar tf target/*.jar | grep spring-boot

或:

unzip -p target/*.jar META-INF/MANIFEST.MF

目的:确认 Jar 内真实 Spring Boot 版本

2️⃣ Docker 构建阶段信息(90% 的坑在这里)

请用户提供:

A. 完整 Dockerfile

特别关注:

COPY target/*.jar

以及是否使用了:

--from=builder

B. Docker Build 日志(重点关键词)

让用户检查并提供包含以下关键词的日志行:

Using cache
CACHED
#x CACHED

你可以直接告诉用户:

如果 build log 中出现 Using cache,请全部贴出来

C. Docker Build 命令

docker build -t xxx .

还是:

docker build --no-cache

3️⃣ 镜像 Tag & 推送信息(非常容易被忽略)

请用户提供:

✔️ Image 完整名称(含 tag)
✔️ Push 日志

你要重点看:

Successfully pushed
digest: sha256:xxxx

4️⃣ 部署阶段(GKE / Cloud Run)

A. Deployment 使用的 Image

kubectl describe pod | grep Image

或 Deployment YAML 中:

image: xxx:???

B. 是否真的发生 Rollout

kubectl rollout history deploy

5️⃣ Runtime 校验(最后确认)

让用户 任选其一 在 Pod 内执行:

jar tf /app/app.jar | grep spring-boot

或:

java -verbose:class -jar /app/app.jar | grep snakeyaml

三、你在日志中要“解锁”的关键词(重点)

你在看用户 CI 日志时,只盯这些关键词即可:

🔍 Maven / Gradle

Reactor Build Order
Skipping tests
BUILD SUCCESS

⚠️ 但重点是有没有:

Cleaning target
Downloading from central

🔍 Docker

Using cache
CACHED
Step X/Y
COPY target

一旦看到:

COPY target/app.jar
---> Using cache

👉 可以直接下结论:旧 Jar 被复用

🔍 Image Tag

latest
dev
stable

只要看到 latest,直接标红

四、你可以给用户的【标准定位话术】

你可以直接复制用 👇

从当前错误看,这是一个 运行期加载了旧 Spring Boot 依赖 的典型问题

初步判断 不是代码问题,而是 CI 构建产物或 Docker 构建阶段复用了缓存

为了进一步确认,请提供以下信息(用于确认最终运行的 Jar 是否为最新构建):
1. CI 中 Maven / Gradle 的完整执行命令
2. CI 中 jar tf target/*.jar | grep spring-boot 的输出
3. Dockerfile 原文 + docker build 日志(关注是否出现 Using cache)
4. 实际部署使用的 Image 名称(含 tag)
5. Pod 内查看 Jar 版本的输出

有了以上信息,可以直接定位是否为缓存导致的旧构建产物被使用。

五、平台侧“兜底建议”(可选,但很加分)

你可以顺带建议用户:
• 在 CI 中 临时加入

mvn clean package -U
docker build --no-cache

•	如果问题消失 👉 100% 缓存问题

六、一句话总结(你自己用)

这个阶段不是解决问题,而是“收敛证据”

你要的是:构建命令、Jar 内容、Docker 缓存命中点

只要这三点拿到,问题基本就结束了

如果你愿意,下一步我可以帮你:
• 🧩 设计一份平台统一的 CI Debug Checklist(Markdown)
• 🧩 帮你把这些检查项固化成 CI 模板
• 🧩 模拟一次用户 CI 日志,我帮你快速判读

你选一个,我直接给。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions