同一个 Go 客户端,怎样同时运行在 Linux 和 OpenWrt 上?Fleetd 的部署取舍
同一个 Go 客户端怎样运行在 Linux 和 OpenWrt 上?结合 Fleetd v0.3.0 讲清 amd64/arm64、systemd/procd、状态持久化、固件升级恢复与监控能力差异。
把客户端放进一台云服务器,和放进家里的软路由,看起来都是复制一个程序、写一份配置,再让它运行起来。
真正要长期使用时,问题才会逐渐显出来:机器重启以后谁来启动程序?网络还没准备好怎么办?日志去哪里找?保存过的设备身份和命令记录,会不会跟着一次重启消失?
Fleetd 要同时管理 Linux 服务器和 OpenWrt、iStoreOS 软路由。这一篇就聊它怎样处理这些差异。前面已经讲过设备接入和浏览器命令签名,这里把视角移到机器本身:怎样让客户端成为一个能持续运行、可以排查问题的系统服务。
以下按 Fleetd v0.3.0 的实现来讲。当前安装入口支持 amd64、arm64,以及 systemd 或 OpenWrt 的 procd,不代表所有使用 Linux 内核的设备都能直接安装。
共用一套源码,按架构生成程序
OpenWrt 本身也是 Linux。对 Fleetd 来说,服务器和路由器之间,有不少可以直接复用的部分:网络连接、身份验证、命令授权,以及从内核接口读取基本指标。
因此,客户端没有分成“服务器版”和“路由器版”两套代码。构建时指定 Linux 目标,再分别生成 amd64 和 arm64 的程序。
这里的“同一个客户端”指同一套源码和协议。amd64 的可执行文件不能直接拿到 arm64 上运行,安装时仍要选择匹配的产物。
| 机器报告的架构 | 安装器选择的产物 |
|---|---|
x86_64、amd64 |
amd64 |
aarch64、arm64 |
arm64 |
| 其他值 | 当前安装器拒绝继续 |
这也是介绍 OpenWrt 支持时需要说清楚的地方。OpenWrt 覆盖的硬件很多,包括 MIPS 和 32 位 ARM;Fleetd 当前这条安装路径没有覆盖它们。
Go 支持交叉编译,并不自动等于项目支持所有目标架构。要增加一种架构,还要检查依赖、构建产物、安装识别、更新流程和真实设备上的行为。不能只把构建参数改一下,就宣布兼容。
安装前可以用 uname -m 查看机器报告的架构,但这也只是第一步。内核、用户空间和可用的系统功能仍然会影响运行。
单文件部署,减少的是运行环境准备
Fleetd 的客户端构建设置了 CGO_ENABLED=0。这样可以减少对目标机器上 C 动态链接环境的依赖,不必给每台设备安装 Go 编译器,也不用先准备 Node.js 或 Python 运行环境来启动客户端。
对软路由来说,这一点很实用。它的存储、软件包和系统组件往往比普通服务器精简。多安装一套运行环境,意味着更多下载、依赖和维护工作。
但单个可执行文件不等于没有运行条件。网络要可达,HTTPS 需要有效的证书信任环境,配置目录要能写,内核也要提供程序依赖的功能。
有些采集项还会调用系统工具。例如容器信息依赖 docker,WireGuard 信息依赖 wg。程序本体不需要安装一套语言运行时,和所有功能都不需要外部工具,是两回事。
因此,我更愿意把这个选择理解为:先减少部署的基础负担,再明确哪些能力有额外条件。
安装器先判断环境,再接入系统服务
当前安装器大致按这个顺序工作:
- 检查是否以 root 执行,识别 CPU 架构。
- 判断机器使用 systemd,还是具备 OpenWrt 的 procd 环境。
- 从自己的 Fleetd 服务端下载对应架构的客户端。
- 运行一次
version,确认下载的程序至少能在这台机器上启动。 - 创建或保留配置,安装程序,注册并启动服务。
- 输出设备指纹,等待管理后台核对并批准。
其中第四步很有必要。下载成功只说明拿到了文件,version 能运行,才进一步说明这个可执行文件可以在当前环境里启动。
不过,这仍然不是完整验收。它没有验证长期连接、采集、权限、开机自启或固件升级后的恢复。短暂运行一次版本命令,不能替代这些检查。
下载工具方面,安装器优先用 curl,没有时使用 wget。这对精简环境比较方便,但仍要确认系统实际提供的工具支持相应 HTTPS 请求。这里不统一给出某一条包管理器安装命令,因为 OpenWrt 版本和固件集成的软件包可能不同。
还有一个容易误会的信任关系:客户端从自己的 Fleetd 服务端下载。服务端从已上传并通过签名验证的发布包中取出程序;初次安装脚本本身依赖你信任的服务端和 HTTPS。它没有在目标机器上独立验签下载文件。后续由客户端验证更新包,是另一条流程,留到更新篇再展开。
systemd 和 procd,接管的是同一个进程
程序安装好以后,Fleetd 把进程交给系统已有的服务管理器。两边的主要差异如下:
| 项目 | systemd 机器 | OpenWrt / procd 机器 |
|---|---|---|
| 程序位置 | /usr/local/bin/fleetd-agent |
/usr/bin/fleetd-agent |
| 配置位置 | /etc/fleetd/agent.yaml |
/etc/fleetd/agent.yaml |
| 服务定义 | /etc/systemd/system/fleetd-agent.service |
/etc/init.d/fleetd-agent |
| 新装时的状态目录 | /var/lib/fleetd |
/etc/fleetd |
| 日志入口 | systemd journal | OpenWrt 系统日志 |
systemd 服务通过 ExecStart 指向客户端,设置 Restart=always 和五秒重启间隔,同时配置 network-online.target 的启动顺序。
OpenWrt 则生成使用 USE_PROCD=1 的启动文件,设置开机启动顺序、进程命令和 respawn 参数,把标准输出、标准错误交给系统日志。
两个分支最终运行的都是同一个客户端入口,读取同一类 YAML 配置。差异主要留在安装和服务接入层,不需要在连接协议里到处判断当前是哪种发行版。
这里还要分清两种恢复:进程退出,由服务管理器处理;连接中断,由客户端自己的重连逻辑处理。
如果服务端暂时不可达,客户端进程仍然可以活着,并按退避策略继续尝试。没有必要每次网络断开都重启整个服务。反过来,进程已经退出时,内部重连代码也无从运行,就需要 systemd 或 procd 接手。
开机顺序同样不能保证远端一定可用。systemd 的网络目标和 OpenWrt 较晚的启动顺序,都无法确保此时 DNS、上游链路和服务端已经准备完成。客户端自己能处理连接失败,才不会把“启动得够晚”当成唯一保障。
在 OpenWrt 上,状态存在哪里比文件名更重要
这一处差异会直接影响上一章讲过的防重放。
普通服务器上,/var/lib/fleetd 通常适合放客户端运行状态。但在常见 OpenWrt 布局中,/var 依赖内存中的临时存储。程序重启和整台设备重启,对这些文件的影响并不一样。
Fleetd 的安装器因此在 OpenWrt 分支把 state_dir 指向 /etc/fleetd。这里需要区分三类文件:
| 文件 | 作用 |
|---|---|
agent.yaml |
连接地址、受信公钥、权限和状态目录等配置 |
agent.key |
客户端自身的身份密钥,决定设备身份 |
seen.json |
已接受请求的 ID 记录,用于防重放 |
前两项默认就在 /etc/fleetd。seen.json 则跟随 state_dir:systemd 新装时放在 /var/lib/fleetd,OpenWrt 新装时放在 /etc/fleetd。
身份密钥丢失后重新生成,后台看到的就会是另一种设备身份。防重放记录丢失,也不能再声称原来那些尚在有效期内的请求一定会被识别为重复。
所以,“文件写到磁盘了”还不够,要进一步问:这个路径在当前系统上到底对应持久存储,还是重启即清空的内存盘?如果使用自定义固件、只读布局或特殊挂载,也应按实际挂载情况确认。
把状态放到闪存还有写入成本。当前实现接受新的授权请求时会更新防重放记录,并不是每十秒上报一次指标就把指标写进这个文件。两种写入不能混为一谈。若以后发展成高频远程任务执行工具,就要重新评估持久化频率和设备闪存条件;目前不能凭空给出寿命或吞吐保证。
保留配置,不等于固件升级后服务自动恢复
OpenWrt 安装分支还会把 /etc/fleetd/ 加入 /etc/sysupgrade.conf,让这部分文件进入配置保留清单。
这有助于在支持并启用保留配置的升级流程中保住身份和配置。不过,清单里保留的是这个目录,并没有同时保证 /usr/bin/fleetd-agent 和 /etc/init.d/fleetd-agent 一定随固件升级留下。
因此,升级固件以后可能出现一种状态:身份和配置还在,但程序或启动文件需要重新安装。恢复出厂、明确不保留配置的刷机方式,也不在这个保留机制的保证范围里。
这里不能把“写进 sysupgrade 清单”写成“升级以后肯定自动上线”。真正需要确认的是:
- 保留的配置和身份是否还在。
- 程序和启动服务是否存在,是否仍能运行。
- 客户端恢复后,设备指纹是否与升级前一致。
- 如果上报暂时没有回来,是服务未启动、网络未通,还是需要重新批准。
这些是升级前后应该做的检查,不是本文已经完成的一轮刷机测试。
再次运行当前安装器,会保留已经存在的 agent.yaml,更新程序并重新写入服务定义。这个行为适合恢复安装,但也有另一面:你重新传入 --permission、--server 或 --signer,并不会自动覆盖已有配置。
同理,旧配置如果把 OpenWrt 的状态目录写错了,重跑安装器也不会顺便替你纠正。需要检查配置本身,再重启客户端使修改生效。
能运行同一个客户端,不代表监控能力完全相同
Fleetd 的基础指标主要来自 Linux 的 /proc:CPU、内存、负载、运行时间和网卡累计流量等。这部分接口相对统一,所以可以在服务器和软路由之间复用。
网络速率和 CPU 使用率需要前后两次采样。刚启动时只有第一份计数,不能把这时候的速率值当成一段完整采样区间的结果。网卡重建导致计数器回退时,当前实现也会跳过那次速率计算,避免直接算出不合理的差值。
扩展采集则有明显的环境差异:
| 采集项 | 当前依赖 |
|---|---|
| 失败服务 | systemctl list-units --state=failed |
| 容器状态 | docker ps -a |
| WireGuard 对端 | wg show all dump |
客户端能由 procd 托管,不意味着已经实现了 procd 服务健康状态采集。当前失败服务的采集路径仍然使用 systemd 命令。
当外部工具不存在或调用失败时,相应采集会返回无数据,基础监控仍可继续。这个行为让精简环境不必为缺少 Docker 等组件而完全退出,但也留下一个必须正确理解的结果:没有采集到异常,不等于已经证明所有服务健康。
当前实现也没有为每一种缺失原因都提供完整的能力状态说明。如果页面没有相关数据,排查时要区分功能不适用、工具不存在、调用失败和确实没有对象,而不能一律理解成零异常。
基础指标按十秒节奏采集上报,磁盘、服务、容器和 WireGuard 这组较慢的信息每六轮采集一次。这是调度设置,不是严格的实时承诺。外部命令耗时、网络异常和机器负载都可能影响实际间隔。
装好以后,按运行链路验收
对一个常驻客户端,我更关心下面这条链路是否完整:程序能启动,服务管理器能接管,网络恢复后能连上,设备重启以后身份和配置仍然正确。
在 systemd 机器上,先看服务状态和近期日志:
systemctl status fleetd-agent
journalctl -u fleetd-agent -n 50 --no-pager
OpenWrt 上可以从系统日志查起:
logread -e fleetd
这几条是诊断命令,不会替你重启设备。需要重启客户端使配置生效时,systemd 使用 systemctl restart fleetd-agent,OpenWrt 使用 /etc/init.d/fleetd-agent restart。
随后在后台确认设备已批准、数据时间在更新,观察 CPU 和网卡是否经过了后续采样。如果设备只需要监控,保留 monitor 即可,不必为了验收先开放 root 命令权限。
整机重启和固件升级的验证应安排在维护窗口,尤其是承担上网或远程接入的路由器。一次受控重启后确认客户端自动上线、指纹不变,比连续启动几次 version 更接近长期运行需要的证据。
项目的 CI 包含 amd64、arm64 构建和测试,但交叉编译通过不等于每种 OpenWrt 固件都通过了实机验收。目前也没有在本文给出内存占用、CPU 开销或最低闪存容量的实测结论。选择设备时,仍应检查剩余空间,并观察自己的使用负载。
Fleetd 这部分实现的取舍,是把协议和客户端逻辑共用,把服务管理与目录布局的差异留在明确的适配位置。这样既能复用代码,也不会把路由器当成一台目录结构完全相同的小服务器。
下一篇继续聊监控之后的事:采集到了指标,怎样让告警真正值得看,而不是每天多收到几封通知邮件。
项目与相关资料: