• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

Microduck 的双层配置:为什么改过参数的鸭子升级后不会丢设置

Build in Public admin 11小时前 16次浏览 已收录 扫描二维码

Microduck 的双层配置:为什么改过参数的鸭子升级后不会丢设置

搞嵌入式的人大概都遇到过这个场景:你在某台设备上调了一堆参数,固件升级之后参数全没了。要么是因为更新把配置文件覆盖了,要么是因为新版本改了配置格式,旧值直接不认了。消费者产品还好,大不了重新配一遍。但要是你在一台机器人上花了两周调步态参数,升级后全丢,那就想砸东西了。

Microduck 的方案挺有意思。它用了一个双层 TOML 配置系统,把”出厂默认”和”用户修改”分到两个文件里,靠合并逻辑保证升级不丢设置。今天拆开看这套设计是怎么工作的。

两个文件,各管各的

Microduck 的主配置叫 robotd.toml,但它不是一个文件,是两层。

第一层在 /etc/robot/robotd.toml,这是基线层。系统镜像或者安装脚本写进去的,里面是板级配置:总线端口、舵机布局、相机参数这些出厂就该有的东西。安装脚本只写一次,之后不会覆盖它。OS 镜像更新的时候,这个文件会跟着镜像一起换新的。

第二层在 /var/lib/robot/robotd.toml,这是用户层。所有你在机器人上改过的参数——用 robotctl configure 改的、手柄配置改的、策略切换改的——都写到这个文件里。每个写入者只往这里写,绝不碰基线层。

加载的时候,robotd_params::Params::load 把两个文件合并:表按 key 逐个合并,其他类型(包括列表)整体替换。也就是说,如果你在用户层改了 [pad_drive] max_speed 的值,基线层里同一个 key 的值会被覆盖,但 [pad_drive] 下其他你没改的 key 仍然来自基线层。

这个设计的关键在于:升级换掉基线层,用户层不动。新版本可能加了新参数、改了默认值,但你改过的那些 key 还在用户层里,合并之后仍然是你的值。

为什么用户层坏了不会把机器人搞挂

配置文件损坏是个真实场景。断电写一半、手动编辑拼错、版本不兼容——都可能产生一个无法解析的 TOML 文件。

Microduck 对此的处理策略是:用户层解析失败时,把它搁到一边,用基线层跑,然后报告 degraded 状态,同时点名是哪个文件出了问题。注意是 degraded 不是 unhealthy。

这两个状态的区别很关键。updaterd 的健康检查把 unhealthy 视为”需要回滚”——如果一个更新之后 robotd 报 unhealthy,updaterd 会把整个 release 退回旧版本。但如果是用户层配置坏了,那跟 release 没关系,退回旧版本也修不了。所以 degraded 状态不会触发回滚,机器人继续用基线配置运行,你有机会修那个文件。

不过基线层坏了就是另一回事了。基线层是镜像的一部分,基线坏了意味着镜像坏了,这是致命的。好在安装脚本和编辑器在写入用户层之前会先跟基线层做校验,只有手动编辑文件才可能产生不兼容的用户层。

A/B 分区下的配置迁移

Microduck 的 OS 更新走的是 A/B 双分区方案(microduck_yocto)。每个分区有完整的根文件系统,更新时切换到另一个分区。这意味着 /etc 目录会跟着新镜像一起被替换——基线层 robotd.toml 也包括在内。

但 /var/lib/robot 是独立分区,两个分区共享。所以用户层在 OS 更新后仍然存在。

这里有个迁移问题。一台从老版本升级上来的机器人,它的配置可能全在基线层里(那时候还没有分层机制),跟镜像模板混在一起。第一次启动带分层支持的新版本时,robotd 发现没有用户层文件,就会执行一个叫 adopt_base_edits 的逻辑:把基线层里那些”值跟任何 release 模板都不一样”的 key 移动到用户层。

是移动不是复制。如果留在基线层里,编辑器会比较基线值来判断”这个 key 有没有被用户改过”,一份留在基线的副本会让编辑器以为没改过,”恢复默认”也就变成了空操作。移走之后,基线层的值回到模板默认,用户层的值是实际生效的。

对于 A/B 分区的机器人,如果 OS 更新先到(新分区没有用户层),bundle 的 install hook 会把当前运行分区的基线层复制到 /var/lib/robot/robotd.toml.from-slot,新分区启动后从那个文件里采纳编辑。

updater.toml 为什么还是单层

不是所有配置都适合双层。updater.toml 目前还是单层结构,大部分内容是镜像策略——更新频率、签名要求这些。只有一个 key 是 per-robot 的决定:auto_apply = "all",用在金丝雀机器人上自动应用所有更新。

在 A/B 分区的机器人上,OS 更新目前会把 updater.toml 重置回镜像的值。这是一个已知的不完美之处,文档里标注为 open。曾经有个 is_dev_board 的 key 也是 per-robot 的,后来改成了一个独立标记文件放在 /var/lib/robot 下,绕开了这个问题。

除了配置,还有什么能活过更新

配置文件之外,Microduck 还有几类持久化数据,它们各自有不同的存活策略。

/var/lib/robot/vetbook.json 是机器人的”病历本”——lifetime record,由 robotd 拥有。记录这台机器人发生过什么,修过什么,换过什么零件。更新和回滚都不影响它。

/var/lib/robot/config/config.json 存机器人名字和配对 PIN,由 configd 拥有,用 flock 做并发控制。WiFi 凭证存在 NetworkManager 的 profile 里,Microduck 的代码从不接触它们。

/var/lib/robot/hf-token 是 Hugging Face 的登录令牌,updaterd 写入,mediad 读取——用于从 Hugging Face 拉取新策略或者推送自己训练的。

/opt/robot/daemon/releases/<version>/ 是 release 目录,二进制文件、策略和默认配置都在里面。更新就是放一个新版本目录进去,然后把 current 符号链接指过去。回滚就是把符号链接指回旧版本。原子操作,不会出现半更新状态。

总结起来就一条规则:releases/ 目录之外的所有东西都能活过更新和回滚。这是整个设计的出发点——owners 的决定和 image 的决定分开存放,各走各的更新通道。

这套设计给我们的启示

双层配置不是什么新技术,很多 Linux 发行版用了多年(systemd 的 /etc/systemd/*.conf 和 /etc/systemd/*.d/*.conf 覆盖机制就是类似思路)。但 Microduck 把它用在一个 800 克的小型机器人上,处理的是更具体的问题:sim2real 参数调优的持久化、OS 更新和 daemon 更新解耦、配置损坏的优雅降级。

三个值得借鉴的点。

第一,合并策略要明确。TOML 的表可以按 key 合并,但列表是整体替换。这意味着如果你有一个 [joints] offsets = [0.1, 0.2, ...] 这样的列表参数,用户层要么整个列表覆盖,要么完全不覆盖,不能只改其中一个元素。Microduck 的做法是避免在配置里用长列表,或者把需要单独调整的值拆成独立的 key。

第二,损坏检测要有分级。degraded 和 unhealthy 是两个不同的状态,前者不触发回滚后者触发。如果你的系统只有一个”坏了”状态,配置文件损坏就会导致不必要的回滚,而回滚修不了配置问题。

第三,迁移要向前兼容。adopt_base_edits 这个逻辑保证了老机器人升级到新版本时不会丢配置。关键是它判断”哪些 key 被用户改过”的方法——跟所有 release 模板都不一样的值就算用户改的。这不完美(万一用户改的值恰好等于某个模板的默认值),但在实际使用中够用,而且比手动迁移可靠得多。

配置管理在机器人项目里是个容易被忽视的基础设施问题。它不性感,但等你调了两周参数然后升级丢了的时候,你就知道它有多重要了。

喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。