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

作为一名运维工程师,最让人头疼的场景之一莫过于收到“磁盘空间不足”的告警。当你登录服务器,熟练地敲下 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,以匹配问题场景)
这条日志提供了破案的全部信息:
COMMAND
和PID
:dockerd
进程(Docker守护进程,PID 1317)是“作案者”。SIZE/OFF
: 文件大小高达 51539607552 字节,约等于 48GB!这与我们丢失的空间完全吻合。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)。
- 编辑(或创建)Docker的配置文件
/etc/docker/daemon.json
。 - 在其中加入以下内容,为所有新创建的容器配置全局的日志策略:
{ "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会自动管理日志的分割和旧文件的删除,并且是以正确的方式进行,不会产生“幽灵占用”。
- 保存文件后,再次重启Docker服务使配置生效。
sudo systemctl restart docker
此配置对之后所有新创建的容器都有效。对于已存在的容器,需要重新创建它们(例如通过docker-compose up -d --force-recreate
)才能应用新的日志限制。
总结
这次从 df
与 du
的诡异差异开始的磁盘探案之旅,最终揭示了一个由错误运维习惯引发的“血案”。核心教训是:永远不要用 rm
命令去处理一个正在被服务持续写入的日志文件! 应当始终使用应用程序或服务自身提供的日志管理机制。通过正确配置Docker的日志轮转,我们不仅解决了眼前的危机,更为服务器的长期稳定运行提供了保障。