VPN 中转停止接受 SSH:文件描述符上限
隧道流量仍在继续,而它后面的 socat 中转却拒绝新会话。先追踪路径,再读取活动 PID 上继承来的上限。
WireGuard 握手依然能完成,对端也仍显示着最近的端点,可经由其后中转的新 SSH 会话却毫无横幅地挂住了。这一组合把问题从隧道引向接受套接字的进程。像 socat 这样的中转处在应用路径上,有自己的描述符预算、自己的子进程和自己继承来的上限。下文的走访从客户端开始,停在第一个无法再打开套接字的组件上。
改数字之前先追踪路径
追踪一个失败的请求,而不是对整条隧道泛泛推理。客户端写入隧道,VPN 守护进程解密并路由,中转在远端接受。每一步都消耗描述符,而通常耗尽的是最后一步。
从一个可复现的请求开始并计时。如果 TCP 握手完成却没有应用横幅到达,说明中转接下了套接字,随后卡在子进程或它的上游腿上。如果握手本身从未完成,原因就是路由、防火墙或监听状态,而不是描述符天花板。记录你看到的是哪种形态,因为它们指向不同的进程。
也要数一数重复工作的跳数。会分叉的监听器把每个已接受的套接字交给一个保有自己副本的子进程,所以真实总数属于整棵树,而不只是父进程。
读取中转继承的上限
唯一重要的上限属于正在运行的进程,并且由启动该进程的东西设定。在 shell 里输入 ulimit -n 描述的是 shell。问服务管理器应用了什么,然后在 PID 上确认。
systemctl show -p MainPID -p LimitNOFILE vpn-relay.service
cat /proc/$(systemctl show -p MainPID --value vpn-relay.service)/limits
limits 文件会按进程所见打印软上限和硬上限。宽松硬值下的低软值意味着守护进程可以自行抬高上限,有些会在启动时这样做;而相等的低值意味着来源是 unit 文件。
drop-in 按字典序加载,后加载的文件胜出,所以 systemctl cat vpn-relay.service 是判断生效内容的权威视图。修改一个管理器从不读取的文件,是抬高数字却毫无效果的最常见原因。
把稳态并发与滞留状态分开
两个进程可能因无关原因撞上同一上限,而补救办法不同。跟随有用并发会话的需求是容量问题。工作结束后仍继续增长,则是泄漏或一组没人关闭的会话。
在空闲时、正常峰值时,以及负载停止几分钟后再测一次。用 ls /proc/PID/fd | wc -l 对照软上限统计打开条目,就能看出差距;比上限低一两个的计数是硬性停止,而不是慢路径。
当计数不肯下降时,看套接字状态而不是总量。停留在 CLOSE_WAIT 的描述符意味着对端已关闭而你这边从未关闭,这是应用生命周期缺陷,更大的上限只会推迟它。也要限制子进程模型:没有并发子进程上限的分叉式中转会同时耗尽描述符、进程槽和内存。
在活动进程上检查新值
编辑 unit 之后,重新加载管理器并重启服务,因为仅重新加载永远不会替换运行中进程已经继承的上限。要在真正服务流量的进程上验证。
systemctl daemon-reload
systemctl restart vpn-relay.service
awk '/Max open files/ {print}' /proc/$(systemctl show -p MainPID --value vpn-relay.service)/limits
ss -tnp state established '( sport = :2222 )' | wc -l
awk 行会返回新的那一对;如果仍显示旧的,说明你编辑的 drop-in 不是被加载的那个。ss 计数是你的负载检查,所以打开你预期的会话数并看它攀升。这些会话关闭后计数仍继续上升,意味着上限从来不是问题的全部。
任何大幅提高都附带一个提醒。用 select() 轮询的应用无法监视超过 FD_SETSIZE 的描述符,在典型构建上这个值是 1024,所以把上限抬到那之上可能产生看似无关的失败。systemd 执行参考对它所启动的服务也给出同样的警告。
让重启远离隧道可用性
重启中转会丢掉它的活动会话,而这是与 VPN 掉线分开的事件。要安排变更,使中转轮转时隧道保持在线,并警告任何长期会话会跨越它的人。
回退就是上一个 unit 文件再加一次重启,而那个文件要一直留到新值扛过一个工作日。然后用最初失败的请求收尾:一个经由中转的新 SSH 会话、一次足够长的传输,以及回到空闲水平的描述符计数。