宁语之溪
思君如满月,夜夜减清辉。 (唐·张九龄·赋得自君之出矣)
自豪地使用 Typecho 建站搭配使用 🌻Sunny 主题博主 昨天 13:54 在线 · 当前 28 人活跃
文章

旧 JAR 与源码不一致时,如何定位部署版本问题

语之溪

·

容器与部署

·

⚠️ 本文最后更新于2026年05月02日,已经过了114天没有更新,若内容或图片失效,请留言反馈

在 4 月的 HAOVP 接口验收中,我遇到过一种很容易误导排查的问题:本地源码已经包含修复,但运行中的接口行为仍像旧代码。继续检查业务逻辑时,怎么都解释不通。

最后确认,部分服务容器运行的还是旧 JAR,与本地源码不一致。重新构建并更新运行产物后,相关服务才与源码对齐。

这类问题提醒我,部署中的“版本”不是一个点,而是一条链:

源码版本
    -> 编译生成的 JAR
    -> 镜像中的 JAR
    -> 运行容器实际加载的 JAR
    -> 对外表现出来的接口行为

任何一层没有更新,都会出现“代码明明改了,服务为什么没变化”。

先判断是不是版本问题

遇到以下现象时,我会先怀疑运行版本,而不是立即继续改业务代码:

  • 本地源码中明确存在修复,运行结果却完全没有变化;
  • 日志格式或错误信息仍是旧版本内容;
  • 某些服务行为已更新,另一些服务仍保持旧行为;
  • 重启容器后问题不变;
  • 接口文档、源码和运行响应互相矛盾;
  • 同一个问题在 IDE 运行时消失,在容器环境中仍然存在。

这些现象不能单独证明 JAR 过期,但足以让版本核对提前,而不是等业务逻辑排查一圈后再检查部署。

HAOVP 的构建链路

HAOVP 的业务服务先由 Maven 构建,在各模块的 target 目录生成 JAR。Dockerfile 再把匹配的模块 JAR 复制到镜像内,作为固定名称的应用文件运行。

一个简化结构是:

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/service-*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

示例隐藏了实际模块名称,不包含镜像仓库和服务器路径。

这条链路至少包含四个可能过期的位置:

Git 工作区
Maven target 目录
Docker 镜像层
当前运行容器

只执行容器重启,不会自动重新编译源码;只重新编译 JAR,也不会自动替换已经构建好的镜像;只构建镜像,如果容器没有重新创建,运行实例仍可能使用旧镜像。

第一步:确认源码本身

先确认准备验证的修复确实存在于当前工作区:

git status
git log -1 --oneline
git diff

这些命令只用于说明检查思路。实际仓库如果有未提交改动,需要同时记录提交版本和工作区差异,不能只写一个提交号就认为源码状态完整。

我会进一步确认:

  • 修改发生在哪个模块;
  • 构建命令是否会构建该模块;
  • 修改是否已经保存;
  • 当前分支是否正确;
  • 是否存在同名类或旧目录;
  • 配置来自本地文件还是配置中心。

有时接口行为不变不是 JAR 旧,而是修改了没有被实际加载的配置或另一个同名实现。

第二步:检查 JAR 是否重新生成

项目文档中的构建顺序是先在后端目录执行 Maven 构建,再重建业务服务:

mvn clean package -DskipTests
docker compose up -d --build service-a service-b

服务名是通用占位示例。

clean 会清理旧构建目录,package 重新生成 JAR。跳过测试只能减少构建环节,不能证明代码正确;正式验收前仍要运行适合的编译、测试和接口检查。

构建完成后,我会确认:

  • 目标模块的 JAR 是否存在;
  • 文件时间是否对应本次构建;
  • 构建日志是否真的包含目标模块;
  • Maven 是否因为依赖失败而跳过后续模块;
  • target 中是否残留多个能匹配通配符的 JAR;
  • Dockerfile 的 COPY 模式最终会选择哪个文件。

如果目录里同时存在旧版本、原始 JAR 和重新打包 JAR,宽泛通配符可能产生歧义。更稳妥的做法是让构建产物名称可预测,并在镜像构建前验证匹配结果。

第三步:确认镜像真的重建

docker compose restart 只会重启现有容器,不会重新执行 Dockerfile。

需要构建新镜像时,应明确使用构建操作:

docker compose build service-a
docker compose up -d service-a

或者使用项目文档中的 up -d --build

即使带了 --build,仍要观察构建日志。Docker 会使用构建缓存;如果上下文、忽略规则或文件时间判断出现偏差,某些层可能被复用。

我会检查:

  • Docker 的构建上下文是否包含新 JAR;
  • .dockerignore 是否排除了目标文件;
  • COPY 层是否重新执行;
  • 新镜像的创建时间和标识是否变化;
  • Compose 使用的镜像名是否就是刚构建的镜像;
  • 是否有同名旧镜像或远程镜像覆盖本地构建。

不能只看到“build completed”就结束,还要确认目标服务使用了这次产物。

第四步:确认容器被重新创建

镜像更新后,运行容器还需要指向新镜像。

docker compose ps
docker inspect <container>

这些命令可以帮助检查容器创建时间、镜像标识和运行状态。公开记录不保留真实容器名、主机路径和内部网络信息。

重点确认:

  • 容器是否刚刚重新创建;
  • 容器使用的镜像标识是否与新镜像一致;
  • 服务是否启动成功,而不是反复重启;
  • 是否有多个旧容器仍在对外提供服务;
  • 反向代理或网关是否仍把请求转发到旧实例。

只更新一个副本时,如果负载入口仍在新旧实例之间分流,接口可能一会儿是新行为、一会儿是旧行为,看起来像随机故障。

第五步:核对容器内实际文件

当外部信息仍无法确定时,可以检查容器内的实际运行文件,而不是只看宿主机上的 JAR。

检查目标包括:

容器内应用文件是否存在
文件大小与修改时间是否符合预期
Java 进程启动参数指向哪个文件
启动日志是否来自当前版本

如果项目有条件,可以在构建时写入版本元数据,例如 Git 提交号、构建时间和应用版本,并通过 Actuator info 或启动日志暴露非敏感版本信息。

build.commit=${GIT_COMMIT}
build.version=${PROJECT_VERSION}

这是通用设计示例,不对应当前已部署配置。

版本信息必须避免包含用户名、本地绝对路径、仓库凭据和内部地址。

第六步:让接口行为证明版本

文件时间和镜像标识能说明部署链路,但最后还要验证原问题对应的接口行为。

我会选择最小可判定用例:

  • 修复的是公开路径,就验证未登录请求是否按预期放行;
  • 修复的是字段映射,就检查关键字段是否正确返回;
  • 修复的是权限,就同时测试允许和拒绝场景;
  • 修复的是事务时序,就检查响应、数据库副作用和后续查询;
  • 修复的是异常处理,就触发明确且安全的错误条件。
版本证据
    + 目标接口复测
    + 关键副作用核对
    = 运行版本与修复行为基本对齐

只看到容器启动不能证明修复生效;只看到接口偶尔成功,也不能排除仍有旧实例参与流量。

为什么覆盖容器内 JAR 只适合临时排查

验收报告记录了重新构建并覆盖容器内应用 JAR 后,相关服务与本地源码对齐。这个动作能够快速确认“问题是否来自旧产物”,但不适合作为长期部署流程。

直接覆盖运行容器有几个问题:

  • 容器重建后修改会丢失;
  • 镜像内容与运行容器内容不再一致;
  • 其他节点和副本不会自动同步;
  • 很难通过镜像标识追溯真实版本;
  • 回滚过程不清晰。

确认根因后,更稳定的闭环仍然是:重新构建 JAR、重新构建镜像、重新创建容器,并记录版本。

多服务项目要避免只更新一半

微服务项目中,一次改动可能同时涉及调用方、被调用方和公共模块。

例如:

公共 DTO 变化
    -> 服务 A 重新编译
    -> 服务 B 也需要重新编译
    -> 两边镜像都要更新

如果只更新一端,可能出现字段缺失、序列化失败、接口签名不一致或业务判断错误。

我会根据改动范围列出受影响服务:

  • 公共模块变更影响哪些依赖方;
  • 配置中心的变更是否需要重启;
  • 数据库迁移是否先于应用发布;
  • 前端是否依赖新的响应字段;
  • 网关路由和白名单是否同时变化;
  • 回滚时新旧版本能否兼容。

部署单个服务之前,先理解改动传播范围,可以减少“某个服务是新版本,另一个服务仍按旧协议运行”的问题。

把版本核对加入验收流程

这次排查后,我会把版本核对放在接口验收之前:

记录源码版本和工作区状态
    -> 清理并构建目标模块
    -> 核对 JAR
    -> 构建镜像
    -> 重新创建容器
    -> 核对镜像与运行文件
    -> 检查启动和健康状态
    -> 执行接口用例

验收报告中也应记录非敏感版本信息。这样出现失败时,可以先判断是代码缺陷、配置问题、数据前提,还是部署了错误版本。

这类问题的真正根因

旧 JAR 问题表面上是“忘了重新构建”,更深层原因是构建和部署链路缺少可见的版本证据。

如果源码、JAR、镜像和容器都没有统一版本标识,排查只能依赖文件时间和行为猜测。更完整的改进方向包括:

  • 构建产物携带提交版本;
  • 镜像使用不可变版本标签或摘要;
  • 启动日志输出应用版本;
  • 健康或信息端点提供非敏感构建信息;
  • 部署记录保存服务与镜像映射;
  • 发布后自动执行最小冒烟测试;
  • 回滚使用明确的上一版本,而不是临时复制文件。

当运行行为与源码不一致时,先沿着“源码—JAR—镜像—容器—流量入口”逐层核对,通常比继续修改业务代码更快。部署版本可追溯,接口验收得到的结果才知道是在验证哪一份代码。

现在已有 11 次阅读,0 条评论,0 人点赞
评论:共0条
发表
搜索 消息 足迹 排行
你还不曾留言过..
你还不曾留下足迹..
博主 不再显示
博主
未知作品 歌曲封面
立即安装