代码明明修了,板端为什么还在跑旧版本
“源码已经改了”与“板端正在运行修复后的程序”之间,隔着构建、打包、上传、安装、服务重启和进程替换。任何一层发生漂移,日志看起来就像新版本,真实执行文件却仍然来自旧 release。
这次发布链排障里,真正的问题并不在算法代码,而在资产路径、包内容和版本身份没有形成闭环。
现场:每一层单独看都像是对的
源码里已经替换了配置和模型路径,CI 也能运行,服务安装后还是 active。但检查最终 package 和板端进程时,陆续发现:
- 运行时读取的配置实际位于
/etc,打包脚本却只放进 install tree; - 模型文件名改了,CI 或 health check 仍引用旧名称;
- payload 脚本的默认路径依赖调用时 cwd;
- 宿主机只完成 staging,没有生成真实
.deb; - 板端降级后,旧进程没有完全停止,仍然持有相机设备。
这类问题最难的地方是:每个阶段都可能给出局部成功。
第一个根因:运行时路径与打包路径不是一回事
构建系统把默认配置安装到 share/...,不代表运行时就从那里读取。源码若固定读取 /etc/camera.json,factory package 必须显式把文件 stage 到 package root 的 /etc。
排查时不能从打包脚本猜运行时路径,应该从当前源码的 open/read 入口反查权威位置,再检查最终包内容。
第二个根因:模型改名没有全链更新
运行时资产路径通常同时出现在:
- service 或 launch 默认值;
- payload 脚本;
- CI 检查;
- health check;
- 文档与示例命令。
只替换源码中的模型名,会让 package、CI 和 runtime 各自持有不同答案。正确做法是把它们当成一份 release contract 一起搜索、修改和测试,并删除无意保留的 legacy fallback。
第三个根因:脚本依赖当前目录
脚本在 repo root 执行时正常,从 CI 或其他目录调用就找不到默认模型。这说明资源路径是相对 shell cwd,而不是相对脚本或仓库根目录。
修复后的原则是:先稳定解析 repo_root,再构造默认资源路径。用户显式传入的路径可以覆盖默认值,但默认行为不能依赖“大家刚好都从同一目录运行”。
第四个根因:Staging 不是 Package 证据
宿主机缺少 dpkg-deb 时,脚本可以把文件摆进临时 package tree,让目录检查全部通过,却没有生成真正可安装的包。
最终验证必须回到可信容器或目标架构构建环境:真实生成 .deb,列出包内容,安装到干净环境,并检查 post-install/service 行为。目录看起来正确,只能算中间证据。
建立 Source Commit 追踪
每个 production package 应携带:
- 人可读的 app version;
- 完整
source_commit; - 构建时间和目标架构;
- 关键资产版本或 hash;
- 构建 profile。
版本可以类似:
prod-YYYYmmdd-HHMMSS-g<shortsha>同时在运行时版本文件中保存完整 commit。这样 deb 文件名、板端状态接口和源码提交可以互相核对,而不是依赖 CI job 名或聊天记录。
板端验收不能停在安装成功
部署后按以下顺序确认:
- 包管理器显示目标版本已安装;
- 运行时版本文件中的
source_commit正确; - systemd 的
MainPID已变化; /proc/<pid>/exe指向目标 release;- 旧进程与旧 container 已停止;
- 设备没有被旧 owner 占用;
- topic、rate、socket 和 health 输出符合新版本合同。
一次 A/B 测量中,版本切换后旧相机进程仍然存活,导致设备占用和基线异常。这个问题说明,package version 对了也不能代替进程身份验证。
结论
发布可追踪性不是在版本页面多显示一个 commit id,而是把源码、资产、package、安装状态和真实进程连成一条证据链。
当板端行为与预期不一致时,第一步不该继续改代码,而应该先证明正在运行的到底是哪一份代码、哪一套配置和哪个模型。