版本号管理:语义化版本号规范、版本号在Rootfs中的实践、版本号存储位置
版本号这东西,看着简单,但搞不好就是个大坑。我见过太多团队因为版本号混乱,导致升级失败、回滚困难,甚至线上事故。说白了,版本号就是Rootfs的身份证,你得让系统、让运维、让自动化工具一眼就能看懂这个文件系统是什么版本、能不能升级、会不会兼容。
这一章,我们就来聊聊版本号怎么管、怎么用、怎么存。
语义化版本号规范
先说说业界通用的语义化版本号规范(SemVer)。它的格式很简单:主版本号.次版本号.修订号,比如 2.1.3。
但每个数字背后的含义,你得搞清楚:
| 字段 | 含义 | 什么时候增加 |
|---|---|---|
| 主版本号 | 不兼容的API或重大变更 | Rootfs结构、核心库、ABI发生破坏性变化时 |
| 次版本号 | 向下兼容的功能新增 | 增加新功能、新驱动、新服务,但不影响旧版本 |
| 修订号 | 向下兼容的问题修复 | 修bug、安全补丁、配置优化 |
核心原则:版本号一旦发布,就不能再修改。任何变更都必须通过增加新版本号来表达。
我个人习惯在Rootfs版本号后面再加一个构建元数据字段,比如 2.1.3+build20240501。这个构建号不参与版本比较,但能帮你快速定位是哪个CI流水线、哪次编译出来的。
版本号在Rootfs中的实践
理论说完了,咱们看看实际怎么用。我在项目中遇到过一个问题:两个设备,一个跑着 1.2.0,一个跑着 1.3.0,但它们的Rootfs结构完全不一样。结果升级脚本没做版本判断,直接把 1.2.0 的配置覆盖到了 1.3.0 上,导致服务全崩。
所以,版本号在Rootfs升级中,至少要干这几件事:
- 升级路径判断:从
1.x.x到2.x.x需要全量升级,从1.2.x到1.3.x可以增量升级。 - 兼容性检查:升级脚本根据版本号决定是否执行数据迁移、配置转换。
- 回滚决策:如果新版本启动失败,系统根据版本号判断能否回滚到旧版本。
- 日志追溯:出问题时,版本号是定位问题的第一线索。
这里我建议你定义一个版本比较函数,而不是简单做字符串比较。因为 2.1.0 比 2.0.9 大,但字符串比较会认为 2.0.9 更大。嗯,这个坑我踩过。
// 伪代码:版本比较函数
int compare_version(const char *v1, const char *v2) {
int major1, minor1, patch1;
int major2, minor2, patch2;
sscanf(v1, "%d.%d.%d", &major1, &minor1, &patch1);
sscanf(v2, "%d.%d.%d", &major2, &minor2, &patch2);
if (major1 != major2) return major1 - major2;
if (minor1 != minor2) return minor1 - minor2;
return patch1 - patch2;
}
版本号存储位置:/etc/version
版本号放哪儿?我见过有人放在 /etc/os-release、/etc/issue、甚至放在 /proc 下的虚拟文件里。但我的建议是:统一放在 /etc/version。
为什么是 /etc/version?
- 约定俗成:很多嵌入式Linux发行版都这么干,工具链和脚本可以直接读取。
- 简单明了:一个文件,一行内容,不需要解析复杂的INI或JSON格式。
- 原子更新:升级时先写临时文件,再rename覆盖,保证版本号不会读到一半被截断。
文件内容格式,我建议这样:
ROOTFS_VERSION=2.1.3
BUILD_DATE=2024-05-01
BUILD_ID=ci-pipeline-1234
PRODUCT=my-embedded-device
小技巧:在构建Rootfs时,把版本号写死到 /etc/version 里。不要在启动时动态生成,否则回滚后版本号会乱掉。
我曾经遇到一个案例:某团队把版本号放在 /data 分区,结果用户重置数据后版本号丢了,系统再也无法升级。所以,/etc/version 必须放在只读的Rootfs分区里,或者至少放在不会被用户随意擦除的位置。
版本号管理流程图
下面这张图,展示了从构建到升级再到回滚的完整版本号流转过程:
避坑指南
最后,分享几个我踩过的坑:
- 版本号不要用时间戳:时间戳无法表达兼容性信息,你没法判断
20240501和20240502之间有没有破坏性变更。 - 不要依赖文件名:有人把版本号写在Rootfs镜像文件名里,但解压后文件名就丢了。版本号必须写在文件系统内部。
- 版本号要可读:别用
v2.1.3-alpha.1+build.20240501这种花哨格式,简单2.1.3加构建信息就够了。
警告:千万不要在升级过程中修改 /etc/version 的权限或属主。我曾经见过一个案例,升级脚本把 /etc/version 改成了 root:root,结果后续的OTA客户端因为权限不足读不到版本号,直接罢工了。
版本号管理,说白了就是给Rootfs一个清晰的身份标识。你想想看,如果没有版本号,你怎么知道这个设备能不能升级?怎么知道升级后能不能回滚?怎么知道出问题时该找哪个版本的代码?
所以,从今天开始,给你的Rootfs一个规范的版本号,放在 /etc/version 里,让一切都变得可追溯、可管理。