1. 这不是“磁盘满了”而是系统在报警:先别急着删文件,得看懂它在说什么
“Linux磁盘空间满了怎么办”——这句搜索背后,站着无数个凌晨三点还在服务器前刷新df -h的运维人、被客户投诉网站打不开而手忙脚乱的开发者、还有第一次部署博客就卡在“上传失败”的新手。我试过不下二十次类似场景:某天监控告警突然炸开,/dev/sda1使用率99%,SSH还能连上,但ls卡顿、journalctl报错、docker pull直接中断……这时候最危险的操作,就是打开终端狂敲rm -rf /var/log/*——你以为清的是日志,实际可能顺手干掉了正在写入的auth.log锁文件,或者误删了/var/lib/docker/overlay2里某个容器的元数据层,结果服务起不来,问题比原来还重。
真正关键的,从来不是“怎么删”,而是“删什么才安全”“为什么这里会满”“删完会不会立刻再满”。Linux的磁盘空间管理逻辑和Windows完全不同:它没有“回收站”概念,没有图形化空间分析器,所有空间占用都裸露在文件系统层级下,且受inode限制、挂载点隔离、日志轮转策略、容器镜像分层等多重机制交叉影响。比如你看到/根分区满了,但df -h显示/home和/var是分开挂载的,那问题大概率出在/var子目录;又比如df -h显示只用了85%,可touch test.txt却报“No space left on device”,这时八成是inode耗尽了(df -i一看便知),跟磁盘块完全无关。
所以这篇文章不教你怎么一键清理,而是带你走一遍真实排查链路:从第一眼看到df输出开始,到定位真实罪魁祸首,再到安全释放空间、建立长效防护。所有命令我都附带参数原理说明,所有操作都标注风险等级(⚠️低危 / ⚠️⚠️中危 / ⚠️⚠️⚠️高危),并给出替代方案。你不需要背命令,只需要理解每一步在解决什么问题——这才是面对“磁盘满了”时,真正能让你稳住局面的能力。
2. 空间到底被谁吃了?四层穿透式排查法
很多人的第一反应是du -sh * | sort -hr | head -10,扫一眼哪个目录最大就开删。这方法在桌面环境可能凑效,但在生产服务器上,极大概率会漏掉三类“隐形吞噬者”:被删除但仍被进程占用的文件、容器未清理的悬空镜像、以及日志系统因配置错误导致的无限追加。我们必须用一套分层递进的排查逻辑,像剥洋葱一样一层层定位。
2.1 第一层:确认是“块空间”还是“inode”告急?
这是最容易被忽略的起点。df -h只告诉你磁盘块(block)使用率,但Linux文件系统还有另一套计数单位——inode(索引节点)。每个文件、目录、软链接都消耗一个inode,当inode用完时,即使磁盘块还有90%剩余,系统也会拒绝创建新文件,报错“No space left on device”。
执行:
df -h # 查看各挂载点块使用率 df -i # 查看各挂载点inode使用率重点观察Use%列。如果df -h显示/使用率98%,但df -i显示/的inode使用率也是98%,那问题根源就是小文件泛滥——比如某程序每秒生成一个1KB的日志碎片,半年下来积攒百万个小文件,块没占多少,inode早爆了。此时du -sh按大小排序毫无意义,必须改用find按文件数量统计:
# 统计根目录下各子目录的文件数量(排除/proc /sys等虚拟文件系统) find / -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -nr | head -10这条命令的逻辑是:-xdev防止跨文件系统扫描(避免扫到/proc这种内存映射目录),-type f只统计普通文件,cut -d "/" -f 2提取路径第二级目录名(如/var/log/messages→var),uniq -c计数,sort -nr按数量倒序。实测某次故障中,/var/spool/postfix/maildrop目录下有230万个零字节临时邮件文件,正是inode耗尽的元凶。
提示:
df -i的IUsed值接近ITotal时,优先排查小文件。常见高危目录:/var/spool/(邮件队列)、/tmp(临时文件)、/var/lib/docker/overlay2/(容器层,尤其Docker未清理时)
2.2 第二层:识别“已删除但未释放”的幽灵文件
Linux的文件删除机制是:rm命令只是解除文件名到inode的链接,当该inode的引用计数(link count)降为0且没有任何进程正打开该文件时,内核才真正回收磁盘块。但如果一个大日志文件被rm后,仍有进程(如nginx、java应用)持续向其fd写入,这个文件虽在ls中消失,但磁盘空间不会释放,lsof能揪出它。
执行:
lsof +L1 # 列出所有链接数为0的打开文件(即已删除但未释放的)输出示例:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1234 root 6w REG 253,1 123456789 12345678 /var/log/nginx/access.log (deleted) java 5678 app 12w REG 253,1 987654321 87654321 /opt/app/logs/error.log (deleted)看到(deleted)标记,就确认是幽灵文件。释放方法只有两种:重启对应进程(如systemctl restart nginx),或向进程发送SIGUSR1信号(部分服务支持日志重载,如nginx收到此信号会关闭旧fd并打开新日志文件)。切勿尝试echo > /proc/PID/fd/FD_NUM强行清空,这可能导致进程崩溃。
注意:
lsof +L1需root权限。若无权限,可用sudo lsof -n -P | grep deleted替代,但结果更杂乱。
2.3 第三层:定位“大文件”与“大目录”的精确坐标
当确认是块空间不足后,du仍是主力,但必须规避两个经典陷阱:一是du -sh *会跳过隐藏文件(.开头),二是未排除挂载点导致重复计算。正确姿势是:
# 1. 进入目标挂载点根目录(如根分区就cd /) cd / # 2. 统计所有一级子目录(含隐藏目录)的磁盘占用,按大小倒序 du -sh .[!.]* * | sort -hr | head -20 # 解释:.[!.]* 匹配以.开头但非..的目录(即隐藏目录),*匹配普通目录,sort -hr按人类可读格式倒序若发现/var异常庞大,继续深入:
cd /var du -sh .[!.]* * 2>/dev/null | sort -hr | head -15这里2>/dev/null很重要——du扫描时遇到权限不足的目录(如/var/lib/lxd)会报错,错误信息会混在结果里干扰排序,重定向丢弃错误流才能看到真实数据。
实操心得:我曾在一个数据库服务器上发现/var/lib/mysql占了80GB,但du显示只有20GB。原因在于MySQL启用了innodb_file_per_table=OFF,所有表数据都存放在共享表空间ibdata1中,而du无法穿透InnoDB引擎层统计。此时必须登录MySQL执行:
SELECT table_schema "Database", ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) "Size (MB)" FROM information_schema.TABLES GROUP BY table_schema ORDER BY SUM(data_length + index_length) DESC;2.4 第四层:容器与虚拟化环境的“空间黑洞”
Docker、Podman等容器运行时是现代服务器磁盘满的高频诱因。它们的存储驱动(如overlay2)会将镜像层、容器层、卷(volume)分开存放,但df -h只显示底层块设备,无法反映上层逻辑关系。典型症状:docker system df显示镜像+容器仅占20GB,但/var/lib/docker目录du却显示120GB。
诊断步骤:
# 1. 查看Docker整体空间使用(逻辑视图) docker system df -v # 2. 定位/var/lib/docker下具体膨胀点 du -sh /var/lib/docker/{overlay2,volumes,buildkit,containers} 2>/dev/null | sort -hr # 3. 若overlay2最大,检查是否有悬空层(dangling layers) docker system df | grep "Build cache" # BuildKit缓存常被忽略 docker images -f "dangling=true" -q | xargs docker rmi # 清理悬空镜像特别注意/var/lib/docker/volumes/:用户手动创建的命名卷(named volume)不会被docker system prune自动清理,且默认不启用日志轮转。某次故障中,一个ELK栈的logstash-data卷因Logstash配置错误,将所有日志写入同一文件且永不rotate,半年涨到65GB。
警告:
docker system prune -a会删除所有未使用的镜像、容器、网络、构建缓存,务必确认业务无依赖再执行。生产环境建议先用-f --dry-run模拟。
3. 安全清理的七种武器:什么能删,什么必须留
定位到问题目录后,“删”不是目的,“安全释放空间且不引发次生故障”才是核心。以下按风险等级排列七种清理手段,每种都标注适用场景、执行命令、原理说明及避坑要点。
3.1 高危操作:直接删除大文件(⚠️⚠️⚠️)
适用场景:确认文件已无进程占用(lsof无结果)、非系统关键日志(如自定义应用日志)、且有备份。
命令:
# 先确认文件状态 ls -lh /path/to/bigfile.log lsof /path/to/bigfile.log # 应无输出 # 安全删除(不走rm -f,避免误删) rm /path/to/bigfile.log # 或更稳妥:先清空内容再删(保留文件inode,避免应用因fd失效崩溃) > /path/to/bigfile.log原理:>重定向会截断文件为0字节,但保持inode和fd有效,应用可继续写入。比rm更温和,适合正在被tail -f监控的日志。
避坑:绝不在/usr、/lib、/etc下执行rm *!某次事故中,运维在/usr/local/bin误删了python软链接,导致所有Python脚本启动失败。正确做法是ls -l确认目标后再删。
3.2 中危操作:日志轮转与归档(⚠️⚠️)
适用场景:/var/log/下常规服务日志(nginx、apache、systemd-journal等)。
命令:
# 方案A:使用logrotate(推荐,系统原生) # 编辑配置 /etc/logrotate.d/myapp /path/to/app/*.log { daily missingok rotate 30 compress delaycompress notifempty create 0644 app app sharedscripts postrotate systemctl kill -s USR1 `cat /var/run/myapp.pid 2>/dev/null` 2>/dev/null || true endscript } # 手动触发轮转测试 logrotate -d /etc/logrotate.conf # -d调试模式,不实际执行 logrotate -f /etc/logrotate.d/myapp # -f强制执行 # 方案B:journalctl日志(systemd系统) journalctl --disk-usage # 查看当前占用 journalctl --vacuum-size=500M # 保留最近500MB日志 journalctl --vacuum-time=2weeks # 保留最近2周日志原理:logrotate通过rename+create新文件实现无缝切换,postrotate脚本能通知应用重载日志fd。journalctl --vacuum-*直接调用journald API清理,比手动删/var/log/journal/安全得多。
避坑:journalctl --vacuum-size参数必须带单位(M/G),否则默认是字节!--vacuum-size=500只会留500字节,瞬间清空所有日志。
3.3 低危操作:清理包管理器缓存(⚠️)
适用场景:Ubuntu/Debian的apt、CentOS/RHEL的yum/dnf。
命令:
# Ubuntu/Debian sudo apt clean # 删除所有已下载的.deb包(最彻底) sudo apt autoclean # 只删已过期的.deb包(较温和) # CentOS/RHEL 7+ sudo yum clean all # 清理所有缓存(包括元数据) # CentOS/RHEL 8+ sudo dnf clean all # 检查清理效果 du -sh /var/cache/apt/archives/ # Ubuntu du -sh /var/cache/yum/ # CentOS原理:包管理器下载的安装包(.deb/.rpm)默认保留在本地缓存,供重装或离线安装。这些文件长期不清理可达数GB,且无业务影响。
避坑:apt clean后首次apt update会重新下载元数据,稍慢但无害;yum clean all不影响已安装软件,放心执行。
3.4 中危操作:清理内核残留(⚠️⚠️)
适用场景:长期未重启的服务器,/boot分区常因旧内核堆积而满。
命令:
# 查看已安装内核 dpkg -l | grep linux-image # Ubuntu/Debian rpm -qa | grep kernel # CentOS/RHEL # Ubuntu/Debian:保留最新2个内核,删其余 sudo apt autoremove --purge # CentOS/RHEL:保留当前运行的内核,删其余 sudo package-cleanup --oldkernels --count=1 # 或手动(先确认当前内核) uname -r sudo rpm -e kernel-3.10.0-1127.el7.x86_64 # 替换为要删的旧版本原理:每次内核升级,/boot/vmlinuz-*和/lib/modules/*都会新增,但旧版本不自动删除。/boot分区通常只有200-500MB,5个内核就能塞满。
避坑:uname -r必须执行!删错当前内核会导致重启失败。package-cleanup是CentOS官方工具,比手动rpm -e更安全。
3.5 高危操作:清理Docker构建缓存(⚠️⚠️⚠️)
适用场景:CI/CD频繁构建的服务器,/var/lib/docker/buildkit或/var/lib/docker/image/overlay2膨胀。
命令:
# Docker 20.10+(BuildKit默认启用) docker builder prune -f # 清理构建缓存 docker system prune -f --volumes # 清理未使用卷(慎用!) # 查看详细占用 docker system df -v # 若需精准清理某镜像的中间层 docker image ls -a | grep "<none>" | awk '{print $3}' | xargs docker image rm -f原理:BuildKit缓存包含构建过程中的中间镜像层、网络缓存、构建上下文,长期积累可达数十GB。docker builder prune专为此设计。
避坑:--volumes参数会删除所有未被容器引用的命名卷!确认docker volume ls中无重要数据再执行。生产环境建议先docker volume inspect vol_name检查卷内容。
3.6 低危操作:清理临时文件(⚠️)
适用场景:/tmp、/var/tmp下的过期文件。
命令:
# systemd系统(推荐,自动管理) sudo systemctl status tmp.mount # 确认tmpfs挂载状态 sudo systemctl enable --now systemd-tmpfiles-clean.timer # 启用自动清理 # 手动清理(设置超时时间) sudo find /tmp -type f -mtime +7 -delete # 删7天前的文件 sudo find /var/tmp -type f -mtime +30 -delete # 删30天前的文件 # 清理core dump(若开启) sudo find /var/lib/systemd/coredump -name "*.core" -mtime +7 -delete原理:/tmp默认是tmpfs(内存文件系统),但部分系统会挂载到磁盘;/var/tmp则明确要求持久化。systemd-tmpfiles-clean按/usr/lib/tmpfiles.d/*.conf规则自动清理。
避坑:find ... -delete务必先用-print测试!find /tmp -mtime +7 -print确认列表无误后再加-delete。
3.7 中危操作:调整日志级别与采样率(⚠️⚠️)
适用场景:/var/log/journal/或应用日志因DEBUG级别全量记录而暴涨。
命令:
# systemd-journal:降低日志级别 sudo mkdir -p /etc/systemd/journald.conf.d/ sudo tee /etc/systemd/journald.conf.d/limit.conf << 'EOF' [Journal] SystemMaxUse=500M SystemMaxFileSize=100M MaxLevelStore=warning MaxLevelSyslog=warning EOF sudo systemctl restart systemd-journald # 应用层:如Nginx,关闭access_log或降低日志格式 # /etc/nginx/nginx.conf # access_log /var/log/nginx/access.log main; # 注释掉或改为 # access_log /var/log/nginx/access.log combined; # combined比main少记录一些字段原理:MaxLevelStore=warning表示只存储WARNING及以上级别日志,DEBUG/INFO全部丢弃,空间直降80%。SystemMaxFileSize限制单个日志文件大小,避免单文件过大。
避坑:MaxLevelStore设为err可能丢失关键错误上下文;生产环境建议warning,调试时临时调回info。
4. 长效防护:三道防线让磁盘满成为历史
清理是救火,防护才是治本。我给所有管理的服务器都部署了这三道防线,三年来再未发生过因磁盘满导致的服务中断。
4.1 第一道防线:实时监控与阈值告警
不能等df报99%才行动。我的标准是:根分区>85%、/var>80%、/boot>70%即触发告警。
实现方案(Prometheus + Grafana):
# prometheus.yml 增加磁盘指标采集 - job_name: 'node' static_configs: - targets: ['localhost:9100'] # 添加磁盘使用率告警规则 - alert: DiskUsageHigh expr: 100 * (1 - (node_filesystem_free_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) > 85 for: 10m labels: severity: warning annotations: summary: "High disk usage on {{ $labels.instance }}" description: "Disk usage is above 85% (current: {{ $value | printf \"%.2f\" }}%)"替代方案(无监控系统时):
# 创建每日检查脚本 /usr/local/bin/check-disk.sh #!/bin/bash THRESHOLD=85 ROOT_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$ROOT_USAGE" -gt "$THRESHOLD" ]; then echo "ALERT: Root disk usage $ROOT_USAGE% at $(date)" | mail -s "Disk Alert" admin@example.com fi # 加入crontab每日8点执行 0 8 * * * /usr/local/bin/check-disk.sh实操心得:邮件告警必须测试!我曾因
echo "test" | mail -s "test" your@email.com先验证。
4.2 第二道防线:自动化清理策略
监控只管“发现问题”,自动化清理才“解决问题”。以下策略已在20+台生产服务器稳定运行两年。
策略1:日志自动归档
# /etc/logrotate.d/production-app /opt/app/logs/*.log { daily rotate 7 compress delaycompress missingok notifempty create 0644 app app # 关键:归档后发送到对象存储(如MinIO) postrotate if [ -f "/opt/app/logs/app.log.1.gz" ]; then aws s3 cp /opt/app/logs/app.log.1.gz s3://logs-bucket/$(hostname)/$(date +%Y%m%d)/ --endpoint-url http://minio:9000 fi endscript }策略2:Docker定期瘦身
# /etc/cron.weekly/docker-prune #!/bin/bash # 清理悬空镜像、停止的容器、未使用网络 docker system prune -f --filter "until=168h" # 只删7天前的 # 清理构建缓存 docker builder prune -f --keep-storage 2g # 保留最多2GB缓存策略3:临时文件智能清理
# systemd-tmpfiles规则 /etc/tmpfiles.d/clean-app.conf # 删除/opt/app/tmp下30天未访问的文件 f /opt/app/tmp - - - 30d # 删除/var/log/app下7天前的压缩日志 z /var/log/app/*.gz 644 app app 7d注意:所有自动化脚本必须先在测试环境运行一周,确认无误再上线。
docker system prune这类命令建议加--dry-run参数先模拟。
4.3 第三道防线:容量规划与弹性扩展
技术手段再强,也抵不过业务爆发式增长。我的经验是:对任何存储型服务(数据库、对象存储、日志中心),必须做容量基线分析。
基线分析三步法:
- 采集历史数据:用
df -h每天同一时间记录/var/lib/mysql、/var/log/journal等关键路径,持续30天。 - 计算增长率:
awk '{sum+=$5} END {print sum/NR}' df-history.log得到平均日增长百分比。 - 预测扩容时间:若
/var/lib/mysql当前80GB,日均增1.2GB,预留20%缓冲,则200GB - 80GB = 120GB可用空间,120 / 1.2 ≈ 100天后需扩容。
弹性扩展方案:
- 短期应急:
lvextend扩展LVM逻辑卷(需提前规划LVM)。 - 中期方案:将
/var/lib/mysql迁移到更大磁盘,并用mount --bind挂载(不影响应用路径)。 - 长期架构:数据库分库分表、日志接入ELK集中存储、静态资源托管至对象存储。
个人体会:我曾因忽略基线分析,在一个电商项目上线后第47天磁盘告急。后来强制所有新项目立项时,必须提交《存储容量规划书》,包含初始容量、月增长率、扩容触发阈值三项,从此再无被动救火。
5. 故障复盘实录:一次真实的99%磁盘事件
去年处理过一个典型案例,整个过程极具代表性,我把关键节点和决策逻辑还原出来,帮你建立真实处置手感。
现象:
监控告警/dev/sda1使用率99%,df -h显示/分区198G/200G;df -i显示inode使用率仅42%,排除inode问题;lsof +L1无输出,排除幽灵文件。
第一轮排查(耗时12分钟):
du -sh .[!.]* * | sort -hr | head -5 # 输出: # 180G var # 12G home # 3.2G usr # ... cd /var && du -sh .[!.]* * | sort -hr | head -5 # 175G lib # 2.1G log # ... cd /var/lib && du -sh .[!.]* * | sort -hr | head -3 # 175G docker锁定/var/lib/docker。但docker system df显示总占用仅45GB,矛盾!
第二轮深挖(耗时25分钟):
du -sh /var/lib/docker/{overlay2,volumes,buildkit,containers} 2>/dev/null | sort -hr # 170G overlay2 # 3.2G volumes # 1.8G buildkit # ... # 问题在overlay2!继续查: find /var/lib/docker/overlay2 -name "diff" -type d -exec du -sh {} \; 2>/dev/null | sort -hr | head -5 # 168G /var/lib/docker/overlay2/abc123.../diff发现一个diff目录独占168G。docker ps -a查看该容器状态为Exited,但docker inspect abc123显示其Mounts挂载了一个/data卷,而该卷实际指向宿主机/mnt/data——一个被误配置为容器内/data的NFS挂载点!容器退出后,NFS挂载未卸载,overlay2/diff层仍保留所有NFS文件的硬链接副本。
最终解决(耗时8分钟):
umount /mnt/data卸载NFS(确认无其他进程使用)docker system prune -f清理已退出容器的残留层du -sh /var/lib/docker/overlay2确认降至5GB- 修改容器启动脚本,添加
--mount type=bind,source=/mnt/data,target=/data,readonly,避免写入
复盘教训:
- NFS挂载点绝不能作为容器
-v卷的源目录,必须用--mount显式声明readonly docker system df的“逻辑视图”不反映底层文件系统真实占用,du仍是黄金标准- 所有挂载点必须在
/etc/fstab中配置noauto,x-systemd.automount,避免开机自动挂载导致问题
这个案例里,如果第一步就rm -rf /var/lib/docker/overlay2,不仅168G空间没释放(因为NFS文件还在),还会导致所有容器无法启动。真正的解法,永远藏在“为什么”里,而不是“怎么做”里。
6. 最后分享一个压箱底技巧:用一行命令生成空间热力图
日常巡检时,我常用这个命令快速定位“热点区域”,比du逐层扫描快10倍:
# 生成当前目录下所有子目录的磁盘占用热力图(颜色编码) ncdu -x -o /tmp/ncdu-report / 2>/dev/null && ncdu -f /tmp/ncdu-reportncdu(NCurses Disk Usage)是du的交互式增强版,-x参数确保不跨文件系统,-o导出报告,-f加载报告。进入界面后:
- 按
d可直接删除选中目录(带确认) - 按
c可查看目录内最大文件 - 按
n按名称排序,s按大小排序 - 按
?呼出帮助菜单
安装:apt install ncdu(Ubuntu)或yum install ncdu(CentOS)。它不依赖Python,纯C编写,扫描200GB目录仅需15秒,是我每天必跑的“空间CT扫描”。
如果你现在正面对一个告警的终端,别慌。先敲df -h和df -i,再跑lsof +L1,然后用du -sh .[!.]* * | sort -hr | head -10定焦。记住,Linux磁盘空间问题从来不是“满了”,而是“失衡了”——日志没轮转、缓存没清理、挂载没规范、规划没前瞻。把这四层排查法和七种清理武器吃透,你就能从救火队员,变成防火专家。