登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux 严格只读隔离后配置写不进去:ReadWritePaths、RuntimeDirectory 与回滚检查

来源:17golang原创

时间:2026-08-20 23:55:36 185浏览 收藏

服务升级后突然报 Read-only file system,很多人第一反应是去改目录属主,跑chmod调权限。如果服务的unit配置里新增了 ProtectSystem=strict,出问题的根源大概率是服务进程感知到的挂载视图变了:哪怕宿主机上对应目录权限已经设成755,进程所在的隔离命名空间也会把这部分路径识别成只读状态。

排查这类问题的思路很清晰,先定位哪部分路径被挂载成了只读,再判断是把写入操作迁移到专属运行目录,还是只为确实需要持久化的存储路径开一个最小范围的可写权限口子。

启用ProtectSystem=strict之后出现写入失败,先别反复改宿主机文件权限,优先核对systemd挂载隔离层的路径规则,再按临时运行数据、持久业务数据的属性拆分可写范围,最后做全链路回滚校验。
要点速览
  • ProtectSystem=strict 限制的是服务进程视角下的文件系统视图,和普通的文件属主权限控制不是同一层逻辑。
  • 临时PID文件、socket套接字和缓存数据优先放进 RuntimeDirectory;需要持久化留存的业务数据,才考虑配置 ReadWritePaths=
  • 修改unit配置后必须执行daemon-reload、重启服务,并用 systemctl showfindmnt 校验新启动的进程实际受的权限边界。
  • 操作回滚时要先撤掉新增的隔离规则,再恢复原来的目录写入路径,避免配置改一半留下隐蔽的故障点。

升级后写入失败,先区分普通权限错误和挂载只读

假设服务名是 report-worker.service,升级后它需要把临时状态写到 /var/lib/report-worker,服务日志里却抛出了下面的报错:

open /var/lib/report-worker/state.json: Read-only file system

这个报错和 Permission denied 属于完全不同的问题场景。前者代表内核直接拒绝了写入操作的文件系统属性限制,后者才更可能是UID、GID或者文件权限位配置不对。排查顺序应该先确认unit里有没有启用这类隔离项,再查服务进程实际的挂载信息:

systemctl cat report-worker.service
systemctl show report-worker.service -p ProtectSystem -p ReadWritePaths -p RuntimeDirectory
pid=$(systemctl show -p MainPID --value report-worker.service)
findmnt -no TARGET,OPTIONS --target /var/lib/report-worker
test "$pid" -gt 0 && grep ' /var/lib/report-worker ' /proc/$pid/mountinfo

最后一条命令只能在服务已经生成主进程的情况下执行。如果unit是一次性任务或者正处于重启流程,要先读取 systemctl status 的运行状态,再获取对应PID,千万不能把旧PID的挂载检测结果当成当前新版本的运行依据。

Linux systemd ProtectSystem=strict 写入失败的挂载只读证据与权限错误对照

ProtectSystem=strict 到底改变了哪些路径规则

ProtectSystem=strict 会让服务进程看到的系统级目录全部进入只读或者不可写入的隔离视图。它的设计目标是降低长期运行的后台服务随意修改系统文件的风险,并不是用来替代文件属主、ACL或者SELinux策略的。这里要特别注意:哪怕你在宿主机上用root账号写入同一个目录完全正常,也不代表unit配置里运行的服务进程能拿到同等的写入权限视图。

需求场景推荐配置方案验收核心要点
PID文件、socket、短期缓存RuntimeDirectory=report-worker/run/report-worker 随服务启动自动创建
必须持久化的业务状态ReadWritePaths=/var/lib/report-worker只开放目标文件夹,不要放开整个 /var 目录的可写权限
静态配置和程序二进制文件保持默认只读状态服务启动后不能自行改写安装目录下的任何文件

如果你的应用现在把临时文件和长期业务状态混存在同一个目录里,优先拆分不同用途的目录,远比直接加一条宽泛的可写规则稳妥得多。隔离规则开得越松,后续排查问题的时候,就越难判断某次意外写入是不是真的有业务逻辑支撑。

优先把运行态数据迁移到 RuntimeDirectory

运行态文件本来就不应该依赖运维手工提前创建的 /var/run 子目录。你可以直接修改unit配置,让systemd在服务启动时自动生成对应运行目录,再通过环境变量把这个路径传递给业务程序:

[Service]
ProtectSystem=strict
RuntimeDirectory=report-worker
RuntimeDirectoryMode=0750
Environment=REPORT_RUNTIME_DIR=/run/report-worker
启动指令=/usr/local/bin/report-worker

程序侧把PID、socket或者可丢弃的临时缓存写到 REPORT_RUNTIME_DIR 路径下,需要跨重启保留的数据再放到单独的持久存储目录里。这样服务停止之后,运行目录的清理行为是完全可预期的,不会出现把临时生成的日志文件误当成核心业务数据留存的问题。

如果你的程序写死了固定路径,暂时没法改代码适配新的运行目录规则,才考虑用 ReadWritePaths 做临时过渡。就算是过渡配置,也要在注释里写清对应目录的用途,后续再安排把代码逻辑切到标准运行目录的路径下:

[Service]
ProtectSystem=strict
ReadWritePaths=/var/lib/report-worker
RuntimeDirectory=report-worker

ReadWritePaths 只开最小权限口子,别用整棵目录兜底

ReadWritePaths= 的核心价值是明确声明“这个服务业务逻辑上确实需要写入这个位置”,而不是直接把全局的只读保护全部关掉。别因为某个状态文件写不进去,就直接改成 ProtectSystem=no 或者开放 /var 全路径;这种操作会让原本能被隔离规则拦截到的路径误写问题,重新变成很难发现的静默风险。

配置改完之后,先让systemd重新读取全部unit配置,再重启服务生成新的进程:

systemctl daemon-reload
systemctl restart report-worker.service
systemctl show report-worker.service -p ProtectSystem -p ReadWritePaths -p RuntimeDirectory
systemctl status report-worker.service --no-pager

修改完unit配置却不执行 daemon-reload,或者只重载配置却不重启服务,都会造成“配置文件看起来已经改对了,实际运行现场完全没变化”的假象。验收的时候最好把unit文本、属性输出结果和新生成的主PID一起留存,后续排查故障的时候就能明确对应到到底哪一版规则在生效。

systemd 从持久目录混写迁移到 RuntimeDirectory 与 ReadWritePaths 窄范围开放的检查清单

回归检查要覆盖正常写入、重启和异常失败路径

只看到服务运行状态变成 active (running) 完全不够,至少要做一遍正常写入验证、重启后状态复查,还有越界写入拦截测试:

  1. 写入 /run/report-worker 的PID或者socket文件成功,服务日志里没有出现只读相关的报错。
  2. 重启服务之后,运行态文件按预期重新生成;需要持久化保留的状态数据仍然完整保存在 /var/lib/report-worker 路径下。
  3. 尝试往系统目录 /usr/local 或者没有列入 ReadWritePaths 规则的路径写入数据,服务应该直接报错退出,并且留下明确可识别的错误日志。
  4. 用新生成的 MainPID 复查 /proc/$pid/mountinfo 挂载视图,确认检测结果对应的是新进程的状态,不是旧进程的残留数据。
  5. 回滚操作前先备份当前的unit文件和服务状态,再删掉本次新增的隔离配置项,执行 daemon-reload 与重启,最后重新校验原来的写入路径是否正常。不要只删掉 ProtectSystem 规则,却保留程序已经切换过去的新环境变量,不然会把原本的权限问题变成更难排查的“路径不存在”故障。

常见问题

ProtectSystem=strict 会影响配置文件的读取操作吗?

它主要限制写入行为;读取操作是否能正常执行,还要结合ProtectHome、InaccessiblePaths、文件本身权限和实际配置的挂载边界一起判断,不能只凭这一个参数的名字就推断全部的访问结果。

ReadWritePaths 能绕过Linux原生的文件权限规则吗?

不能。它只是调整服务进程视角下的文件系统可写范围,原本的UID、GID、ACL和其他安全策略仍然会正常生效。

RuntimeDirectory 里的文件重启后还会保留吗?

它本身就是为运行态临时数据设计的,生命周期和服务绑定;需要跨重启留存的内容要放到单独的持久目录里,再明确通过ReadWritePaths规则开放这个目录的可写权限。

为什么改完unit配置之后路径还是只读状态?

最常见的几个原因是没有执行daemon-reload重载配置、服务本身没有重启、检查权限的时候取了旧PID的信息,或者有drop-in配置文件覆盖了主unit里的规则。你可以用systemctl cat、systemctl show和新的MainPID三组信息交叉核对,很快就能定位到问题。

把隔离配置变成可回滚的迁移项

这次配置调整真正要确认的不是“服务能不能正常启动”,而是每一类文件都有明确的归属路径:运行态数据放到RuntimeDirectory,持久化业务状态只开放必要的目录,系统级文件全程保持只读。完成daemon-reload、重启、正常写入验证、越界拦截测试和回滚复测之后,ProtectSystem=strict才算从一行写在配置里的参数,变成真正可落地校验的生产级安全约束。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>