磁盘告警这种事干运维的早晚都会碰上。我见过最典型的场景是这样的某天监控突然弹出“/ 分区使用率 92%”业务方在群里你说服务写不进数据了你ssh上去第一件事就是想看看空间到底被谁吃了结果发现df -h是满的du却看不出哪里大再查发现一堆被删掉的文件还被进程占着日志文件动辄几十个GDocker的 overlay2 目录更是膨胀得不像话。这一套连环问题其实就是Linux磁盘查看和清理命令的基本功。这篇内容我就以实际运维的视角把从“查看到定位再到清理”的完整链路拆开讲一遍重点不是罗列命令而是告诉你每个命令在什么场景下用、输出怎么看、以及删文件之前有哪些坑必须先避开。不管你是刚接手服务器的新人还是自建NAS、跑个人项目的老手这套思路都能直接拿去用。1. 先看清磁盘整体状态df、lsblk 与文件系统全貌1.1 df所有排查的第一步df是最基础的磁盘查看命令但它有几个容易被忽略的细节。很多人只敲df -h看到容量和已用就完事了实际上做运维排查时我更常用的是这三组参数组合df -h # 人类可读的容量视角 df -i # inode使用率视角 df -T # 显示文件系统类型df -h输出里的“已用”“可用”“使用率”大家都会看但我要提醒一句别只盯着/根分区/home、/var、/data这些独立挂载点也要逐行扫一遍。很多服务器在装机时就做了独立分区规划比如/var单独挂了一块盘日志把/var塞满以后系统日志写不进去连登录认证都可能出问题但根分区反而显示正常。这种“局部满了”的情况只扫一眼根分区是发现不了的。df -i看的是 inode。文件系统在格式化时就会固定好 inode 数量每个文件或目录要占用一个 inode。如果磁盘空间还剩几个G但 inode 用完了系统同样会报No space left on device可你df -h一看还有空间非常迷惑。这种问题通常是小文件泛滥导致的比如临时文件脚本疯狂创建小文件、邮件队列堆积或者是某个应用生成了海量的缓存碎片。我曾经处理过一台机器检查下来是/tmp下有个进程每秒创建几十个5字节的小文件把 inode 硬生生耗光了。后面我讲实战环节会再展开这里先记住df -i看 inodedf -h看容量两个都得看。df -T则会告诉你每个分区是什么文件系统xfs、ext4 还是 overlay。为什么这个重要因为不同文件系统有些操作行为不一样。比如 xfs 不支持缩小ext4 支持在线扩容docker 默认的 overlay2 如果跑在 xfs 上那个pquota特性会影响到容器磁盘配额。提前用df -T确认底层文件系统能避免后续踩坑。1.2 lsblk 和 fdisk从系统视角到物理盘视角df看的是“已经挂载的文件系统”但服务器上还有一类情况硬盘明明插着系统里却看不到容量。这时候就得用lsblk看块设备全貌。lsblk lsblk -f # 顺便显示UUID和文件系统类型lsblk输出结构是一棵树顶层是物理磁盘比如 sda、nvme0n1下一层是分区sda1、sda2再往下是挂载点。它最大的价值在于帮你厘清“这块盘到底分没分区、挂没挂载、格式化没有”。运维里有个高频场景新加了一块数据盘fdisk -l能看到但df -h里没有因为还没分区、没格式化、没挂载。这时候lsblk一看就明白卡在哪一步了。fdisk -l是另一个经典查看命令它比lsblk更底层显示物理磁盘的大小、扇区数、分区表类型。有些云主机的数据盘厂商在控制台已经挂载了但你得自己在系统里分区用fdisk操作前先跑一遍fdisk -l确认盘符和路径别搞错盘把系统盘给格式化掉。这块盘符的坑我在后面会单独讲这里先养成习惯动盘之前lsblkfdisk -l双确认。如果你还想看得更细可以再补一个blkid它显示分区的UUID和文件系统类型这个信息在写/etc/fstab做开机自动挂载时非常重要因为用UUID挂载比用设备名/dev/sdb1更可靠设备名在某些情况下会漂移。2. 定位空间去向du、find 和“删除但没有释放”的大坑2.1 du从根目录往下逐层缩小范围df告诉你“哪里满了”du告诉你“什么内容占了空间”。这是两个维度df 是文件系统维度du 是目录维度。经典的排查路径是这样的du -h --max-depth1 / # 看根目录下每个一级子目录占用 du -h --max-depth1 /var # 顺着大的目录继续往下挖--max-depth1的意思是只看指定目录下的第一层子目录这样输出不会爆炸。另一种写法是du -sh /var/* | sort -rh | head -20把/var下所有子目录排个序直接捞出前20个最大的。先用du -h --max-depth1 /会卡几秒甚至几十秒这很正常因为du要递归遍历目录树如果机器上有海量小文件这个命令会跑得特别慢。我的习惯是先看根目录第一层定位到/var或/home这种大户再往下逐层筛不要一上来就du -sh /那会等到怀疑人生。另外一个实用技巧排查时排除掉那些不需要关心的挂载点。比如你只想看根分区的内容但/proc、/sys、/dev这些虚拟文件系统也要跟着遍历慢还不说还容易被误伤。加--exclude就能跳过去du -h --max-depth1 / --exclude/proc --exclude/sys --exclude/dev2.2 find直接捞大文件和各种垃圾文件du按目录统计能定位出哪个目录有问题但具体是哪个文件把空间吃了还得靠find。我常用的两类find用法# 找出根分区下大于200M的文件-xdev 限定不跨文件系统 find / -xdev -type f -size 200M -exec ls -lh {} \; 2/dev/null | awk {print $5, $9} | sort -rh | head -20 # 找出所有 .log 结尾且大于500M的日志文件 find /var -xdev -type f -name *.log -size 500M -exec ls -lh {} \; 2/dev/null-xdev这个参数特别关键它告诉find不要跨越文件系统边界。如果不加find /会把当前机器上所有挂载盘都遍历一遍包括你的数据盘、备份盘。生产环境机器挂载多的时候加了-xdev只会扫当前分区速度和安全都有保障。-size 200M的参数可以灵活调整排查阶段先用大阈值比如 1G捞最大的文件定位到目录后再逐步降低阈值细化。find还有一个场景是清理历史遗留文件比如超过30天没动过的临时文件find /tmp -type f -mtime 30 2/dev/null先查出来看看确认可以删再执行删除操作。很多人喜欢把find和-delete连用一键删除我强烈不建议在生产环境这么搞。先无脑列出结果、人工过一眼再删这个步骤不能省。2.3 lsof处理“文件删了但磁盘空间没释放”这是运维磁盘排查里最经典、也最玄学的一个坑。场景是这样的你用du查了半天发现/var/log下某个日志文件已经不在磁盘上了因为被rm删了但df -h一看空间还是满的。原因很简单某个进程还持有这个已经被删除文件的句柄。在Linux里文件被删除但进程没关闭文件描述符时文件占用的空间不会立即释放只有当进程退出或者关闭该文件描述符那块空间才会真正还给文件系统。排查命令就是 lsoflsof | grep deleted输出里你能看到进程名、PID、以及被删除文件的路径路径后面带(deleted)标记比如java 1234 root 456w REG 253,0 5368709120 123456 /var/log/application.log (deleted)这个例子说明 PID 1234 的 java 进程有一个写向/var/log/application.log的文件描述符但那个文件已经被删了5G多的空间就这么被“锁住”了。解决方式有两种一是重启这个进程前提是业务允许二是更优雅的做法ls -l /proc/PID/fd找到那个描述符用 /proc/PID/fd/N把它清空不一定每次都能成功取决于进程对文件偏移量的处理方式。实际上生产环境里最稳妥的就是重启进程并配合 logrotate 让日志轮转而不是手动删。我见过不少同学在没查明这个情况前就去扩容扩完发现空间还是满的最后才发现是进程占着已删除的文件不松口。所以请记住磁盘满先看有没有 deleted 状态的句柄再决定怎么清理。3. 清理实操从日志到缓存再到 Docker 目录3.1 最优先处理的清理对象日志日志是磁盘空间被吃掉的“头号元凶”尤其是 Java 应用、Nginx、MySQL 这类长时间运行的进程。日志清理有几个层次先看/var/log下的大文件ls -lhS /var/log/ # 按大小排序 du -h --max-depth1 /var/log | sort -rhsystemd 系的系统journald日志也是大户用journalctl查看和清理journalctl --disk-usage # 看journal日志占用 journalctl --vacuum-size200M # 保留200M以内的日志 journalctl --vacuum-time7d # 只保留最近7天这两个参数是清理 journald 最常用的--vacuum-size是按容量控制--vacuum-time按时间控制按需选择。如果你确定 journal 日志完全不需要保留也可以直接journalctl --rotate配合清空但不建议把日志全关掉出问题连排查线索都没有。对于应用自己写的日志文件最安全的方案是truncate清空而不是 rm 删除 /var/log/application.log # 清空但保留文件本身为什么因为很多应用进程启动时就把日志文件的 fd 打开了如果直接rm进程还是会往那个被删除的文件描述符里继续写你会看到文件明明没了磁盘占用却不降——又回到了前面说的 deleted 句柄问题。而清空文件内容、保留文件本身和文件描述符进程可以继续往里面写空间立刻释放双赢。生产环境更规范的做法是配置 logrotate按大小或时间自动切割日志保留一定份数老日志自动压缩甚至删除。这是“把问题消灭在机制里”而不是等磁盘满了再手动救火。3.2 清理包管理器缓存yum、dnf、apt这类包管理器会把下载的 rpm/deb 包缓存到本地日积月累也有好几个G。清理命令# CentOS / RHEL / Rocky (yum) yum clean all # CentOS / RHEL / Rocky (dnf) dnf clean all # Ubuntu / Debian apt clean apt autoremove补充说明一下yum clean all清理的是缓存的包文件和元数据安全可靠放心执行。apt clean同理。另外一个经常被忽略的是apt autoremove它会删掉那些因为依赖关系被自动安装、但现在不再需要的软件包。跑autoremove之前最好apt list --autoremovable看一眼列出来的都是什么虽然它一般不会误删核心组件但谨慎点总是好的。如果是 CentOS 7 这个时代的老机器还能用yum autoremove用法逻辑和 apt 类似。3.3 Docker 目录占用排查与清理装 Docker 的服务器/var/lib/docker经常是空间占用大头。镜像、容器可写层、日志、build cache 都可能堆积。先用docker system df看占用分布docker system df这个命令会显示镜像Images、容器Containers、本地卷Local Volumes、构建缓存Build Cache各自占了多少空间。下面这几个清理命令按需使用docker system prune # 清理停止的容器、悬空镜像、无用的网络和构建缓存 docker system prune -a --volumes # 深度清理删所有未被使用的镜像和卷 docker image prune -a # 清理所有未被任何容器引用的镜像 docker container prune # 清理所有已停止的容器 docker volume prune # 清理未被任何容器使用的卷这里必须给个强烈提醒docker system prune -a --volumes是核弹级别它会干掉的远不止临时文件包括所有没有运行中容器引用的镜像和卷。如果你的卷里有需要保留的数据库文件或应用数据这命令一跑就全没了。生产环境我只推荐两条路要么按docker system df的结果精准清理要么在确认所有重要数据都已备份、或者这些机器就是可以随时重建的测试环境时才用核弹级命令。还有一个 Docker 日志占满磁盘的经典情况某个容器疯狂打日志导致/var/lib/docker/containers/容器ID/*-json.log达到几十G。查看最大日志文件可以用find /var/lib/docker/containers -name *-json.log -size 100M -exec ls -lh {} \;清掉它的思路跟普通日志一样truncate 而不是删文件 /var/lib/docker/containers/容器ID/容器ID-json.log更根本的解法是给 Docker daemon 配置日志轮转在/etc/docker/daemon.json里加{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }改完后重启 Docker 服务才生效这样每个容器日志单文件最多50M、保留3份从源头控制日志暴涨。3.4 旧内核和多余软件包服务器常年不重启的情况下系统更新时会留下很多旧版本内核它们体积不大但数量多了也会占一两G的/boot和/usr/lib/modules。CentOS/RHEL 系查看已安装内核rpm -q kernel清理旧内核最省心的方式是装yum-utils然后yum install -y yum-utils package-cleanup --oldkernels --count2--count2意思是保留最近2个内核版本再多就删。Ubuntu/Debian 系清理旧内核通常就是apt autoremove --purge它会自动处理不再需要的旧内核包。还有一类多余的软件包是装某个服务时被作为依赖带进来的后续不再使用。这类清理没有特别通用的一键方案基本都是用包管理器查、再手动确认。建议不要在排查磁盘的时候顺手乱删软件包空间收益有限风险却很高。3.5 临时文件与 core dump 文件/tmp和/var/tmp是临时文件聚集地很多程序崩溃时也会留下 core dump 文件。Core dump 文件通常体积巨大而且带core字样非常适合用find捞find / -xdev -type f -name core.* -size 100M 2/dev/null有些程序崩溃后生成的 core 文件可能没带进程名只有一堆core.12345这样的数字后缀看到基本都能确认是 dump 文件确认这个进程已经跑不起来、文件没用了可以直接删。顺便提醒一句如果服务器上经常有程序崩溃产生大量 core 文件建议在/etc/security/limits.conf或 systemd 里限制 core 文件大小或者干脆设置ulimit -c 0禁用 core dump避免反复占满磁盘。4. 生产环境避坑指南与实战排查案例4.1 删除前必须养成的四个习惯写这段之前我想先分享几个我见过太多次的“活生生的事故”。磁盘清理最怕的不是找不到文件而是找对了文件、却因为手快删错或者选错了删除方式把服务搞挂甚至把数据丢了。习惯一先查后用把“列出结果”和“执行删除”分开。用find查出来、ls -lh看过大小、再决定删除。千万别搞find / -name *.log -mtime 30 -delete这种一条龙我见过因为路径少写了一个限制条件把某个应用当前正在使用的热日志给清了应用停摆一天的。习惯二优先用 truncate file而不是rm file。这个前面已经强调过主要是应对进程还没释放文件描述符的情况。如果你不确定进程是否还开着这个文件先lsof file查一下再动手。习惯三删除前留意“文件是否正被占用”。比如数据库的 binlog、应用的活动日志、正在写入的临时文件这些文件即使容器还在写、事务还在进行你贸然删掉也会造成数据损坏或程序报错。习惯四删除前做大小和数量预估删完后用df -h验证空间是否真的释放。有些文件删了但空间没释放如果删完不验证你根本发现不了问题还在。4.2 实战案例一inode 耗尽导致服务异常某次接手一台机器现象是应用报No space left on device但df -h显示空间还有30%多。当时我第一反应就是查 inodedf -i结果/分区IUse%已经是 100%。继续用find / -xdev -type f | wc -l探查文件数量又用du -h --max-depth2 / 2/dev/null | sort -rh | head按目录找最后定位到/var/spool/postfix/maildrop下面堆积了上百万个小文件。原因是系统某个脚本把输出用邮件发送但 postfix 发不出去所有邮件小文件全堆在了队列目录里。清理方式是直接清空目录find /var/spool/postfix/maildrop -type f -delete或者干脆rm -rf /var/spool/postfix/maildrop/*然后确认应用恢复正常。之后我还顺手排查了为什么会涌进这么多邮件原来是 crontab 里某个任务忘了把输出重定向每次执行都产生一封邮件。在对应 crontab 行尾加上/dev/null 21问题彻底根治。这个案例的启示是很多“空间满”问题最后都绕不开“小文件过多”这条线而小文件的排查find永远是最趁手的工具。4.3 实战案例二磁盘空间满但 du 看不出大头另一类高频问题是磁盘快满了du在根目录下却找不到明显的大目录。排查思路依次是第一步lsof | grep deleted看有没有进程守着已删除文件不放手如果有处理方式前面已经讲过。第二步查看是否有已经卸载但还在被挂载的“僵尸挂载点”。比如某个网络存储被强制卸载了但还有进程用着或者/dev/shm里被写了很多东西。findmnt和df -h都能看出来。第三步看/proc下有没有超大文件某些进程用临时文件映射了内存du /proc/*/fd能看到实际占用。但这属于很特殊的场景了日常排查到前两步基本就能解决90%的问题。4.4 常见问题与排查速查表这部分我整理成一个速查表平时遇到磁盘异常直接按表查思路异常现象可能原因排查命令处理手段No space left on device但df -h还有空间inode 耗尽df -i、find / -xdev -type f | wc -l清理小文件从源头避免产生海量小文件磁盘满du查不出大目录已删除文件被进程持有lsof | grep deleted重启进程或清空对应 fd/var/lib/docker占用越来越大镜像、容器层、日志堆积docker system df、find /var/lib/docker/containers -name *-json.logdocker image prune、truncate 容器日志、配置 daemon 日志轮转/var/log占用满系统或应用日志过大du -h --max-depth1 /var/log配置 logrotate、truncate 大日志、清理 journald 日志/tmp或/var/tmp占满临时文件堆积、core dumpfind /tmp -type f -mtime 30清理过期临时文件设置 ulimit 限制 core 生成数据库 binlog 膨胀主从复制或备份配置不当ls -lh /var/lib/mysql/*-bin.*确认复制正常后PURGE BINARY LOGS清理历史 binlog4.5 预防体系别等满了才想起来清理最后再说点“治病不如防病”的思路。服务器磁盘清理搞得最狼狈的往往都是因为之前没有任何自动化机制。几个低成本高收益的预防措施一是配置日志轮转。不管是系统自带 logrotate 还是应用自己的日志策略一定要按大小或者天数做切割。一次配置长期省心。二是写个简单的磁盘监控脚本。不用太重crontab 每小时跑一次df -h超过阈值比如80%、90%就通过邮件或者钉钉机器人告警。很多故障只要发现得早处理起来就是几分钟的事。三是定期做清理巡检。每月或每季度跑一遍disk_usage分析把历史遗留的临时文件、旧内核、无用镜像理一遍别让垃圾“攒”着变成事故。四是对核心数据目录必须有明确规划。数据盘单独挂载、日志盘单独挂载避免日志和业务数据争抢同一块磁盘。如果预算允许系统盘和数据盘物理分离这是运维上最踏实的设计。根据我个人的经验磁盘问题虽然看起来是“空间不够”这个单一表象但背后牵扯的可能是日志策略、应用行为、文件系统特性、进程生命周期等多个层面的问题。排查时把命令用熟只是第一步更重要的是建立一套清晰的排查顺序先df看整体、再du定位目录、用find捞大文件、用lsof查已删文件最后再进入清理环节。这套顺序走下来绝大多数磁盘问题都能在十分钟内判断出方向。最后再分享一个小技巧每次清理完磁盘把当时的df -h、du结论、用过的命令和删掉的文件大小记到自己的运维笔记里。下次再遇到类似问题翻记录会比重新查一遍快得多。毕竟运维工作里能复用的不只是命令本身还有上一次排查时积累的判断经验。