启动机器、登录,然后发现隧道起来了,几秒之后就死了。配置没问题,证书没问题,唯一留下的线索就是那次启动的日志。通常发生的是竞态:单元在机器拿到地址之前就启动了,而它声明的顺序并不像看起来那样生效。下面全部内容都围绕如何用证据证明这场竞态,并把它妥善关掉。

目标是汇合点,不是管理者

network-online.target 完全不知道网络是什么。它只是一个协调点,负责把那个本该延迟启动、直到链路可用的服务收集进来,并把定义委托给拥有这台机器的网络管理器。在大多数桌面和服务器上,这意味着由 NetworkManager 或 systemd-networkd 提供的等待服务。两个都不启用,目标就没有什么可等,于是某个东西把它拉进来的那一刻它就完成了,而这可能远早于 DHCP 作答。你单元里的顺序行是诚实的,却依然没用。

所以第一个问题不是你的单元声明了什么。而是这台机器上是否真有东西在做等待。

读最后一次启动,而不是读单元文件

单元文件描述意图,日志描述实际发生了什么。只查那一次启动,用单调时间戳,让每条消息落在同一只表上。然后找两行:隧道单元开始启动的时刻,以及地址出现或等待服务结束的时刻。把这两个数字排个序,竞态要么被确认,要么被排除。

依赖视图可以印证这一点。如果隧道单元的关键链里没有等待服务,这条路径上就没有任何东西延迟启动,目标也没做你以为它做的事。两个视图,一个结论。

systemctl list-units --type=service --state=running | grep wait-online
journalctl -b -o short-monotonic -u openvpn-client@office

补上等待,再补上重试

用 drop-in,而不是直接改打包的单元,这样软件包更新不会悄悄把改动丢掉。有两条指令要紧:单元必须排在目标之后,并且必须把目标拉进同一事务。光有顺序并不会召唤目标,想要它才会。

OpenVPN 客户端的打包单元已经兼具两者。手写单元通常只带一个,或者一个都不带,这点值得在动手添加之前先查。

[Service]
Restart=on-failure
RestartSec=15

为什么固定的 sleep 是陷阱

在 ExecStartPre 里加一个 sleep,感觉像在修顺序,实际却像多绕几步的延迟。它拖慢每一次启动,包括本来健康的那些;链路耗时超过你选的数字时,它照样输。

更糟的是,它掩盖了等待服务到底有没有工作,于是下一次失败会以全新的问题出现,而不是同一问题的另一种时序。短的重启延迟才是更好的工具:健康启动时零成本,还能把输掉的竞态变成再一次尝试。把间隔保持克制,让单元成功。如果它永远重启,那是配置坏了,再多重试也改不了,所以该去读日志,而不是调高间隔。

顺序对了却仍然解析失败时

单元处于活动状态且有隧道路由,说明顺序起了作用,剩下的故障在更上面一层。常见形态是隧道 up 脚本在链路或解析器还没准备好承受时就应用了解析器设置。把故障期间的解析器状态和重启隧道单元之后的状态做个对比。

如果重启修好了查询,那你有的是启动顺序缺陷,而不是 DNS 服务器错了,这两者需要不同的修法。然后再用冷启动而不是重启服务来证明整件事,因为重启是对着已经起来的网络跑的,恰好会掩盖你在追的那个缺陷。把链路故意拖慢再重复一遍,并且把只在网络快时才工作的单元依然当作坏的。

相关文章