运维干了几年,最怕半夜收到阿里云的短信告警,其中磁盘使用率超过80%这条尤其让人头疼。很多新手同学第一反应是直接扩容,结果扩完没两天又满了,其实核心问题是没搞明白数据到底是谁占的。这篇文章就把我处理阿里云ECS磁盘使用率过高的一套完整流程写出来,从定位分析到在线扩容、日志清理、监控预防,全是生产环境实打实的经验,适合运维、开发、以及自己折腾服务器的同学参考,照着做基本能把水位稳稳控制在安全线以内。
1. 磁盘使用率过高的常见场景与初步判断
1.1 现象与影响
服务器磁盘使用率过高不是小事,它不像CPU偶尔飙一下能忍。根分区满了以后,最直接的表现是数据库写入报错、临时文件创建失败、日志直接丢数据,严重的时候连SSH登录都困难,因为登录过程也要写历史记录和临时会话文件。我遇到过最夸张的一次,某台ECS磁盘100%满掉,应用进程直接hang住,重启之后又起不来,循环崩溃,就是因为系统d目录需要写pid文件但磁盘没有空间。
另外要特别注意“磁盘使用率”和“磁盘IO”不是一回事。使用率过高代表存储空间不足,IO过高代表读写吞吐跑满了,这两个是不同的告警,处理方式也完全不同。我们这轮只讨论使用率。
1.2 判断思路:是数据量大还是文件异常
收到告警后先别急着重启或扩容,先用脑子想一个关键问题:磁盘是慢慢涨上去的,还是突然一下就满了?
如果是慢慢涨,大概率是业务数据累积,或者日志文件一直在写没有轮转。如果是突然满掉,优先怀疑有人上传了大文件、临时目录被灌满、系统生成了巨大的core dump文件,或者是某个服务异常写入了超大日志。我处理过的案例里,有npm缓存把空间干满的,有Docker容器日志json文件膨胀到几十个GB的,还有MySQL的binlog没清理导致磁盘爆掉的。
这里给你一个初期排查的顺序:
- 先看整体容量:
df -h看哪个分区满了,以及总容量多大。 - 再看文件系统inode是否够用:
df -i如果inode也满了,小文件多到爆炸。 - 用
du逐层统计目录,定位哪个目录最大。 - 用
lsof +L1查看被删除但仍被进程打开的文件。
1.3 排查前的准备工作:快照备份与信息收集
开始动任何清理动作之前,务必先创造磁盘快照。阿里云ECS控制台里左侧菜单找“云盘”,选中当前系统盘或数据盘,点“创建快照”,这个过程不影响线上业务,一般几秒钟就能提交。快照是低成本保险,万一删错了文件还能原地恢复。
同时把当前告警时段记录下来,顺手把df -h、df -i、du -sh /的输出保存到一个文本文件。这不是形式主义,出现连续告警时,这些基线数据能帮你判断清理后空间是否真的降下来了,还是说某个进程又在继续写数据。有一次我清理掉30GB日志,结果第二天又满了,回看当时的df记录才发现根分区在十分钟内涨了5GB,于是顺藤摸瓜找到了失控的Java进程。
2. 两步定位:先用命令查清楚谁占用了磁盘
2.1 df与du结合使用找到大头
上服务器第一件事,不用花里胡哨,直接df -h,先确认到底是哪个分区满了。很多ECS默认系统盘就40GB,数据盘单独挂载在 /data 之类的目录,如果系统盘满了而数据盘还很空,完全可以通过迁移目录的方式解决,而不一定非得扩容。
确定分区之后,用du逐级往下找。比较快的定位命令:
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -20这个命令列出根目录下第一层子目录的大小,从大到小排列。看输出你会非常直观地发现,到底是 /var、/home、/usr、/root 哪个目录占用超标。注意加上2>/dev/null,否则一些没有权限的目录会刷出大量错误信息干扰视线。
之后逐层进去继续执行同样的du命令,比如发现 /var 很大,就执行:
du -h --max-depth=1 /var 2>/dev/null | sort -rh | head -20如此迭代三四次,大头基本就锁定了。这套思路不看监控面板也能精准定位,是运维的基本功。
2.2 find与大文件排查,日志文件特殊处理
目录级别定位到大头之后,还要找到具体的巨型文件。推荐两个方向:
我惯用的命令是:
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -30这个会找出所有大于100MB的文件,并按大小排序显示前30个。看到结果你就清楚是哪个应用、哪个路径下的文件最占空间。
日志文件是最常出现的问题源。如果你的ECS装了Docker,一定要检查容器日志,很多情况是容器长期不重启,stdout日志全被写进 host 的/var/lib/docker/containers/<id>/*-json.log里,单个日志轻松上10GB。定位到之后,可以临时执行cat /dev/null > 文件路径清空,但注意不要直接用rm删除,因为有些进程还握着文件描述符,删了空间也不一定释放,后面第5节详细说。正确的长期方案是配置日志轮转,控制单个日志文件大小和保留份数。
2.3 inode耗尽问题与排查
除了容量满,还有一种隐蔽的“磁盘满”其实是inode耗尽。df -h 看容量还剩好几个GB,但df -i 显示/目录 inode 100% 已用,应用一样会报“No space left on device”。这种情况常见于小文件极多的目录,比如邮件队列、缓存目录、PHP会话文件、Docker容器层文件等。
排查时用:
df -i for d in /*; do echo -n "$d: "; find "$d" -xdev | wc -l; done 2>/dev/null统计每个一级目录的文件数量,文件数量最大的就是问题目录。处理inode耗尽没有捷径,只能删除废旧的小文件,或者重新规划把文件移到容量更大的磁盘并换用支持更多inode的文件系统。阿里云默认的ext4在格式化时其实已经根据磁盘大小设置了足够的inode数量,绝大多数情况是业务本身制造了百万级小文件,属于设计问题,建议从应用层根治,比如把文件存储切换到对象存储OSS,而不是无脑扩充inode。
3. 在线扩容:云盘扩容的正确打开方式
3.1 扩容前需要确认的几件事
清理之后如果空间依然紧张,或者业务增长趋势明确,扩容是最终解法。阿里云ECS的云盘扩容非常成熟,基本支持在线扩容,不需要停机。但有多件事必须提前确认,踩过坑的同学都懂:
第一,确认当前ECS实例本身支持在线扩容。大部分新一代规格都支持,部分老规格可能只支持离线扩容,控制台如果提示不支持,就老老实实先停机再操作。
第二,确认云盘类型。高效云盘、SSD云盘、ESSD云盘基本都支持按需扩容,但不同地域的步长可能不同,有的按1GB递增,有的按整量级递增,下单前看一下费用变化就好。
第三,也是最重要的,扩容前一定要观察数据盘的分区方式是MBR还是GPT。MBR分区最大只支持2TB,如果你当前数据盘已经是MBR,扩容量一旦让总容量超过2TB,就必须先改成GPT,这个操作非常麻烦,可能需要数据迁移。所以创建数据盘时如果能选GPT,尽量直接选GPT,省得后面折腾。
3.2 控制台扩容量与分区扩容步骤
在控制台的操作路径很简单:云服务器ECS → 实例 → 磁盘 → 找到目标云盘 → 点“扩容” → 输入目标容量 → 支付。这一步只是把云盘的底层容量变大,操作系统里的分区和文件系统还需要手动扩展,很多第一次操作的同学就是卡在这里,说扩容了但df看到的大小没变。
Linux系统的后续操作分两段,一是分区扩容,二是文件系统扩容。如果数据盘当初是一整块分区占满全盘,直接执行growpart扩展分区,再resize2fs扩展文件系统。举例,数据盘是 /dev/vdb1,挂载在 /data:
# 安装growpart工具,不同发行版包名不同,CentOS用 cloud-utils-growpart yum install -y cloud-utils-growpart # 查看分区情况 lsblk # 扩展分区 growpart /dev/vdb 1 # 扩展文件系统(ext4用resize2fs,xfs用xfs_growfs /data) resize2fs /dev/vdb1如果是系统盘扩容,思路一样,但根分区在线扩容时尽量保持系统负载较低,防止元数据更新期间出现问题。扩容完成后df -h就能看到新容量。
3.3 扩容后文件系统是否自动扩好的判断
很多用户用的是自动扩容工具,或者买了包含“云助手”功能的镜像,阿里云在某些镜像里会自动执行扩容脚本,但不要迷信这一点,以df -h输出为准。如果发现容量没变,手动执行一次上面的命令即可,不会丢数据。
这里特别提醒:resize2fs必须在分区已经扩大的前提下运行,不要顺序搞反。我曾经遇到一位用户,直接对原分区执行resize2fs,结果报错说设备忙,他还以为失败了,后来发现他的分区本身就是整盘分区,不需要growpart,直接resize2fs /dev/vdb1就行。所以操作前一定先lsblk看清楚磁盘结构和分区号。
4. 清理与治理:从数据面把使用率真正降下来
4.1 日志轮转与定时清理
扩容只能解决一时之痛,日志不轮转,扩多少都能给你写满。Linux自带的logrotate是最方便的轮转工具,配置文件一般在/etc/logrotate.d/。以Nginx日志为例,我一般这样配:
/var/log/nginx/*.log { daily rotate 7 missingok notifempty compress delaycompress create 0640 nginx nginx sharedscripts postrotate /bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || true endscript }这段配置的意思是每天轮转一次,保留最近7天的日志,超过的自动压缩并清理最老的。注意postrotate里的信号一定要写对,Nginx需要USR1重新打开日志文件,否则轮转后继续往旧文件里写,等于没轮转。
Docker容器日志则建议在/etc/docker/daemon.json里配置全局限制:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }然后重启Docker使配置生效。注意这个配置只对之后新建的容器生效,已有容器要 recreate 才会应用。
4.2 临时目录与缓存文件清理
系统的 /tmp、/var/tmp 也是垃圾重灾区。有些程序异常退出后会在 /tmp 留下几百MB的临时文件,还有一些下载上传的临时切片文件。建议在crontab里加一条,每天凌晨清理超过3天未访问的临时文件:
find /tmp -type f -atime +3 -delete 2>/dev/null这条命令我用了很多年,基本没出过问题。但有个坑要提醒:如果服务器上有正在运行的长时间任务,比如视频转码的临时文件,atime超过3天但任务还没结束,删除会导致任务异常,所以执行前最好确认业务没有低频写入 /tmp 的进程。稳妥起见,也可以把范围缩小到/tmp/xxx这种专用子目录。
另外,包管理器缓存也很占空间。CentOS的yum缓存、Ubuntu的apt缓存,安装大量依赖后可能有几个GB。定期清理:
yum clean all apt-get clean还有pip和npm的缓存目录,~/.cache/pip和~/.npm/_cacache,用户目录下积攒多了也很可观,清理时不会影响已安装的包。
4.3 数据库与备份文件归档策略
生产环境的ECS上多少都跑着数据库,MySQL的binlog是特别常见的磁盘杀手。如果不需要基于时间点的完整恢复,就不要长期保留binlog,可以在MySQL配置里设置过期时间:
[mysqld] expire_logs_days = 7MySQL 8.0以上建议用binlog_expire_logs_seconds = 604800。已经积累的binlog用PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;手动清理。同样逻辑适用于MongoDB的journal和PostgreSQL的WAL,长期不清理都会让磁盘使用量线性上涨。
业务备份文件也要养成好习惯,本地备份保留最新的2份就够了,历史备份全部挪到阿里云OSS。用ossutil写一个定时同步脚本,比如每天凌晨把本地备份目录 sync 到OSS,然后删除本地超过3天的文件。OSS按量计费比数据盘便宜得多,还能额外拿到跨区域容灾能力。
5. Linux/Windows系统特有的“坑”与处理
5.1 Linux误删文件后空间未释放
这是一条高频问题:明明rm -rf 大文件了,但df -h看到空间一点没变。原因是文件被某个进程打开着,删除的只是目录项,文件本身还被进程的文件描述符引用着,空间自然不释放。我见过最典型的场景是删Nginx日志后,Nginx进程不重新打开文件,磁盘空间死活不降。
排查命令:
lsof +L1找出 deleted 状态且被进程持有的文件。处理办法有两种,要么重启对应进程让文件描述符释放,要么通过/proc/<pid>/fd/<fd>方式清空文件:
: > /proc/<pid>/fd/<fd>但这属于比较侵入式的操作,生产环境建议先重启服务,如果服务不便重启,可以用清空的方式顶一下,之后找业务低峰期重启。另外教训是:大日志文件不要直接rm,用cat /dev/null > file清空才是正道。
5.2 Windows Server磁盘管理中的初始化与分区
Windows Server的ECS同样会遇到磁盘满和“磁盘必须经过初始化逻辑磁盘管理器才能访问”的提示。这个提示一般出现在新挂载了数据盘但还没初始化时。Windows上操作最简单:右键“此电脑” → “管理” → “磁盘管理” → 找到未初始化的磁盘 → 右键“初始化磁盘”,磁盘分区类型选择GPT,然后新建简单卷,分配盘符格式化。注意格式化时文件系统选NTFS,分配单元大小保持默认就好。
很多时候Windows C盘满了,常见的占用大头是系统更新缓存、Windows.old目录、用户临时文件。可以搜“磁盘清理”,选择清理系统文件,能快速清掉 Windows 更新留下的旧版本。对于C盘空间实在不够的情况,思路和Linux一样:把users目录或某些应用的数据目录迁移到D盘,用目录符号链接mklink /J实现无缝迁移。
5.3 虚拟化环境磁盘扩展后的识别问题
阿里云ECS本身是云盘,扩容后直接lsblk能看到新容量。但如果你是在本地虚拟化环境(比如VMware、KVM)里用阿里云镜像装的系统,虚拟机磁盘扩容后Windows和Linux都可能“看不到”新空间。Windows的处理是到磁盘管理里右键分区选择“扩展卷”;Linux则需要重新扫描SCSI设备:
echo 1 > /sys/class/scsi_disk/0:0:0:0/device/rescan然后再lsblk确认容量识别到,继续走growpart和resize2fs流程。这个坑非常多同学踩过,总以为是系统坏了,其实就是虚拟机没重新扫描磁盘。
6. 预防监控与常见问题速查
6.1 监控告警设置
磁盘使用率处理的最高境界是让它在发生之前就被处理掉。阿里云ECS自带云监控,默认就有磁盘使用率监控,但很多同学没设置阈值,等于没有。建议在云监控控制台创建自定义告警规则:磁盘使用率超过70%就发钉钉或短信告警,超过85%提升告警级别。给一个我自己的参考阈值:
| 级别 | 阈值 | 建议动作 |
|---|---|---|
| 预警 | 磁盘使用率 > 70% | 观察趋势,安排时间排查 |
| 告警 | 磁盘使用率 > 85% | 立即登录定位,必要时扩容 |
| 紧急 | 磁盘使用率 > 92% | 业务面临写入风险,立刻处理 |
有人觉得70%就告警太敏感,其实对于没有批量清理机制的服务器,从70%涨到100%可能只要一两天,提前预警才有缓冲窗口。
6.2 常见问题与排查技巧速查表
以下是我这几年处理磁盘问题的高频清单,直接收藏当速查表用:
| 现象 | 可能原因 | 优先处理方案 |
|---|---|---|
| df -h显示满,但du看不出大文件 | 被删除文件仍被进程占用 | lsof +L1 定位进程,重启服务 |
| df -h有空间,但应用报磁盘满 | inode耗尽 | df -i确认,删除小文件 |
| /var目录持续膨胀 | 系统日志或容器日志无限增长 | 配置logrotate和docker日志限制 |
| 扩容后df容量没变化 | 分区/文件系统未扩展 | growpart + resize2fs动手扩 |
| 系统盘满,数据盘空 | 应用数据默认写在系统盘 | 迁移目录到数据盘并用软链 |
| 数据盘刚挂载无法访问 | 磁盘未初始化/未格式化 | Windows磁盘管理初始化,Linux mkfs后挂载 |
每一条我都亲手处理过至少一次,不是从文档里抄的。尤其是第一条,最容易让人怀疑人生,明明du显示就那点文件,df却说满了。
6.3 个人经验:磁盘水位怎么定更合理
根据我个人经验,给所有ECS用户一个非常实用的建议:不要把磁盘规划得“刚刚好”。系统盘至少留20%余量,数据盘也尽量保持30%以上空闲,因为数据库临时排序、应用缓存、日志峰值都是不可预测的。与其在磁盘97%满的时候手忙脚乱处理,不如在60%的时候花十分钟写一个脚本。我知道很多人会觉得大磁盘费钱,但算一下宕机损失和半夜加班的时间成本,就知道这个钱花得值。
最后再分享一个小技巧:处理完每一次磁盘告警后,花一点时间把根因、清理命令、结果写成一段笔记。阿里云控制台里其实有操作审计日志,但那只记录操作不记录原因。我自己有一个本地文档,记录了每次磁盘问题的关联进程、目录和命令,几乎每次都第一个推荐给同事。磁盘问题的本质都是“某个目录的数据增长超出预期”,只要把增长源找到,清理和扩容都是辅助手段,这个思路比任何一条命令都重要。