【运维实战】Linux磁盘“幽灵占用”探案:48GB去哪了?从df与du诡异差异到Docker日志黑洞

第一幕:谜案开端 – 70G与22G的诡异差异

image-20250710195329534

作为一名运维工程师,最让人头疼的场景之一莫过于收到“磁盘空间不足”的告警。当你登录服务器,熟练地敲下 df -h 时,看到了这样的输出:

Filesystem                         Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv   98G   70G   24G  75% /

70G 已用空间!情况紧急。于是,你准备找出占用空间的大文件,便执行了 du -sh /*,期望找到元凶。然而,结果却让你大跌眼镜——即使将所有目录的大小相加,也只有区区22G左右。

df 说用了70G,du 说只找到22G,那凭空消失的48G空间去哪了?

这便是经典的Linux磁盘“幽灵占用”问题。要解开谜题,我们首先要理解两个核心命令的根本区别:

  • df (Disk Free): 它直接从文件系统的元数据(Superblock)中读取信息,报告整个分区已分配和未分配的磁盘块数量。它看到的是全局的、底层的视角。
  • du (Disk Usage): 它通过遍历你指定的目录,累加所有它能访问到的文件和子目录的大小来计算总和。它看到的是文件目录树的视角。

当两者出现巨大差异时,几乎可以肯定:有文件被进程占用着,但它的目录链接(即文件名)已经被删除了。 对于du来说,它看不见这个文件,但对于df来说,它所占用的磁盘块依然是“已使用”状态。

第二幕:神探登场 – lsof 揪出“隐形”真凶

要找到这些被删除但仍被进程“霸占”的文件,我们需要请出我们的神探——lsof (List Open Files)。一个简单的命令就能让真相浮出水面:

# 列出所有状态为 (deleted) 的已打开文件
sudo lsof | grep '(deleted)'

执行后,我们从海量信息中,精准地捕获到了以下关键线索:

COMMAND   PID    USER   FD   TYPE DEVICE   SIZE/OFF           NODE NAME
...
dockerd   1317   root   52w  REG  253,0   51539607552        1454447 /data/docker/containers/17d55f10.../17d55f10...-json.log (deleted)
...

注:此处文件大小为模拟的48G,以匹配问题场景

这条日志提供了破案的全部信息:

  1. COMMANDPID: dockerd 进程(Docker守护进程,PID 1317)是“作案者”。
  2. SIZE/OFF: 文件大小高达 51539607552 字节,约等于 48GB!这与我们丢失的空间完全吻合。
  3. NAME: ...-json.log (deleted),这是一个已经被删除的Docker容器日志文件。

案情明朗了:某个Docker容器产生了巨量的日志,之后这个日志文件被删除了,但由于 dockerd 进程仍然持有它的文件句柄,导致其占用的48GB空间迟迟无法被操作系统回收。

第三幕:水落石出 – crontab 中的“定时炸弹”

是什么操作导致了这个日志文件被删除呢?进一步排查,我们在系统的定时任务中找到了元凶。执行 crontab -l,看到了这样一条惊人的配置:

* 3 * * * rm -f /data/docker/containers/*/*.log

这是一个每天凌晨3点执行的定时任务,其目的是“清理”所有Docker容器的日志文件。这种做法看似直接,却是运维中的一个典型“大坑”。

为什么 rm 是错误的做法?rm 命令执行时,它只是切断了文件名与文件数据(inode)之间的链接。如果此时有进程(比如 dockerd)正打开着这个文件,系统会认为文件仍在使用中,因此不会释放其占用的磁盘块。进程会继续向这个“看不见”的文件写入数据,直到进程重启或关闭,文件句柄被释放,空间才会被回收。

第四幕:拨乱反正 – 从治标到治本的解决方案

既然找到了根源,我们就能对症下药,彻底解决问题。

第一步:拆除炸弹 (紧急处理) 立刻停止这种错误的行为,防止问题复发。

# 编辑当前用户的定时任务
crontab -e
# 找到并删除 `rm -f ...` 这一行,然后保存退出。

第二步:释放空间 (立即生效) 要让 dockerd 释放文件句柄,最直接的方法就是重启它。

# 重启Docker服务
sudo systemctl restart docker

【警告】 此操作会中断并重启服务器上所有正在运行的Docker容器。请务必在业务低峰期或可接受中断的时间窗口执行。

重启完成后,再次运行 df -h,你会发现那消失的48GB空间已经回来了!

第三步:建立长效机制 (治本之策) 为了避免未来重蹈覆辙,我们必须采用Docker官方推荐的日志管理方式——配置日志轮转(Log Rotation)

  1. 编辑(或创建)Docker的配置文件 /etc/docker/daemon.json
  2. 在其中加入以下内容,为所有新创建的容器配置全局的日志策略: { "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }
    • "max-size": "50m": 设置单个日志文件的最大体积为50MB。
    • "max-file": "3": 最多保留3个日志文件(1个当前,2个备份)。
    • 这意味着每个容器的日志占用上限为 50MB * 3 = 150MB。Docker会自动管理日志的分割和旧文件的删除,并且是以正确的方式进行,不会产生“幽灵占用”。
  3. 保存文件后,再次重启Docker服务使配置生效。 sudo systemctl restart docker 此配置对之后所有新创建的容器都有效。对于已存在的容器,需要重新创建它们(例如通过 docker-compose up -d --force-recreate)才能应用新的日志限制。

总结

这次从 dfdu 的诡异差异开始的磁盘探案之旅,最终揭示了一个由错误运维习惯引发的“血案”。核心教训是:永远不要用 rm 命令去处理一个正在被服务持续写入的日志文件! 应当始终使用应用程序或服务自身提供的日志管理机制。通过正确配置Docker的日志轮转,我们不仅解决了眼前的危机,更为服务器的长期稳定运行提供了保障。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享