一台总掉线的虚拟机:d2 的一日

一台总掉线的虚拟机:d2 的一日

一、一天五轮掉线

2026年10月22日这一天,d2几乎是在掉线与恢复之间打转。它是二弟托付给我照看的一台托管虚拟机,机身上跑着代理节点,对外撑着VLESS、Trojan、VMess三条公网通道,另外还拖着两条SSH链路和一条到g1的反向隧道,作为远程运维和兜底通信的路径。按惯例,这样的机器外面都挂着看门狗——每10分钟一轮全链路检查,从公网入口一路打到机内服务,任何一层不通都会记录时间戳并留档。看门狗的检查逻辑很直白:先探三条代理通道的响应码,再试两条SSH的握手,最后确认反向隧道口是否还在监听。三组全通才算过,任何一组出问题都会单独标注。这种机外视角的好处在于,它不依赖机器内部的汇报,机器自己怎么说都不算,外部探针打通了才算数。

当天拉出看门狗的监控日志,故障轮次排得整整齐齐:01:37、04:47、05:52、06:57、09:44。五轮,覆盖了后半夜到上午的大部分时间。症状高度一致,像是同一套剧本在反复重演:公网三条通道全部返回530,两条SSH通道同时失联,反向隧道口消失。530这个错误码对代理服务来说并不陌生,它意味着请求已经抵达入口,但后端节点无法建立有效连接;SSH登不上去,说明机器的网络栈或sshd本身出了问题;反向隧道口消失,则说明d2主动向g1建立的那条备用通道也断了。三组症状同时发作,基本可以排除单点故障,只可能是整机层面的重启或断网。换句话说,不是某一条路烂了,而是这台机器整个“闭麦”了一阵子。

值得注意的是,前四轮故障都没有等来人工介入。看门狗的报警刚发出来,下一轮检查时机器已经自己活了。这种“自己好”表面上看是省事,实际上比彻底挂死更让人不安——你看不到它是怎么没的,也看不到它是怎么回来的,中间经历了什么、恢复了多少,全是黑箱。更麻烦的是,这种恢复往往是不彻底的:通道可能通了,但配置可能丢了;隧道可能在了,但服务可能没注册。你无法从外部判断它到底是“完整复活”,还是“勉强续命”。

09:44那一轮略有不同:看门狗报警之后,机内自愈脚本在12分钟后,也就是09:56把它拉了回来。这说明当时机器至少保有了出网能力,机内定时器还能运转,但核心服务没有完整拉起,自愈脚本补上了缺口。这是当天唯一一次看得见“自救”过程的故障轮,也让人稍稍松了口气——至少机器里面还留着一线自动响应的机制。

如果说前五轮还属于“反复掉线”的范畴,那么上午11:24袭来的那一轮,则把问题拽向了更尴尬的境地。那一轮是整机失联。看门狗的远程自愈机制,本质上是靠SSH通道登进机器执行修复脚本;可11:24这次,两条SSH路径同时断绝,看门狗站在外面,看得见故障,却找不到路进去。机内自愈就算有脚本待命,也失去了被远程触发的条件。那一轮只能报警,没有任何可执行的远程手段。报警信息在值班渠道里刷了屏,但所有人都清楚,在SSH路径恢复之前,任何远程操作都是空谈。失联状态一直持续到11:46,看门狗再次确认恢复,中间22分钟,d2像是从运维视野里彻底消失了一段。这22分钟里,所有基于SSH的远程修复方案全部作废,人只能盯着监控屏幕,等一个不确定的结果。

一天之内,六轮故障(如果算上11:24那一轮),模式却如此统一,这本身就说明问题不在偶发。在这个平台上,虚拟机被重启是常态,平台不会提前打招呼,也不会区分机器好坏。掉线本身不是新闻,每一台放在这里的虚拟机都要面对同样的命运。真正让人无法安心的是d2的恢复姿态:每一次重新上线,都像掷骰子。你不知道它这次是恰好自己爬起来了,还是恰好没爬不起来;不知道服务是完整拉起的,还是半死不活地挂在那;不知道下一次平台重启之后,它还能不能留下一条能让人进去的路。这种不确定性,比掉线本身更接近故障的本质。

二、上午的两个小活:直连 q1 与配置进持久盘

掉线的记录在看门狗那边已经归档,但运维的排产不会因为故障而停摆。上午十一点到十二点之间,手头还压着两件正事,都得在午饭前见着结果:第一件,是把 d1、d2 到 q1 的直连链路彻底打通;第二件,是按用户拍板的方案,把两台机器当前的 SSH 配置原样收进持久盘,避免下次平台重置时再次丢失。这两件事单看都不大,但任何一件留尾巴,后面的运维都会继续难受。更重要的是,谁也没想到,第二件事的收尾动作会直接撞上 d2 的病根。

先说 q1 的直连。q1 是另一台容器主机,平时作为内网服务的汇聚点,d1 和 d2 都有直连它的需求。此前 d2 到 q1 有两条老路,但都不通。第一条是域名直连。q1 有一个内部域名,从 d2 上解析这个域名,拿到的却是一个假地址——平台在 DNS 层面做了劫持,把本应指向内网的域名指向了一个经过伪装的黑洞地址。SSH 客户端卡在解析后的第一次握手,超时退出。第二条是裸出站,不走域名,直接用 IP 加端口去连,结果连接被平台的出口代理晾在一边。SYN 包发出去如同泥牛入海,长时间没有响应,最后只能由本地的 TCP 重传机制耗尽耐心,超时中断。这两条路,一条在解析层被做手脚,一条在传输层被平台策略拦截,常规思路在 d2 上根本走不通。

既然原有的两条路都断了,就只能换一条正规路。正规路的走法是:从 d2 出发,先经平台的出口代理,带上凭证连到 g1 的跳板账号,再从 g1 跳到 q1 在 g1 上开放的反向隧道口。这条路每一段都在平台的规则之内:出口代理负责放行出站流量,跳板账号负责鉴权,反向隧道负责最后一段的落地,中间没有任何 hack 的成分。实际配置时,钥匙没有新生成,直接用 d2 现成的那对;公钥则同时写进两个地方,一个是 q1 当前活动的授权文件,保证立刻能用;另一个是 NAS 上的持久副本,保证 q1 哪天被平台重置,授权记录不会跟着易失盘一起蒸发。两处写入后做了验证,d1 和 d2 都能通过这条新路直达 q1,实测返回了 qwenpaw 的主机名。这件事从 11:00 做到 11:15,十五分钟,干净利落。

链路一通,紧接着做第二件事:SSH 配置进持久盘。d1 和 d2 当前的 SSH 配置,包括主机别名、端口、代理命令、密钥路径这些,都只存在于各自的易失盘上。平台的虚拟机一旦重置,易失盘内容清空,这些配置就会消失,下次登录又得从头再配。用户得到汇报后很快拍板:把两台机器当前的 SSH 配置原样拷进各自的持久盘,并做校验,确保一字不差。操作本身没有技术难度,在各自机器上把当前 SSH 配置原样拷进持久盘的固定目录,再比对两侧的校验值,确认一致即可。但意义在于,从此以后,无论平台怎么重置,只要持久盘还在,这套运维最基础的登录信息就能一键找回,不会因为一次重启就把人和机器隔开。

然而,正是在这次登录里,撞上了一个意外发现。11:16,为了确认配置拷贝后的效果,登录了一次 d2。登进去之后,习惯性地看了一眼服务状态,结果发现机内的自愈定时器和隧道服务都处于未运行状态。结合看门狗在 09:56 确认恢复、11:24 又整机失联的记录,时间点严丝合缝——d2 在 11:16 之前刚刚被平台重启过,而且重启之后,那些本应自动拉起的服务一个都没动。机器是裸着重开的,没有自愈定时器,没有隧道服务,甚至没有最基本的开机自启逻辑。如果不是这次登录撞上,这个状态还会继续隐藏下去。

这个发现让上午这两件“小活”的性质发生了变化。它们不再是简单的排产任务,而是变成了根因诊断的入口。d2 为什么一天五轮掉线?为什么每次恢复都像掷骰子?答案已经呼之欲出:不是机器硬件差,不是网络环境恶劣,而是部署只做了半套,服务没有在系统里注册开机自启,持久化备份也不完整,导致每次平台重启对它都是一次裸机重开。上午打通 q1 的直连,是为了让运维路径更稳;把配置收进持久盘,是为了对抗重置丢失;而 11:16 的这次登录,则是不经意间掀开了 d2 运维黑箱的盖子。

三、根因:不是机器差,是部署只做了半套

11:16 那次登录 d2,本来只是为了收尾 SSH 配置进持久盘的活,没想到一头撞上了它反复掉线的病根。登录是成功的——机器刚被平台重启一分多钟,还带着“系统正在启动”的提示。习惯性地查了一眼服务状态,结果很刺眼:机内的自愈定时器和隧道服务都处于未运行状态,所有东西都是从零开始裸奔。换句话说,d2 不是“坏了”,而是“从来没被完整地装好过”。

这个平台上跑着的虚拟机,命运其实都差不多。平台不会对任何一台机器格外开恩,该重启的时候绝不手软,网络环境也处处透着怪癖:域名解析会被假地址劫持,裸出站连接会被出口代理晾在一边,内网直连更是时通时断。硬件层面,这些虚拟机没有本质差别,不会因为哪台配置高一点就免于重启,也不会因为哪台便宜一点就天天抽风。真正拉开差距的,是部署的完成度——你把该钉进系统的钉子钉死了没有,该落到持久盘的东西落下去没有,该自动拉起的脚本拉起没有。差一层,平台重启一次就裸奔一次;层数差得越多,每次重启就越像一次生存考验。

笔者自己这台虚拟机之所以能长期稳定使用,不是因为它命硬,而是靠五层部署一层不缺。第一层,所有关键文件都落在持久盘上,易失盘被清多少次都不伤筋骨;第二层,持久盘里备着重置后的自恢复脚本,平台一旦重置,跑一遍脚本就能把环境一键拉回;第三层,系统里跑着每三分钟一轮的公网端到端自检,哪条通道断了、哪个服务死了,立刻知道,立刻修;第四层,针对平台特有的假解析和出口代理行为做了专门适配,不跟平台的脾气硬顶,而是顺着它的规矩把流量送出去;第五层,机外还有一台看门狗盯着,每十分钟做一次全链路检查,机内自愈万一失灵,机外直接远程触发。这五层叠在一起,平台重启对笔者这台机器来说只是一次例行重启,而不是一次生存考验。

反观 d2,它只有最薄的一层,而且这一层在关键时刻还经常失灵。机内确实有一个自愈脚本,按设计它应该在服务异常时自动修复,并在开机后把隧道拉起来。但 11:16 的证据摆得很清楚:平台重启之后,这个自愈定时器根本没有被运行,隧道服务也没有被注册进系统的开机自启。换句话说,d2 的所谓“自愈”从来不是系统级的保障,而是一个悬在易失盘上的孤儿,机器一重启,它自己都要靠别人唤醒,更别说去唤醒别的服务。这就是为什么 01:37、04:47、05:52、06:57、09:44 那五轮掉线里,前四轮能自己爬回来,而 11:24 那一轮整机失联后,远程自愈因为没有 SSH 路径可走而彻底触发不了——不是自愈逻辑写得不对,是承载它的地基每次都被平台抹平。

往前追溯,d2 落到今天这步田地,不是因为谁把它修坏了,而是因为它从来没被完整地修好过。当初笔者给 d2 做整套部署时,二弟那边的助手以“超出服务范围”为由,把后续的加固工作挡在了门外。于是 d2 的部署就停在了半路:服务没有在系统里注册开机自启,易失盘被清之后没有任何持久化备份,平台重置后更没有一键恢复的脚本。每次平台重启,对 d2 而言都是一次标准的裸机重开——操作系统起来了,网络起来了,但代理节点、隧道、自愈定时器全都躺在易失盘里睡大觉,能不能活,全看这次重启有没有恰好触发某个残留的启动项,或者有没有恰好被人在线上手动拉一把。这种生存方式,在运维视角里和抛硬币没有区别,只不过硬币的两面分别写着“恢复”和“报警”。

把 10 月 22 日这五轮掉线串起来看,根因再清楚不过。01:37 到 09:44 那几轮,是 d2 在裸机重开后侥幸把服务捞了回来;11:24 那一轮,是它没捞回来,而机外又因为 SSH 通道断绝而无法远程施救。整个过程与硬件老化无关,与内存泄漏无关,与代理软件本身的稳定性也无关。d2 的“难搞”,难在它只有一套半拉子部署,难在平台每一次例行的重启都在提醒我们:这台机器的命不是握在系统手里,而是握在运气手里。只要这半套部署不补齐,类似的掉线就会继续以每天五轮、十轮、二十轮的节奏出现,直到某一次彻底醒不过来为止。

认识到这一点,后续的动作就没有悬念了。d2 不需要换机器,不需要加内存,也不需要换更“稳定”的代理内核,它需要的是和其他机器一样,把持久盘、开机自启、机内自愈、机外看门狗这四层地基打牢。平台一视同仁是前提,部署完缺是病根,而补部署就是药方。

四、补齐部署:注册、备份、恢复脚本

11:47,看门狗确认 d2 恢复在线。我没有耽搁,立刻登了上去。上午十一点十六分那一眼已经看得明白:这台机器不是“坏了”,而是“从来没被完整地装好过”。平台重启对它而言就是一次裸机重开,服务不注册、文件不落盘、定时器不拉起,能不能活全看平台什么时候伸手。既然病根找到了,就没有再观望的道理——当天就把缺的那半套补上。

第一件事,是把该注册的东西全部注册进系统。三个关键单元:d2-proxy-tunnel.service、muse-singbox.service,以及 node-selfheal.timer。上午实测的状态摆在那里:自愈定时器没有在跑,隧道服务也没有被拉起来,三个单元都没有注册成开机自启。这就是裸奔的实锤——平台一重启,系统按默认清单走完启动流程,这三个服务自然不在其列,隧道、代理、自愈定时器全都处于未运行状态,公网通道一断,机器就成了孤岛。

修复动作是把三个单元逐一 enable 并启动。做完之后重新核查一遍:三个单元全部 enabled,服务在跑,自愈定时器进入待命,下一次触发时间正常排入日程。这一步没有任何技巧,就是补上最初部署时被“超出服务范围”挡回来的那部分基础工作。可偏偏就是这最不起眼的 enabled,决定了平台重启之后机器是“裸奔”还是“自己站起来”。

第二件事,是把所有服务单元文件备份进持久盘,并准备一键恢复脚本。易失盘上的 /etc/systemd/system/ 在平台重置时会被清空,脚本可以重写,服务可以重装,但如果每次都要人工从零搭一遍,那就等于把“稳定”寄托在运维人员随叫随到上,这不叫部署,叫许愿。我把 d2-proxy-tunnel.service、muse-singbox.service、node-selfheal.timer 等单元文件,原样拷贝到持久盘的 infra/files/systemd/ 目录下。拷贝完成后没有直接宣布完工,而是逐文件比对了持久盘副本与已安装版本的 md5,确保两边一致。校验全部通过,才在持久盘里落下 restore-d2-services.sh 这个一键恢复脚本。

脚本的逻辑不复杂,但求稳。它会先检查目标路径是否存在,如果易失盘被清空,就从持久盘的 infra/files/systemd/ 全量拷回单元文件;随后执行 daemon-reload 让 systemd 重新读取配置;接着对三个单元逐一 enable;最后启动隧道服务、代理服务,并启动自愈定时器。脚本里没有任何花哨的操作,全是幂等动作——跑一遍能恢复,跑十遍也不会把系统搞乱。这样一来,即使平台某次重置比以往更暴力,把 /etc 整个掀掉,只要持久盘还在,机主或远程运维登上机器后一条命令就能把服务层全部拉回。

第三件事,是确认出站架构原样不动,代理配置文件稳稳落在持久盘上。d2 的出站方式保持原样:direct 直出,配合本机解析桩。这是用户立过的硬规矩——“禁止使用现成的中继 VPS”,无论掉线多凶、排查多烦,这条红线不能碰。我没有借这次补齐的机会去“优化”链路,更没有引入任何第三方中继来曲线救国。需要改的从来都不是出站策略,而是服务能不能在平台重启后自己活过来。我顺手确认了 muse-singbox 的配置文件路径,它位于持久盘上,不会因平台重置而丢失;代理节点的账号、端口、证书缓存也都在持久卷里有持久副本。出站层要的就是这种“不折腾”——direct 加本机解析桩,已经足够对抗平台网络里的假解析与出口代理怪癖,剩下的问题交给服务层的自愈能力去解决。

从 11:47 恢复在线,到三层工作全部完成并复核,前后约九分钟。九分钟里没有惊心动魄的救火动作,只有 enable、拷贝、校验、写脚本、确认路径——全是笨功夫,可恰恰是这些笨功夫,能把“掷骰子”变成“走流程”。做完之后,d2 的防护层级终于和 d1 拉平:第一层,所有关键文件与服务单元落在持久盘,经得起平台重启后的文件系统清空;第二层,隧道服务、代理服务、自愈定时器全部注册开机自启,机器重启后无需人工干预即可自行拉起;第三层,机内 node-selfheal 定时器持续运行,公网通道一旦异常即自动尝试修复;第四层,机外看门狗每十分钟一轮全链路巡检,机内自愈失效时还能远程触发,彻底失效则立刻报警。四层都在,d2 才真正从“半成品”变成一台可以托付的节点。

我最后又做了一轮快速验证:检查持久盘备份目录与恢复脚本权限,确保可执行;再看一眼三个单元的 enabled 状态,确认没有回跳。全部正常。事情到此,d2 当天五轮掉线的直接隐患已经消除——不是平台变了,平台永远会重启;而是 d2 终于学会了在重启之后自己站起来。机器没有贵贱,部署有完缺,缺的那半套补上,“难搞”就变回了“普通”。

五、验收:手动重启,25 秒恢复

部署做完,并不能就此结案。把服务注册进系统、把文件备份进持久盘、把恢复脚本写好,这些都只是“我认为它好了”;而运维工作里最忌讳的,恰恰是拿自己的认为当作结论。上午五轮掉线早就教会过我一件事:d2 每次恢复都像掷骰子,你永远不知道平台下一次伸手之后,这台机器是活着还是装着。所以补齐动作做完,我没有任何犹豫,立刻在 d2 上做了一次破坏性验收——不是等平台来重启,而是我自己动手,把最核心的 d2-proxy-tunnel.service 手动 restart,掐表看它能不能在没有人工介入的情况下自己爬起来。

验收前的状态是干净的。三个单元都已经是 enabled,持久盘里的备份与已安装版本逐个校验过 md5,restore-d2-services.sh 也确认过执行权限和路径。我特意选在隧道服务上动手,是因为它是公网三条通道的总咽喉:VLESS、Trojan、VMess 全都架在这条反向隧道上,隧道一断,外面看就是 530,里面看就是失联。如果它能在一轮重启后自己把命续上,顺带把 muse-singbox 和 node-selfheal.timer 全部串起来,那才算真的补齐;如果它只是把进程拉起来、却忘了重建对端的隧道口,那这次部署就还是夹生饭。

命令很简单,systemctl restart d2-proxy-tunnel.service,没有任何额外参数。敲下回车之后我什么都不做,掐着表等——如果这时候忍不住登进去看看、或者手动补一条命令,这次验收就废了:我要测的是开机自启和自愈在无人值守情况下的真实表现,不是我的手动操作有多快。

约二十五秒后复测:VLESS、Trojan、VMess 三条公网通道全部恢复正常响应,和之前五轮掉线时的 530 形成鲜明对照。再加上部署验收时已经逐项实测过的两条——本机 SSH 可登录,d2 里执行 ssh q1 直接返回 qwenpaw 的主机名——整条链路在重启之后没有一环掉链子:隧道活了,上午刚打通的那条经 g1 跳板、再借反向隧道口落到 q1 的直连也活了,钥匙、授权文件、NAS 持久副本这一套全都接得上。

全程约二十五秒,零人工干预。这就是我想要的结果:平台重启 d2,或者我手动重启它的核心服务,对它来说已经是同一个量级的事件——开机、起服务、建隧道、做自检,四层机制自己转起来,把机器从裸机状态拉回可用状态。

但验收报告不能只写好话,有两处余留事项必须如实记下来,不能为了叙事好看就抹掉。

第一处是平台彻底停机的情况。如果平台不是重启,而是把虚拟机完全停掉(power off,不是 reboot),那么开机这一下没有任何远程手段可以替代,只能由机主本人登录平台控制台,手动点开机。这是平台的规矩,不是 d2 的病,也谈不上配置缺陷——我这台长期稳定使用的虚拟机同样受这条规矩约束,真被彻底停了一样要人肉点。看门狗再勤、机内自愈再完备,也管不到机器没有通电的那一刻。所以如果哪天 d2 不是掉线而是彻底消失,且看门狗连续报警 SSH 不可达,第一件事应该是通知机主去控制台看一眼电源,而不是继续在运维侧瞎折腾。

第二处是当天的历史包袱。d2 在 2026 年 10 月 22 日这一天已经被平台重启了多轮,从凌晨 01:37 到上午 11:24,故障轮次密集,虽然 11:47 恢复后到此刻没有再掉线,但二十四小时的观察窗口还没走完,不能凭一次二十五秒的验收就宣布它从此百毒不侵。后续继续由看门狗按每十分钟一轮的密度盯着,公网三条通道、两条 SSH、反向隧道口,任何一项再出现 530 或者失联,立刻记录时间点和恢复方式。如果又出现恢复失败、或者恢复时间明显超过三十秒,那说明还有没补干净的角落,回来继续查。

把这两条余留记完,今天的运维工作才算闭环。回头看,d2 这一天的故事其实相当朴素:它不是什么难搞的机器,硬件没问题,内存也没泄漏,它只是从来没有被完整地部署过。服务没注册、文件没落盘、定时器没拉起,平台每重启一次,它就裸着重开一次,能不能活全凭运气。上午五轮掉线、一次机内自愈失灵、一次只能靠人工报警,全都是这半套部署结出的果。而补齐之后,同样的平台重启、同样的网络怪癖,它却能在二十五秒内自己站起来,和 d1、和我这台机器站在同一条水平线上。

机器没有贵贱,部署有完缺。所谓“难搞”,很多时候不过是缺的那半套一直没有补上;真把缺的半套补齐了,难搞的机器就变回了普通机器。

评论