你的服务在 OpenVPN 真正运行之前就失败了,而错误信息提到的却是一个你从未配置过的挂载单元。这就是线索:systemd 拒绝了整个事务,因为根挂载单元被屏蔽了,而你的 VPN 配置文件还没有被读取。常见原因是运行时单元目录里残留了文件,而不是 fstab 损坏,也不是证书缺失。先顺着文件查下去,因为这是最容易排除的解释。

错误指向根挂载,而不是你的配置

systemd 根据内核的挂载信息自行生成根挂载单元,并把它命名为 -.mount,因为连字符代表根路径。这个单元通常只存在于内存中,背后并没有对应的文件。当错误说它被屏蔽时,说明有人把单元文件放到了 systemd 会加载的位置,并让它指向 /dev/null。屏蔽是真实状态,不是消息里的笔误。

实例单元失败,是因为它的任务图通过排序和自身的挂载依赖到达了根挂载。事务在 OpenVPN 二进制被执行之前就被拒绝,所以客户端配置里没有任何东西能成为原因。这也是为什么更换配置、证书或凭据对这次特定故障毫无作用。

排查 OpenVPN 之前先检查运行时目录

在运行时单元目录里找这个名字完全一致的文件,然后问 systemd 它认为该单元是什么。两个答案都很快,合起来就能把残留文件和真正的挂载问题区分开。

ls -l /run/systemd/system/-.mount
systemctl status -.mount
systemctl list-unit-files --state=masked | grep mount

正常机器没有这个文件,被屏蔽列表里也不包含根挂载。如果第一条命令打印出指向 /dev/null 的符号链接,诊断就可以定论了。同一目录里也可能有同一套工具创建的 tmp.mount 屏蔽,它会在另一个名字下造成同一类故障。要看整个目录,而不是只看错误里提到的名字。

ls -la /run/systemd/system/*.mount

如果目录是干净的,单元却仍然被屏蔽,那么屏蔽位于搜索路径的其他位置,systemctl cat -.mount 会显示正在加载哪个文件。

删除文件,重新加载,然后重启服务

移除运行时文件并重新加载管理器就够了,而且不会碰任何来自软件包的东西。启动前要先重新加载,因为 systemd 会一直保留已加载的单元状态,直到它重新读取该目录。

sudo rm -f /run/systemd/system/-.mount /run/systemd/system/tmp.mount
sudo systemctl daemon-reload
systemctl status -.mount
sudo systemctl start [email protected]

之后要读服务自己的日志,而不是想当然地认为修好了。干净启动只能证明 systemd 接受了该单元;客户端仍可能因为配置缺失、对端不可达或证书无法验证而退出。这些才是被屏蔽单元掩盖住的普通 OpenVPN 问题。日志会记录加载了哪个配置,以及隧道是否真的建立。如果单元起来了而隧道没建,你已经换到了另一个问题,这就是进展。

journalctl -u [email protected] -b --no-pager

由于 /run 是临时文件系统,重启也会清掉这个文件。这让重启成为一项有用的测试:如果服务在重启后正常、而在下一次部署后又失败,说明文件被烤进了镜像。

阻止它从镜像里回来

在构建时屏蔽挂载单元的容器或虚拟机模板,会把这个屏蔽带进每一个克隆。接下来任何需要文件系统的操作都会坏掉,而错误信息却怪罪挂载,而不是它来自的镜像。在某个运行中的实例里修好,只会把问题藏到下一个实例创建为止。

在构建上下文里搜索这个文件名和创建它的命令,然后从模板里去掉屏蔽并重新构建。如果构建期间确实需要这个屏蔽,就在结束构建的同一层里移除它,这样交付的镜像启动就是干净的。要在全新的克隆上验证,而不是在你手工修好的机器上。这是唯一能证明镜像确实改变了的测试。

值得记住的现象是错误和你正在配置的东西之间的错位。当 systemd 抱怨挂载、而你正在启动 VPN 客户端时,先看消息里的单元名,再看服务名。

相关文章