ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

磁盘满了怎么办?Linux运维排查与清理实战指南

磁盘满了怎么办?Linux运维排查与清理实战指南 1. 从磁盘告警说起先搞清楚到底是谁满了做运维的谁没被“磁盘满了”这条告警深夜叫起来过我在生产环境摸爬滚打这些年最怕的不是服务宕机而是No space left on device——因为服务挂了你能立刻重启磁盘满了却经常让人抓瞎明明感觉没放什么大文件df一看就是100%更诡异的是连df -h都有可能会卡住。先说个最常见的误解。很多人拿到服务器第一反应是du -sh *挨个目录扫然后一层层往下钻。这个思路没错但在生产环境里这是最费时间的做法尤其当数据盘上文件特别多的时候du全盘扫描走一遍可能要好几分钟甚至十几分钟。正确顺序应该是先用df判断是哪个分区满了、满了多少再用du精确定位大目录最后细化到具体文件。先打靶再瞄准而不是上来就满地图乱扫。排查的第一条命令永远是这三件套组合df -h df -i lsblkdf -h看空间使用率df -i看inode使用率lsblk看磁盘分区布局。这里有个很多人忽略的细节df -h只能看到已经挂载的文件系统如果某个磁盘空间满是因为inode耗尽df -h会显示还有一大半空间但服务器照样报“写不进去”。所以df -i必须和df -h一起看两个都100%才是真的彻底没救了只有其中一个爆掉则有完全不同的处理思路。打个比方df -h相当于看仓库里还有多少货架空间df -i则是看仓库里还有多少个空箱子。货架满了可以堆高货架箱子没了连贴标签的位置都不够。仓库再大没有箱子货物依然进不来。inode就是文件系统给每个文件分配的“身份证号”一个文件占一个inode哪怕这个文件只有1KB。如果你的服务器上堆积了大量的小文件比如消息队列的消费日志、缓存碎片就很容易出现“inode先爆”的情况。提示在排查之前先想清楚这是哪种“满”。如果你看到的是No space left on device但df -h还有空间99%的可能是inode耗尽先别去删大文件要去查小文件数量。从我的经验来说排查的第一步先看df -h的输出确认到底是哪个挂载点出问题因为你服务器上可能同时挂了云盘、系统盘、数据盘好几块某一块满了不代表全都满了。很多人在这一步就被带偏明明/home满了他跑去清理/tmp结果白忙活半天。# 实际观察 df 输出时重点关注 Mounted on 列和 Use% 列 Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 40G 12K 100% / /dev/vdb1 500G 200G 300G 40% /data这个格局一目了然根分区满了数据盘还很健康。接下来的清理重点放在根分区上别动/data里的任何东西。2. 用 du 精准定位大文件别满盘扫描要分级下钻确认是哪个分区满了之后就该轮到du登场了。不过我这里要先说一个关于du的冷知识du -sh *看起来很傻瓜式但在文件数特别多的目录里执行时光列目录项就要耗费大量时间。更好的做法是先扫一级目录找到最肥的那个再进入二级目录继续扫像吃蛋糕一样一层层切。生产环境我一般这么干cd / du -h --max-depth1 -x 2/dev/null | sort -rh | head -20参数拆解一下--max-depth1只显示当前目录下一级目录的大小不递归到底速度快很多。-x这个参数是关键它让du停留在当前文件系统内不去跨越挂载点统计。什么意思呢如果你的/data是单独一块云盘挂在/下的子目录里不加-xdu会把/data的大小也算进根分区目录里导致你误以为根分区下有超大目录。sort -rh对结果按人类可读格式反过来排序最大的在最前面。扫出来的结果可能是这样的16G /usr 9.8G /var 5.2G /opt 2.1G /home 1.8G /root看到/usr有16G很多人第一反应是“系统文件怎么会这么大”。这里要提醒一句du统计的是目录树里的所有文件包括被删除但还占着空间的“僵尸文件”后面细说也包括 Docker 的镜像目录在/var/lib/docker下。所以见到/usr或/var出奇地大不要惊慌继续往下钻。继续一级级深入du -h --max-depth1 /var 2/dev/null | sort -rh | head du -h --max-depth1 /var/log 2/dev/null | sort -rh | head真正找到大头之后再直接用ls -lhS看具体文件大小ls -lhS /var/log/messages ls -lhS /var/log/nginx/access.log这里有个非常实用的小经验ls -lhS的S是按大小排序比ls -lht按时间排序好用得多因为你要找的是“大”文件不是“新”文件。我们排查时经常犯的错误是ls -lht看到一个大文件更新时间很新就误以为它是问题源头其实可能只是某个服务在持续写它而已。还有一个find大招用来扫超大的单个文件find / -xdev -type f -size 1G -exec ls -lh {} \; 2/dev/null这个命令会把全盘大于1GB的文件全部列出来-xdev同样是为了不跨文件系统。不过这个命令也会比较慢建议先按目录分级扫描缩小范围之后再find效率更高。3. 磁盘爆满的头号黑手日志文件与 Docker 容器根据我接触过的服务器故障案例磁盘爆满的原因虽然有千百种但真正出现频率最高的是这两类日志文件无限增长和 Docker 容器日志堆积。这两类占据了至少80%的线上事故场景。3.1 系统日志与应用日志日志分两类一类是系统自带的服务日志比如/var/log/messages、/var/log/syslog、/var/log/secure一类是应用日志比如 Nginx 的access.log、Tomcat 的catalina.out、Java 应用的log4j输出文件。系统日志出问题的典型场景是某个服务疯狂刷报错比如网络不通导致连接重试、某个应用进入死循环疯狂打日志。我遇到过最夸张的一次/var/log/messages在一天内涨了30GB原因是某个内网服务一直在尝试连接一个不通的数据库地址每秒钟重试三次每次重试打印一行完整堆栈。日志文件从根上不设置归档策略那么它就会毫无节制地长大。应用日志这边Nginx 的access.log是最经典的增长大户。很多团队对访问日志不做切分访问量一大一晚上下来日志文件能涨几个GB。还有 Java 应用如果通过System.out.println打日志误配置到catalina.out那个文件涨起来的速度堪称恐怖。清理日志我强烈推荐用截断而不是rm# 直接把文件清空而不是删掉重建 cat /dev/null /var/log/messages # 或者 truncate -s 0 /var/log/messages这样做的好处是文件句柄不断开。很多进程一直在往日志文件里写数据它打开的文件句柄指向的是磁盘上的 inode你rm掉文件后句柄不会自动关闭进程继续往那个已经被删除的文件里写数据空间完全不会释放甚至你删掉了文件但磁盘使用率纹丝不动。等你重启进程空间才“突然”回来。这就是所谓的“文件已删但空间未释放”的诡异现象。注意truncate -s 0这个命令对正在被写入的日志文件安全吗从实际使用来看大部分情况下是安全的因为它只是把文件内容清零inode 不变文件描述符不受影响。但要注意个别应用对文件偏移量的处理比较特殊截断之后可能出现“空洞文件”也就是文件系统上显示文件很大但实际占用的数据块不多。遇到这种情况不用担心写入方继续追加即可。不过如果是 Java 的log4j这类框架更稳妥的方式是走它自带的RollingFileAppender配置来做日志轮转。3.2 Docker 容器日志与镜像/容器残留现在服务器上跑 Docker 已经很普遍了但很多人对 Docker 的磁盘占用缺乏概念。Docker 的日志默认是 JSON 文件格式存储在/var/lib/docker/containers/容器ID/容器ID-json.log如果不做限制一个容器能吃掉整个磁盘。我遇到过最离谱的一次一个 Java 容器因为没有配置日志轮转一天内堆了40GB的 JSON 日志。当时df -h看根分区100%du扫/var/lib/docker/containers发现有一个目录占了39GB点进去一看就是那个-json.log。Docker 容器日志的即时清理办法# 方法一直接清空当前容器日志文件 truncate -s 0 /var/lib/docker/containers/$(docker ps -q | head -1)/*-json.log # 更推荐的方式在 docker run 时限制日志大小 docker run \ --log-driver json-file \ --log-opt max-size100m \ --log-opt max-file3 \ your-image如果你的容器已经跑起来了用docker inspect查看当前日志驱动和参数docker inspect --format {{.HostConfig.LogConfig}} 容器名另一个大块头是 Docker 镜像和悬空层。docker system df命令能一眼看明白docker system df这个命令会列出镜像、容器、本地卷、构建缓存四类的占用情况。如果Build Cache那一块特别大或者Dangling Images悬空镜像很多清理方式如下docker image prune -a docker system prune -adocker system prune -a会把所有未被运行中容器使用的镜像和数据卷缓存都清掉。但有个坑要提醒这个命令会删除所有停止的容器如果你只是想清镜像不想删其他东西用docker image prune -a更精准。在大型生产环境里我通常还会先执行docker ps -a看一眼有没有重要的已停止容器确认无误后再跑清理命令。还有一个经常被忽略的 Docker 问题容器内删了文件但宿主机上空间没释放。这就是前面提到的文件句柄问题容器里的进程比如Java进程一直持有已删除文件的句柄导致这部分空间在容器内表现为“已释放”在宿主机上却依然被占用。处理方式只能是重启容器或重启容器内对应的进程。这也算运维圈的老大难问题排查时如果发现容器磁盘占用异常高而docker exec进去看du又找不到大文件基本就是这种情况。3.3 其他高频占位来源除了日志和 Docker还有几个方向值得排查。系统更新残留包。/var/cache/apt/archives下可能堆积了大量已下载但未清理的软件包或者旧内核文件堆积在/boot下。这类文件可以通过以下命令清理# 清理 apt 缓存 apt-get clean # 删除旧内核谨慎操作保留当前版本即可如果有的话 dpkg --list | grep linux-imageCore dump 文件。Java 应用崩溃时产生的hs_err_pid*.logC/C 程序崩溃产生的core.*文件这些动辄几个GB而且经常散落在程序的工作目录下。查找方法find / -xdev -name core.* -type f -size 500M 2/dev/nulltmp 目录下的临时文件。很多程序会在/tmp下创建临时文件并遗忘如果服务长期运行这个目录也会悄悄膨胀。清理时要小心不能全删有些程序还在写。一般建议清理超过7天的文件find /tmp -type f -atime 7 -delete4. 更隐蔽的元凶inode 耗尽与“已删除未释放”文件前面提过磁盘爆满可能是空间满了也可能是 inode 满了。inode 这关我单独拿出来讲是因为它的排查方式和空间排查完全不同很多新手遇到写不进文件时只盯着df -h看看了半天发现空间还有很多就完全失去方向。inode 耗尽的典型场景是邮件系统队列堆积、消息队列的小文件堆积、缓存系统碎片文件过多。排查命令df -i输出里IUsed%超过90%就要警惕了到100%就完全写不进新文件了连创建临时文件都不行。注意这里的细节df -i显示的是文件系统级别的 inode不是目录级别的所以你也需要按前面的方式逐层用find找出文件数最多的目录。找出哪个目录下文件数最多用这个命令统计find / -xdev -type f 2/dev/null | awk -F / {print $2} | sort | uniq -c | sort -rn | head这个统计手法比较粗糙精准定位目录内文件数量可以用for d in /data/*; do echo $d: $(find $d -type f 2/dev/null | wc -l); done | sort -t: -k2 -rn | head一旦确认是 inode 耗尽处理思路就是“清小文件”跟“清大文件”是两个方向。大文件好办删一个几十GB的日志就行小文件则可能几十万个才占几百MB空间删除时要注意使用find -delete或rsync方式批量删除。我自己的教训是不要用rm -rf一次性删几十万个文件因为这样会一下子扛住磁盘 I/O把整台服务器的读写拖垮生产环境还是要限速操作比如分批删、加sleep中断。另一个隐蔽场景进程持有已删除文件句柄。前面提到了 Docker 容器的类似问题这在宿主机进程里一样存在。特别常见于日志文件被运维删了但写日志的服务还在运行。此时文件已经不在目录里但空间没释放。排查方法lsof L1L1参数的意思是只列出“link count为0”的文件也就是已经被删除但依然被进程打开的文件。输出会显示进程名、进程ID和文件大小比如java 12345 root 20w REG 8,1 209715200 1234556 /tmp/log/catalina.out (deleted)看到 209715200约200MB这个数就知道这个被删除的文件还在占着200MB空间。处理办法只能是重启对应进程或者让进程重新打开一个新的日志文件。有些场景下你可以用/proc/pid/fd/文件描述符去查看具体是什么文件比如ls -lh /proc/12345/fd/20这会显示一个指向已删除文件的符号链接ls -l能看到明显的 “(deleted)” 标记。这个技巧在做完清理后发现磁盘空间没降下来时特别有用算是运维老手才常用的手段。5. 实操清理预案从确认到动手的完整流程前面讲的都是单点排查这一节我给出一套我一般在生产环境里实际执行的完整流程按顺序走下来多数磁盘爆满问题能在半小时内解决。第一步先评估业务影响范围登录服务器后先别急着删东西先看一下现在是什么状态。执行uptime看负载ps aux看关键进程是否存在dmesg | tail看有没有因磁盘满导致的服务报错。在确认数据库、核心业务进程还在跑的前提下再往下排查。如果磁盘满到连命令都无法执行这种情况出现在根分区100%时某些基础命令会因无法写临时文件而失败优先考虑重启部分服务释放句柄或者清理/tmp下最老的一批文件腾出一点空间再逐步排查。第二步轻量空间释放在找到大头之前可以先做一轮安全的“顺手清理”。这几个操作风险极低# 清理 apt/yum 缓存 yum clean all # 清理临时文件 find /tmp -type f -mtime 3 -delete # 清空已无用的邮件队列如果有 postfix 之类的服务这一步的目标不是解决根本问题而是先给自己腾出一点余量让后续的du、find等命令有足够的空间跑起来。磁盘空间低于10%时一些服务可能因为无法写入临时文件而产生连锁故障先把余量弄到10%以上是保命操作。第三步逐级定位大文件按照第二部分的思路用du --max-depth1逐级下钻分别检查/、/var、/var/log、/home、/opt、/usr等典型目录。在 docker 环境下优先检查/var/lib/docker相关的目录。同时执行lsof L1找出已删除但未释放的句柄。第四步确认删除对象谨慎执行所有要删的文件先确认三件事这是哪个业务的文件这个文件是否被进程占用这个业务团队是否还有需要备份的数据确认完再动手。在生产环境我习惯的做法是先做一轮“假删除”就是把要删的文件mv到一个临时目录里而不是直接rm。比如mv /var/log/nginx/access.log /tmp/backup_access.log.$(date %F) mv /data/logs/big.log /tmp/观察几个小时或者一天如果业务正常、没人报缺文件再真正删掉临时目录里的文件。这个习惯帮我挡掉了不少“手滑误删”的雷。尤其是日志文件很多公司的合规审计要求保留日志直接rm很容易出事移动到存档目录等确认后再删除既给业务方留了反悔时间也给审计留了余地。第五步一次到位建立长效机制临时的空间清理只是止血不建立轮转机制就是白折腾。日志轮转是一个绕不开的配置项。如果是系统日志用系统自带的logrotate配置如果是应用日志优先走应用自己的日志轮转配置如果是 Docker 容器日志在docker run或docker-compose.yml里加logging配置。以 Nginx 为例最简单的logrotate配置/var/log/nginx/*.log { daily rotate 7 missingok notifempty compress delaycompress sharedscripts postrotate [ -f /var/run/nginx.pid ] kill -USR1 cat /var/run/nginx.pid endscript }这段配置的含义是日志每天轮转一次保留最近7天的日志文件旧的日志用 gzip 压缩轮转之后向 Nginx 发送USR1信号让它重新打开日志文件。关键点在postrotate里的kill -USR1如果少了这一步Nginx 会一直往已经被重命名的旧日志文件里写轮转就白做了。对于 Java Tomcat可以在catalina.out上使用logrotate也可能直接在 log4j2 配置里设置RollingFile核心思路都一样按大小或按时间切割。6. 常见问题速查与我的实战经验笔记最后把最容易踩的坑和常见的排查问题集中列出来方便你遇到具体场景时快速对照。症状可能原因快速验证解决方案df -h显示满但du找不到大文件文件被删除但进程未释放句柄lsof L1重启对应进程或kill后重启服务df -h显示有空间但写文件报No space leftinode 耗尽df -i清理小文件重点查消息队列/缓存目录/var目录异厂膨胀日志文件无轮转du --max-depth1 /var看/var/log下的文件清空/截断大日志配置 logrotate/var/lib/docker占用巨大容器日志或镜像堆积docker system dfls -lhS /var/lib/docker/containers/*/*json.log截断容器日志docker image prune配置max-size根分区满但数据盘没问题系统盘空间规划不合理lsblk、df -h迁移/var或/home到数据盘或清理系统盘无用文件删了文件但空间一直没释放文件被打开的句柄还指向该 inodelsof L1查看 deleted 文件重启持有句柄的进程/tmp下文件异常多某程序临时文件未清理find /tmp -type f | wc -l清理超过3天的文件再分享几个我个人的习惯性操作这些是在无数个凌晨三点总结出来的经验教训第一du命令在大目录上执行时要加--max-depth1不要用du -sh /var/log/*这种一次性把所有子目录都统计完的方式。原因是du -sh在子目录很多时也会遍历所有子目录跟du --max-depth1的结果其实是等价的但命令行的可读性和排序的灵活性差很多。用--max-depth1配合sort -rh一次就能看清每个子目录的体量效率高得多。第二日志文件清理不要用rm。除非你确认进程已经停止否则一律用truncate -s 0或者mv加重建方式目的就是为了绕开文件句柄问题。记得有一次同事写了个误删日志的脚本用rm删掉了 Tomcat 的catalina.out结果 Tomcat 进程还在运行文件确实删了但空间一点没释放而且因为日志文件被删除Tomcat 又创建了新的catalina.out新文件从0开始记旧的空间还被占着。这个局面就是磁盘没满但占了双份的日志空间最终只能重启 Tomcat 才把空间吐出来。这中间的数据流失和业务停顿都是可以避免的。第三容器日志的max-size和max-file参数务必在一开始就配上。很多人用 Docker 默认配置跑容器根本没意识到json.log是无限增长的。等你发现日志文件40GB的时候再去清理虽然能用truncate临时解决但如果要重启容器才能彻底释放影响面就不小了。我的习惯是新容器必配日志限制旧容器在发布窗口里逐一补上log-opts。第四磁盘清理不要图“一次性全清”。碰到一个目录下有几十万个文件直接rm -rf会让磁盘 I/O 飙高影响线上服务。分批删除会好很多。比如# 分批次删除旧文件每次处理一批间隔一下避免 I/O 瞬间飙高 find /data/logs -type f -mtime 30 -delete -exec sleep 0.1 \; 2/dev/nullfind的-exec sleep会让每删一个文件之后睡0.1秒在几百万个文件的场景下这个速度还是太慢更实用的做法是用变量的方式分批处理取出前N个文件名一次rm循环几次。但这属于极端场景常规情况用find -delete的默认速度即可生产环境在低峰期操作就行。第五也是最重要的一个习惯每次清理完务必用df -h和df -i各验证一次确认空间回到安全水位并记录这次清理的根因、处理过程和数据量留存为后续排查参考。运维写文档看起来麻烦但在下次遇到同类问题时翻出之前的记录能省掉一大半的排查时间特别是当这个“下一次”发生在凌晨三点半的时候。
返回列表