问题背景
公司微服务集群部署在 CentOS 7 服务器上,Java 应用(Spring Boot)运行时需要在 /tmp 下创建临时目录用于嵌入式 Tomcat 的文件上传缓存和 JNA 的 native 库解压。此前该环境稳定运行超过一年。
故障现象
3 月 10 日下午 4:00,运维团队完成了一批安全加固操作(包括配置 systemd-tmpfiles 清理规则)后,陆续有监控告警:订单服务、支付服务和文件上传服务的 error rate 开始上升。
日志中出现大量:
|
|
所有涉及临时文件创建的操作——文件上传、PDF 导出、Excel 生成——全部失败。
排查过程
第一步:检查 /tmp 状态
|
|
Spring Boot 默认创建的 /tmp/tomcat.* 目录全部不存在!
第二步:检查 /tmp 的挂载选项
|
|
noexec 标志——/tmp 被挂载为禁止执行。Java 的 JNA(Java Native Access)需要在 /tmp 下解压 .so 文件并加载,noexec 直接导致这个操作失败。
第三步:追溯变更
在安全加固中新增了 systemd-tmpfiles 配置:
|
|
关键问题在这一行 d /tmp 1777 root root 1d——它告诉 systemd-tmpfiles:
如果
/tmp不存在,用权限 1777 创建;如果存在,清空所有超过 1 天的文件和目录。
更致命的是,临时目录规则并不是简单的 rm——它在创建 /tmp 时不会自动加上 exec 选项,而运维恰好在 /etc/fstab 中也添加了 noexec。
第四步:验证清理动作
|
|
一次清理会删掉所有正在运行的 Java 应用创建的临时目录。由于 Spring Boot 的 Embedded Tomcat 在启动时创建了 /tmp/tomcat.* 目录,运行时持续使用。当 systemd-tmpfiles 在每天的定时任务中执行清理时,会连同正在使用的临时目录一起删除。
而且 Java 应用尝试重新创建时,因为 noexec 导致 JNA 的解压-加载流程失败,进而引发连锁失败。
解决方案
紧急恢复
|
|
长期方案
|
|
在服务器上为每个应用创建独立的临时目录 /opt/<app>/temp/,脱离 /tmp 依赖。
根因分析
直接原因:安全加固中同时做了两件事——配置 systemd-tmpfiles 清空 /tmp 并给 /tmp 加了 noexec——导致 Java 应用的临时目录被删除后无法重新创建和加载 native 库。
深层原因:安全策略变更未在测试环境验证对业务应用的影响。/tmp 目录被大量 Linux 应用隐式依赖,任何对其的变更都应该做充分的影响评估。
预防措施
- 应用临时目录独立化:每个微服务使用独立的
/opt/<app>/temp/,不共用/tmp - 安全变更影响评估:对基础环境(
/tmp、/etc/fstab、内核参数等)的变更,必须先在测试环境验证 48 小时 - 禁止盲加 noexec:
noexec对/tmp更安全,但必须确认所有应用兼容后才上线 - 监控 /tmp 异常:添加 Prometheus 指标监控 Java 应用是否能正常创建临时文件
- 变更窗口制度:基础环境变更必须在低峰窗口执行,且保留回滚 SOP
总结
/tmp 是 Linux 上最"透明"又最危险的目录——几乎所有应用都在用它,但没人专门管理它。运维想做安全加固,方向没错,但 noexec + 定时清理这组组合拳在非 JNA 环境下可能没事,一碰到 Java 的 native 库加载就炸了。教训是:对共享资源的变更,影响的不是你自己用的那个应用,而是所有依赖它的应用。