
值班手机在半夜响起来那种感觉相信干过运维的都懂监控面板上根分区使用率已经顶到 100%网站页面打不开日志写不进去SSH 连上去敲个df -h都卡半天。这种时刻最怕的不是问题本身而是面对一个塞满的根分区不知道该从哪里下手。这篇文章就是针对这个场景的实战总结。我会从判断是不是真的满了开始一路讲到用du逐层揪出大目录、用lsof找出删了但还被进程占用的隐形文件、再清理日志和容器镜像这类常见的空间大户。整套思路覆盖 Linux 下排查磁盘空间问题最核心的命令和手法不管你是刚接触 Linux 的新手还是被线上事故折磨过的运维老手这套排查路径都能直接照搬着用。1. 先别急着删文件用 df 判断空间与 inode 状态排查的第一步不是冲进去删东西而是先搞清楚满到底满在哪里。根分区爆满的原因往往不是单一维度的空间满了是一种情况文件数量达到上限是另一种情况两者症状相似但处理思路完全不同。1.1 df -h 看空间使用率先确认是不是真的满了df是最基础的磁盘空间查看命令全称 disk free。加-h参数是以人类易读的格式输出单位会自动换算成 G 或 M。我习惯先执行这一条df -h输出大概是这样的Filesystem Size Used Avail Use% Mounted on /dev/vda1 50G 50G 20K 100% / tmpfs 3.9G 0 3.9G 0% /dev/shm /dev/vdb1 200G 80G 120G 40% /data面板上那个报错没有骗你根分区确实满了。注意看 Mounted on 这一列只要挂载点是/就代表这是根分区。如果/显示 100% 而其他挂载点还有大量空闲说明只是根分区这一块盘的空间被耗尽不是整台机器的磁盘都满了。有人会问为什么机器里还有别的盘系统却偏偏因为根分区满了而罢工这是因为 Linux 的目录是树状结构根分区承载了/etc、/usr、/var、/tmp这些系统运行必需的关键路径。就算/data再空进程的临时文件要写到/tmp、日志要写到/var/log只要根分区满了所有涉及这些路径的写入操作都会报No space left on device。1.2 df -i 看 inode文件数耗尽也会报空间不足另一个容易被忽略的场景是 inode 耗尽。inode 是用来存储文件元数据权限、所有者、大小、指向数据块的指针等的信息节点。格式化磁盘时系统会划分出固定数量的 inode也就是说整个文件系统能容纳的文件总数量在格式化那一刻就已经定死了。如果目录里塞满了海量小文件inode 会被消耗殆尽。这时候执行df -h看到的可能还有剩余空间但实际写入文件却一直报No space left on device。所以排查的时候df -i必须和df -h一起看df -i输出示例Filesystem Inodes IUsed IFree IUse% Mounted on /dev/vda1 3276800 3276800 0 100% / tmpfs 15291 14 15277 1% /dev/shmIUse% 显示 100%说明 inode 已经耗尽。这种情况光删大文件没用得找到那个塞满海量小文件的目录把无用的小文件批量清掉inode 数量才会降下来。典型的场景包括邮件队列、PHP 会话文件目录、/tmp下程序异常产生的大量临时文件、还有某些没有定期清理的缓存目录。所以拿到根分区爆满这个现象先跑这两个命令把问题定性现象df -hdf -i结论空间满100%有剩余空间被大文件或目录占用inode 满有剩余100%文件数量太多小文件泛滥两者都满100%100%大小文件都有问题需分别处理这一步定性非常关键方向错了后面全是白忙活。我见过有同事对着一个塞满 5 万个小文件的目录疯狂找大文件找了半天一无所获回头一看df -i才发现问题根本不在这。先把敌人定位准确再动手收拾它。2. 用 du 逐层下钻定位真正的空间大户确认是空间被大文件占满之后下一步就是找出这些大文件在哪里。du是 disk usage 的缩写用来统计目录或文件占用的磁盘空间。实际排查时我不会一开始就在根目录上跑du -sh *然后干等因为根分区本来空间就紧张全盘扫描会带来额外的磁盘 IO还可能把系统拖得更卡。更稳的做法是一层一层往下钻。2.1 du 的常用参数与磁盘占用的统计逻辑先看几个最实用的参数# 查看指定目录的总大小 du -sh /var/log # 列举某目录下每个子目录和文件的大小只看一层 du -h --max-depth1 /var | sort -hr | head -20 # 限定在同一个文件系统内统计不跨越挂载点 du -x -h --max-depth1 /参数拆开解释一下-ssummarize只显示总计不列出子目录明细。-hhuman-readable输出单位自动换算为 K/M/G。--max-depthN向下统计 N 层目录。--max-depth1就是只统计当前目录下第一级子目录各自的大小不会无限递归进去速度会快很多。-xone-file-system不跨文件系统。这个参数很重要后面单独说。sort -hr按人类可读的大小反向排序从大到小排列一眼就能看到头部的大户。2.2 从根目录开始的四层下钻实战我自己总结了一套固定的下钻路径基本能覆盖 90% 的排查场景。先执行du -x -h --max-depth1 / 2/dev/null | sort -hr | head -10输出类似24G /var 18G /usr 3.2G /home结果很干净问题集中在/var。继续往下钻du -x -h --max-depth1 /var 2/dev/null | sort -hr | head -10如果看到/var/log占了 20G那基本就是日志文件失控了。再钻一层du -h --max-depth1 /var/log 2/dev/null | sort -hr | head -10到这里就能精确到具体的日志文件名比如syslog 8G、kern.log 5G、nginx/access.log 6G这种。找到祸首之后是清空还是保留最近多少行就看业务需求了。2.3 用 find 直接定位超大单个文件有时候 du 只显示了目录占用大但具体是哪个文件最占地方不够直观。这时可以用find按文件大小直接搜索# 找出根分区下所有大于 1G 的文件 find / -xdev -type f -size 1G -exec ls -lh {} \; 2/dev/null | sort -k5 -hr参数说明-xdev不跨文件系统避免把/data、/proc等目录也扫进来防止误判和干扰。-type f只找普通文件。-size 1G文件大小大于 1G。-exec ls -lh {} \;对每个结果执行 ls列出详细大小信息。这条命令在根分区上跑一遍等于把最显眼的大块头全部登记在案。实际使用中大文件多集中在这些地方应用日志、数据库 binlog、打包好的备份文件.tar.gz、.zip、镜像文件、以及 core dump 文件。看到可疑文件先别急着删用ls -lh确认修改时间、用file看文件类型判断是否还有业务在引用再决定处置方式。提示du 在统计目录时如果遇到没权限访问的子目录会有大量报错刷屏。加上2/dev/null把错误输出丢掉既能让结果更干净也不影响排查主流程。3. 空间明明删了却没释放lsof 帮你揪出隐形文件du和find只能统计常规文件但有一种非常阴间的场景它们查不到文件已经被rm删除了空间却一点都没释放。磁盘使用率还是 100%可翻遍了整个文件系统都找不到那个占空间的文件。这种问题只能用lsof来处理。3.1 文件被删除但空间不释放的原理Linux 文件删除的本质是解除目录项与 inode 的硬链接关系。当一个文件的链接数为 0但没有进程再持有它的文件描述符fd时inode 才会被真正释放对应的磁盘块才会被回收。问题就出在没有进程持有这个条件上。如果一个正在运行的进程打开了某个文件比如访问日志随后你用rm把它删掉了此时目录项没了但进程的 fd 仍然指向这个 inode。只要进程不退出、不关闭这个 fdinode 就不会被释放它占用的磁盘空间会一直挂着成为一套隐形占位。你用du无论怎么扫描都找不到它的影子因为目录里已经没有它的名字了。3.2 用 lsof L1 找出已被删除但仍被占用的文件lsof是 list open files 的缩写可以列出当前系统上所有被进程打开的文件。排查这种已删除未释放的场景用得最多的是下面这条lsof L1L1的含义是列出 link count链接数小于 1 的文件也就是说这些文件在文件系统里已经没有对应的目录项了但它们仍然被某些进程打开着。输出大概是这样的COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME nginx 1234 root 2w REG 253,0 8589934592 0 12345 /var/log/nginx/access.log (deleted) java 5678 root 34w REG 253,0 4294967296 0 56789 /tmp/hsperfdata_root/xxx (deleted)SIZE/OFF 列显示的是文件当前偏移量对于日志文件来说基本就等于文件已经写入的总字节数。8G、4G 这个量级一看就知道空间就是被它们吃掉的。如果嫌lsof输出太多太杂也可以精确到某个分区上的文件。找到根分区对应的设备名后用df -h那个输出里的 Filesystem 列的值来过滤lsof /dev/vda1 | grep deleted还有一种更底层的查法直接看进程的文件描述符指向ls -l /proc/5678/fd/ 2/dev/null | grep deleted/proc/pid/fd/下面会以符号链接的形式列出该进程打开的所有 fd指向的文件名后面带着(deleted)标记的就是隐形占位文件。3.3 彻底释放空间的两种处置方法找到占用文件的进程之后可选的处理手段就两条第一条重启占用进程或者直接 kill 掉进程。nginx 这类服务在重启或reload之后会重新打开日志文件旧 fd 被释放空间自然就回收了。数据库、Java 应用如果是低频维护的可以把进程重启一下。但生产环境里面 kill 进程属于高风险操作先确认是什么业务、能不能中断再动手。第二条既然文件已经被删除了目录项都不用管直接把进程持有的那个 fd 对应的文件清空就行。用truncate把文件收缩到 0truncate -s 0 /proc/5678/fd/2注意这里操作的是/proc下的 fd 路径不是原来的文件路径因为原来的目录项已经没了。truncate -s 0会将文件大小截断为 0进程继续往里面写也不会再占磁盘空间。这个操作的妙处在于不需要重启进程适合那种不能随意中断的服务。注意删日志文件并不能节省空间这是运维新人最容易踩的坑。正确姿势是truncate而不是rm否则文件会变成deleted状态继续占用空间直到进程重启。我在实际工作中见过一个线上事故同事rm了一个 20G 的日志文件以为解决了告警结果磁盘使用率纹丝不动排查了半天才发现是没重启 nginx 导致的。4. 按图索骥日志、缓存、容器镜像这些常见大户怎么清理定位到具体文件是技术活不过很多时候我们不需要一步步查直接去几个重灾区转转就能解决问题。根分区爆满的案例里日志、包管理缓存、Docker 镜像这三块占了绝大多数比例。4.1 journald 日志与 /var/log 目录的系统日志清理systemd 体系下/var/log/journal是系统日志的核心目录。journald 默认的日志存储可能会让你意外它没有对保留量做严格的限制长期不清理几十 G 的日志占满根分区真的不稀奇。先看当前日志占用量journalctl --disk-usage输出示例Archived and active journals take up 8.2G in the file system.把日志体积压到指定大小journalctl --vacuum-size500M--vacuum-size会清理旧日志直到剩余日志总量不超过 500M。这条命令非常实用处理完瞬时就能释放几个 G 的空间。对于/var/log下的传统日志文件比如syslog、kern.log、auth.log如果你确定这些日志不再需要留档可以直接清空truncate -s 0 /var/log/syslog /var/log/kern.log /var/log/auth.log这里的思路依然是truncate而不是rm如果某个进程正在写这些日志rm后虽然目录项没了但 fd 不释放空间还是照样占着。更好的长期方案是配置 logrotate 定期轮转。Linux 发行版自带的 logrotate 默认会压缩和轮转一批系统日志但一些第三方应用写入的日志它管不到。可以像这样自定义一份配置cat /etc/logrotate.d/nginx-custom EOF /var/log/nginx/*.log { daily rotate 7 compress delaycompress missingok notifempty create 0644 nginx nginx sharedscripts postrotate [ -f /var/run/nginx.pid ] kill -USR1 cat /var/run/nginx.pid endscript } EOF这份配置的意思是日志每天轮转一次保留最近 7 份旧日志压缩成.gz保存轮转后向 nginx 主进程发送USR1信号让它重新打开日志文件。配置好之后日志无限膨胀的问题就算根治了。4.2 包管理缓存与其他临时目录的清理Debian/Ubuntu 系的机器上/var/cache/apt/archives会缓存所有下载过的.deb安装包。跑过几次系统更新之后这里的体积能轻松超过 1G。清理方式很简单apt-get cleanRHEL/CentOS 系则是yum clean all/tmp目录也是根分区爆满的一个常见源头。很多程序会在这里写临时文件异常退出后临时文件没人清理日积月累就堆起来了。清的时候注意别把正在使用的临时文件干掉比较稳的做法是只清理超过一定天数且没有被进程占用的文件find /tmp -xdev -type f -mtime 7 -delete-mtime 7表示修改时间在 7 天以前-delete直接删除匹配的文件。这个思路比rm -rf /tmp/*安全得多能避开正在被使用的临时文件。4.3 Docker 容器与镜像的瘦身清理容器化环境中Docker 相关目录默认在/var/lib/docker是根分区爆满的头号嫌疑犯。镜像层、容器可写层、构建缓存、悬空镜像哪一个膨胀起来都够喝一壶的。先看整体占用情况docker system df输出示例TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 5 15.2GB 8.1GB (53%) Containers 8 3 1.5GB 1.2GB (80%) Local Volumes 4 2 2.3GB 1.8GB (78%) Build Cache 23 0 3.6GB 3.6GBRECLAIMABLE 这一列写的都是能回收的空间。清理动作分两步# 清理悬空镜像和已停止的容器 docker system prune -f # 清理所有未被容器使用的镜像和构建缓存 docker system prune -a -f --volumesprune -a会把所有没有被容器引用的镜像全部删掉--volumes是清理没有被容器使用的匿名卷。这条命令杀伤力很强生产环境一定要先确认没有你还需要但暂时没用起来的镜像再执行。4.4 core dump 文件的识别与清理还有一种容易忽略的大块头是 core dump 文件。程序崩溃时内核会把进程的内存映像转储到文件里默认位置在工作目录或者/var/lib/systemd/coredump文件名类似于core.1234或者core.程序名.进程号。一个包含大量内存数据的 core 文件轻松就是几个 G。先搜索find / -xdev -name core.* -type f 2/dev/null | head -20确认是无用的崩溃转储后删除find / -xdev -name core.* -type f -delete 2/dev/null/var/lib/systemd/coredump下的 core 文件还可以用coredumpctl管理coredumpctl list查看有哪些转储coredumpctl clear清空全部。这个目录在很多环境里被长期忽略值得在排查列表上占一个坑位。5. 一条清晰的应急排查链路从告警到恢复的全流程上面几节讲的是各个独立的技术点实际告警来的时候还需要一条线性流程把它们串起来保证从接到报警到恢复业务不超过十五分钟。我把这套流程固定成了自己的排查 SOP每次都是照着这个顺序走。5.1 六步定位法把排查时间压缩到最短第一步确认现状。执行df -h和df -i判断是空间满了还是 inode 满了还是两者都满。第二步快速扫描大文件。执行find / -xdev -type f -size 1G -exec ls -lh {} \; 2/dev/null | sort -k5 -hr用现有大文件列表建立一个初步认知。第三步发现大文件后顺着路径找到它所在的目录用du -x -h --max-depth1从根目录开始逐层下钻确认整个目录树的占用分布避免只抓一个文件漏掉其他隐患。第四步执行lsof L1 | grep deleted找出已删除但未释放的隐形文件处理这种问题必须靠它。第五步针对日志和缓存类的大户做集中清理比如journalctl --vacuum-size500M、docker system prune -f、apt-get clean这些。第六步清理完成后再次执行df -h和df -i确认使用率降下来了。同时检查关键服务日志确认没有因为磁盘操作造成新的损失。这个顺序的优先级逻辑是先判断故障类型再找出最明显的空间占用然后处理需要特殊手段的隐形占用最后集中清理常见大户并验证结果。5.2 紧急腾挪空间的几个保命操作如果空间已经满到服务都起不来了连正常的定位都有点吃力可以先用最快的速度腾出一点空间让系统喘口气再慢慢排查。下面这几个操作是按风险从低到高排列的# 1. 清空系统日志风险极低但会让旧日志丢失 truncate -s 0 /var/log/syslog /var/log/kern.log /var/log/messages 2/dev/null # 2. 压缩 journal 日志 journalctl --vacuum-size200M # 3. 清理 apt/yum 缓存 apt-get clean || yum clean all # 4. 清理 Docker 悬空资源 docker system prune -f # 5. 查找并处理超大的 core dump 文件 find / -xdev -name core.* -type f -delete 2/dev/null这套组合拳执行完通常情况下能瞬间回收 2~10G 空间。有了余量之后再去慢慢排查真凶就从容多了。提醒Truncate 系统日志是一种急救手段操作前最好确认这些日志有没有外部采集系统在同步。如果日志需要留存建议优先用journalctl --vacuum-size和 logrotate 这类可控的方式不要动不动就一把清空。5.3 生产环境操作时的三个自我检查问题在生产环境操作之前我习惯先问自己三个问题这个文件/目录删掉之后会不会有进程还往里面写如果答案是可能就用truncate而不是rm。这个是最关键的一个习惯能避免越删空间越满的诡异现象。这个操作会不会影响正在运行的服务比如docker system prune -a之前必须确认所有需要的镜像都有人引用否则重新拉镜像是要花时间的。操作之前有没有记录现场执行任何清理前用date理论上的时间戳记录一下输出必要时截图或保存命令执行的日志。万一后续排查需要复盘这些记录就是线索。这五个字定位、确认、记录、清理、验证。整套流程走下来根分区爆满的告警基本都能在十几分钟内按下去。6. 常见问题速查表与几条保命避坑心得最后把这几年现场踩过的坑集中整理一下以速查表和心得的形式放在这里方便你直接抄作业。6.1 根分区爆满的典型问题速查表故障现象可能原因排查命令处理建议df -h显示 100%df -i正常大文件或大目录占用du -x -h --max-depth1 /定位大文件后按需 truncate 或移动df -h正常df -i显示 100%inode 耗尽小文件过多find / -xdev -printf %h\n | sort | uniq -c | sort -rn | head清理邮件队列、临时目录、缓存目录删了文件空间没释放文件被进程持有lsof L1重启进程或truncate /proc/pid/fd/n/var/log/journal无限膨胀journald 未限制保留量journalctl --disk-usagejournalctl --vacuum-size500M容器镜像、构建缓存占满Docker 积累大量悬空资源docker system dfdocker system prune -a -f/tmp目录堆积程序异常退出留下临时文件du -sh /tmpfind /tmp -xdev -type f -mtime 7 -delete/proc/kcore显示超大虚拟文件表示系统物理内存不需要排查绝对不能删除根分区是 LVM 但扩容失败卷组无剩余空间vgs、lvs扩容 PV 或清理后重新lvresize6.2 几条价值抵得上一整天排查的避坑心得du和df的统计结果经常对不上这不是bug而是统计口径不同。du是从目录树角度累加文件大小它看不到已被删除但被进程占用的空间df是从文件系统块分配角度统计总空间使用情况。两者对上不齐的时候优先怀疑lsof列出的隐藏文件。带快照的文件系统会越清越满。如果你用的是 LVM 快照或者某些支持快照的备份软件删除文件后快照层可能会保留旧数据导致底层空间不见减少。这种情况要在快照管理层面去合并或删除快照光删文件是无效的。/proc、/sys、/dev这几个虚拟文件系统下的文件不要随便动。看到/proc/kcore大小等于物理内存也别慌那个是虚拟文件文件大小没有实际意义更不能去删它。日志轮转配置要赶在事故之前做。事故发生时再配置 logrotate 虽然能亡羊补牢但维护一套合理的日志保留策略比每次磁盘告警后手动清理要省心太多了。建议给所有写日志的应用都配上轮转策略并且让监控系统把日志目录的使用率纳入告警范围。清理大文件之前养成ls -lh和file检查的习惯。看一眼修改时间确认是不是正在写入的热文件用file确认文件类型避免误删数据库文件或者正在使用的备份。这个习惯一旦养成能避免大部分因清理磁盘引发的次生事故。再分享最后一个小心得应急处理之后一定要花点时间找出为什么根分区会长这么快。如果只是删掉文件当无事发生过半个月告警还会再来。把日志轮转配好、把监控阈值设好、把定时清理任务加上这才算把这类问题彻底地解决。磁盘空间的管理其实更多是一个长期维护的问题等到爆满再处理永远是被动的。