status=200/CHDIR:systemd 从未启动 OpenVPN
CHDIR 失败意味着 systemd 无法进入某个目录,而不是 OpenVPN 无法读取配置文件。动 VPN 配置前先读 unit。
服务失败,日志提到 spawn 和某个目录,第一反应往往是检查 VPN 配置。那个文件其实一个字节都还没被读取。systemd 是在准备进程时失败的,可执行文件还没运行,因此既没有解析器、也没有证书、更没有远程主机被问过。把状态码当作对 systemd 的判断而不是对 OpenVPN 的判断,这件事才会变成一次短调查。
先读状态码,再看应用
服务名后面的数字代号指出失败发生在哪一个阶段。目录失败报告为 CHDIR 状态,而无法执行的二进制则是完全不同的代号。两者在摘要行里看似相似,成因却毫无共同点。
这个区别重要,是因为修复点在不同文件里。CHDIR 失败关乎路径以及服务对它的看法。exec 失败关乎二进制、它的解释器或无关的挂载。改 VPN 远程地址、加密套件列表或证书路径,哪一个都解决不了。
确认失败发生在启动阶段,而不是一次成功运行之后。如果服务昨天还好好的,今天在同一阶段失败,那就是目录、它的权限或 unit 本身发生了变化,这个变化才是要找的证据。
问 unit 想待在哪儿
读生效的 unit,而不是你以为已安装的那个文件,因为 drop-in 可以在你从未编辑主 unit 的情况下覆盖工作目录。show 的输出直接回答这个问题:
systemctl cat [email protected]
systemctl show [email protected] -p WorkingDirectory -p RootDirectory -p User -p ProtectHome
journalctl -u [email protected] -b --no-pager | tail -30
对 upstream 服务器模板来说,工作目录就是服务器配置目录,实例名决定其中的哪个配置。名为 branch 的实例因此期望同一个目录里有自己的文件,放在别处的配置根本不满足工作目录这一要求。
把 unit 要求的东西和实际存在的东西对照。曾经存在过的路径、现在指向别处的符号链接、或不再对应目录的实例名,都是会产生这种失败的普通变化。
按服务看到的样子检查路径
你的 shell 和服务并不共享同一个文件系统视图。配置了根目录或 root image 会改变绝对路径在 unit 里的含义,在宿主机上明明存在的路径,在那个命名空间里可能并不存在。先读 unit 的设置,再相信手工测试。
权限会让问题更糟。服务以特定用户运行,该用户必须能穿过每一级父目录到达目标,而不仅仅是能读目标本身。对 root 可读、对服务用户不可进入的目录,会产生看起来像路径缺失的失败。
namei -l /etc/openvpn/server
systemctl show [email protected] -p ProtectSystem -p ReadOnlyPaths
加固设置也是这幅图的一部分。隐藏家目录或把文件系统的一部分设为只读的 unit 只是按配置行事,工作目录必须放在 sandbox 仍然暴露的地方。
修好归属,然后预期另一个错误
通过拥有该配置的人来修正布局或 unit。布局不对时把配置移到期望目录是合理的修复,但它解释不了进不去的目录,所以别把它当成通用答案。
不要走放宽配置树权限或移除 sandbox 的捷径。两者都让症状消失,同时也拿掉了刻意放在那里的保护,下一次失败会更难解读。
重启实例,读下一个事件,而不是假定成功。接下来很可能报 OpenVPN 打不开配置文件,这是进展:工作目录进去了,解析器已经在跑。从这里顺着 unit 的实际文件映射走,别退回网络排查。
systemd 执行参考定义了工作目录如何与根目录和 sandbox 一起被解释。要记住的模式很简单:每个状态码指向不同阶段,只有应用自身的错误才描述应用。