开机时 RRAS 在 OpenVPN 隧道就绪之前就启动了
服务依赖只决定启动顺序,并不会让隧道可用。请说出 RRAS 真正需要的资源,再把重启挂到该资源上,而不是挂到等待上。
服务器重启,OpenVPN 启动,而 RRAS 在它依赖的网卡或路由尚未出现时就已起来。一分钟后手动重启 RRAS 就能恢复正常,这让问题看起来已经解决,反而掩盖了真正的原因。这里混用了两个不同条件:前置服务正在运行,以及 RRAS 真正需要的网络资源已经可用。Windows 服务依赖只能表达前者,永远表达不了后者,所以只加依赖而不定义资源,只会得到一个看起来更整齐的开机过程和一个同样的故障。
冷启动时故障是什么样子
线索是冷启动与重启之间的差别。冷启动时,隧道网卡还不存在、路由缺失、对端地址尚未解析,RRAS 却报告自己在运行。手动重启随后成功,是因为此时资源已经存在,服务只是重新读取了一分钟前它还看不到的世界。两种情况下服务日志都会显示一次干净的启动,所以只看事件查看器往往说明不了问题。
改动之前先把两种状态都记录下来:开机时 RRAS 报告什么,以及手动重启成功的瞬间存在什么。如果这两个观察完全相同,那么启动顺序就不是你的解释,加依赖也不会解决问题。这项比较只要十分钟,却能省下一周的猜测。
基于“重启之后就好了”而添加的依赖,只是把同一个故障推迟了前置服务报告 RUNNING 所需的那点时间。它还会让开机过程更难推理,因为顺序约束现在看起来像是一种解释。要问清楚依赖到底保证哪个对象,以及那个对象是不是 RRAS 看不到的那个。
说出资源,而不只是服务
把缺失的具体东西写下来。它可能是一块带地址的隧道网卡、一条通往对端子网的路由、一个 DNS 应答,或者一张还在签发的证书。“VPN 服务正在运行”不是资源描述,客户端里的绿色图标也不是。在你能说出这个对象以及显示它的命令之前,你还没有定义自己要等的条件。
还要确定这个 OpenVPN 实例是怎么启动的。由 GUI 管理的隧道和由服务管理的隧道有不同开机生命周期,服务名必须来自你面前的机器,而不是来自为另一个安装程序写的示例。把候选者列出来,不要只信一个记得住的名字。
Get-Service | Where-Object Name -match 'vpn|openvpn' | Select-Object Name, Status, StartType
添加之前先读依赖链
服务控制管理器报告 START_PENDING、RUNNING 这类状态,SERVICE_STATUS reference 精确定义了每个字段的含义。这些状态没有一个承诺对端、DNS 名称或下游应用已经可达。正是这个缺口,让该故障在依赖配置正确时依然存在。
Get-Service -Name RemoteAccess | Select-Object Status, StartType sc.exe qc RemoteAccess Get-Service -Name RemoteAccess | Select-Object -ExpandProperty DependentServices
sc.exe qc 输出中的 DEPENDENCIES 那一行是必须保留的列表,因为 sc.exe config RemoteAccess depend= 会替换整个列表,而不是追加。添加 VPN 服务时丢掉一个无关的前置条件,会制造出在另一次开机才出现的第二个故障,而且比原来的更难诊断。动手之前先把现有列表抄进变更记录。
依赖也会改变关机行为,因为停止一个前置条件会停掉依赖它的服务。在把 RRAS 挂到经常重启的东西上之前,先确认维护期间会发生什么。
把重启挂在条件上,而不是定时器上
固定等待假设隧道总会在那个窗口内就绪,而这恰恰是在证书很慢或链路断开的开机中失败的假设。检查资源本身,并设定截止时间,让检查来决定到底需不需要重启。
Get-NetAdapter | Where-Object Status -eq 'Up' Get-NetRoute -DestinationPrefix 0.0.0.0/0 Get-WinEvent -LogName System -MaxEvents 40 | Where-Object ProviderName -eq 'Service Control Manager'
开机时运行的计划任务执行这些检查,只在网卡或路由仍然缺失时重启 RRAS,这样的做法有边界、可审计、也容易移除。另一种做法是每次重连都重启 RRAS,它会打断健康的会话,把不稳定的链路变成重启循环。那个循环比原来的故障更糟,因为它现在会影响到本来一切正常的用户。
给检查一个与你的硬件上隧道合理耗时相符的最长等待,并让任务在放弃时以非零状态退出。一个只是停止尝试的安静任务,和一个成功的任务无法区分。
记录这次转换,让下一次开机能自我解释
在同一行记录三个时间戳:依赖服务到达 RUNNING 的时刻、所需资源出现的时刻,以及 RRAS 最终接受它的时刻。这一行就回答了下一个工程师会问的问题,而生成它的成本几乎为零。没有它,下一次排查就会从和这次一样的猜测开始。
在既定尝试次数之后停止自动化,并报告失败,而不是永远重试。一次以“资源从未出现”这样清晰信息结束的开机,比在黑暗中不断重启服务更有价值。当那条信息出现时,你在第二节写下的资源名,就是下一个人会去搜索的东西。