现场:/ 分区 99%

机器操作明显卡顿。先用 df -h 检查磁盘,发现 400GB 的 / 分区几乎被写满。继续排查 /var/log 后,确认 /var/log/opensm.log 单文件达到 326GB

当时的处置动作是先将日志备份到 /public,再删除原文件。但再次执行 df -h 后,/ 仍显示占满。

随后用目录级统计交叉验证:

du -sh /{var,opt,usr,home,root,tmp} 2>/dev/null
df -h /

如果 du 看到的目录占用明显小于 df 中的文件系统占用,通常要优先怀疑两类情况:

  • 文件已从目录树删除,但仍被进程打开。
  • 某个挂载点覆盖了原目录,使目录统计和底层文件系统统计不一致。

本次更像第一种。

反常:删除后 df -h 仍满

lsof 检查 deleted 文件句柄:

lsof +L1 | grep opensm.log

+L1 会列出 link count 小于 1 的打开文件。看到类似 (deleted) 的结果,说明文件名已经从目录中消失,但进程仍持有对应 inode 的文件描述符。

这时不要反复重建同名 /var/log/opensm.log。新建出来的是另一个文件,旧 inode 仍然被进程占用,空间不会因为同名文件重新出现而释放。

原理:为什么“删了还不回收空间”

Linux 文件系统中,目录项、inode、数据块、进程文件描述符是分开的。

rm /var/log/opensm.log 删除的是目录项,也就是文件名到 inode 的引用。只要还有进程打开这个 inode,内核仍会保留数据块。结果就是:

  • ls /var/log/opensm.log 看不到文件。
  • du -sh /var/log 统计不到这个文件。
  • df -h / 仍显示空间被占用。
  • lsof +L1 能看到被删除但仍打开的文件。

这类问题在日志文件上很常见,尤其是服务持续写日志时直接 rm 大日志文件。

应急处置:释放空间

方案一:可中断服务时,停服务后删除

这是最稳妥的流程:

systemctl stop opensm
rm -f /var/log/opensm.log
systemctl start opensm
df -h /

停服务会关闭文件描述符,删除动作才能真正释放底层数据块。

方案二:已经误删时,重启服务

如果文件已经被删除,但 OpenSM 仍在持有旧句柄,重启服务通常可以释放空间:

systemctl restart opensm
df -h /

本次现场采用该方法后,/ 分区空间恢复正常。

方案三:不重启时清空进程 FD

如果业务要求尽量不重启 OpenSM,可以先定位 OpenSM 持有的文件描述符:

pidof opensm
ls -l /proc/$(pidof opensm)/fd | grep opensm.log

确认实际 FD 后,通过 /proc 清空对应文件内容:

cat /dev/null > /proc/$(pidof opensm)/fd/1
df -h /

上面的 1 只是示例,必须替换成现场看到的实际 FD。误清空错误 FD 可能影响标准输出、错误输出或其他日志流,操作前要确认目标。

追因:OpenSM 为什么会产生日志海啸

释放空间只是止血,还要解释为什么 OpenSM 会写出 326GB 日志。常见原因有三类。

链路 flap

InfiniBand 端口反复在 DOWNINITUP 之间变化,会触发频繁的 subnet 变化和拓扑重扫,OpenSM 可能持续刷日志。

快速体检:

iblinkinfo | grep -E "LinkUp|Down"
ibstat

关注 HCA 是否处于 Active / LinkUp,以及交换机端口是否频繁异常。

Verbose 参数过高

如果 OpenSM 启动参数带 -v-V 或类似 verbose 级别,日志量会被显著放大。调试阶段可以短期开启,长期运行不应保留高详细度日志。

检查启动方式:

ps -ef | grep '[o]pensm'
systemctl cat opensm 2>/dev/null || true

在一些老系统或厂商环境中,OpenSM 可能由 SysV 脚本启动:

sed -n '1,160p' /etc/rc.d/init.d/opensmd

多个 Subnet Manager 竞争

同一 IB fabric 中多个 SM 竞争 master,可能导致主从切换和重复日志。至少要确认运行中的 OpenSM 实例数量:

ps -ef | grep '[o]pensm'

如果存在多个管理节点或交换机内置 SM,也要确认 master/subordinate 关系是否符合预期。

治本:降低日志量和稳定链路

降低 OpenSM 日志级别

如果是 SysV 脚本场景,可以编辑启动脚本,去掉长期运行不需要的 -v / -V

vi /etc/rc.d/init.d/opensmd
# 示例:仅保留后台运行和日志路径
# /usr/sbin/opensm -B -f /var/log/opensm.log

systemctl restart opensm

实际参数应以现场发行版、OFED 包和厂商文档为准。

稳定 IB 链路

如果日志来自链路 flap,应继续检查:

  • 光模块、铜缆、HCA、交换机端口是否异常。
  • 是否有长期空闲但反复抖动的端口。
  • 交换机固件、HCA 固件和 OFED 版本是否存在已知问题。
  • 是否只有一个期望的 master SM。

可选:把日志迁到大容量分区

如果 /var/log 空间较小,可以把 OpenSM 日志迁移到更大的分区,并保留兼容路径:

mkdir -p /public/logs
systemctl stop opensm
mv /var/log/opensm.log /public/logs/opensm.log
ln -sf /public/logs/opensm.log /var/log/opensm.log
systemctl start opensm

这只是降低根分区风险,不替代日志级别治理和 logrotate。

防复发:按大小触发 logrotate

日志暴涨通常不是按周发生的。只做 weekly 轮转,遇到链路风暴时仍可能在一个周期内把根分区写满。更适合 OpenSM 这类场景的是按大小触发。

示例配置:

# /etc/logrotate.d/opensm
/var/log/opensm.log {
    size 50M
    rotate 10
    missingok
    notifempty
    copytruncate
    compress
    delaycompress
    create 644 root root
    postrotate
        systemctl reload opensm >/dev/null 2>&1 || true
    endscript
}

注意 copytruncate 的取舍:它适合不方便让服务重新打开日志文件的程序,但在复制和截断之间可能丢失极少量日志。对诊断日志通常可以接受;对审计日志要谨慎。

演练:

logrotate -d /etc/logrotate.d/opensm
logrotate -f /etc/logrotate.d/opensm

确认自动执行:

systemctl list-timers | grep logrotate
cat /var/lib/logrotate/logrotate.status | grep opensm.log

复盘

本次问题的关键不是“日志太大”本身,而是几个信号连在一起:

  1. df -h 显示 / 接近满。
  2. /var/log/opensm.log 单文件异常膨胀。
  3. 删除后 df 不下降,而 du 统计不到对应空间。
  4. lsof +L1 看到 deleted 文件仍被 OpenSM 持有。
  5. 重启 OpenSM 释放句柄后,空间恢复。
  6. 后续通过降低日志级别、稳定链路和按 size 轮转防复发。

经验是:当 dfdu 明显背离时,优先查 lsof +L1。删除正在写入的日志只是删除文件名,不等于释放空间。眼前止血靠关闭文件句柄,长期治理靠降低日志源头、稳定 IB fabric 和配置按大小轮转。