哪个运维不是从一两次磁盘爆满的教训里长起来的?我刚接手服务器那会儿,最怕的就是半夜收到监控告警,点开一看“根分区使用率 95%”。白天处理还好,晚上正睡觉被叫起来,一边顶着眼皮一边在 SSH 里敲 df -h 的感觉,经历过的人都懂。后来啃了一堆资料,也亲手在测试机上练了无数遍,才算把 Linux 根目录扩容这件事真正吃透——整个过程并不复杂,关键在于你搞清楚根目录长在哪种存储架构上,然后选对对应的扩容工具链。
这篇内容不分发行版,CentOS、Ubuntu、Debian、欧拉这些全都适用。我会把 LVM 扩容和非 LVM 扩容两条路线都完整写出来,连带 VMware、KVM、云主机场景下经常出现的“磁盘调大了但系统里还是旧容量”的疑难杂症,以及扩容之后怎么避免同一件事再次发生。内容比较干,建议收藏,遇到告警时对着操作就行。
1. 扩容前的判断:你的根目录在 LVM 里,还是躺在普通分区上
1.1 根目录“无缘无故满掉”的真正原因
先聊一个大家都会遇到的怪现象:df -h 显示根目录已经 100%,你满世界找大文件,du 扫了几圈却找不到占用大头。这是因为很多隐藏空间根本没算在普通文件里。
最常见的是四类:一是 /var/log 和 journald 日志无限膨胀,长期不清理的机器有这个毛病;二是 Docker 的 overlay 目录,/var/lib/docker 下镜像层、容器层越滚越大,你删除镜像也不一定立刻释放;三是已删除但仍被进程占用的文件,进程没重启之前,磁盘空间被“钉”在那儿;四是软件包缓存和旧内核镜像,/boot 下堆积一大批旧版本内核,占掉的不只是 /boot,还会间接影响根目录可用索引节点。
所以真正操作扩容之前,先把大件清一遍是值得的。不是说一定要清到很低水位才动,而是为了扩容后不让新空间几天就被原样吃回去。
1.2 三组命令快速判断 LVM 还是普通分区
扩容的第一步不是敲扩容命令,而是搞清楚根目录到底长在什么结构上。三组命令就能得到结论:
bash复制df -hT
lsblk -f
mount | grep ' / '
重点看 df -hT 输出里根目录对应设备长什么样。举一个典型输出:
code复制Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/centos-root xfs 50G 47G 3.0G 94% /
devtmpfs devtmpfs 7.9G 0 7.9G 0% /dev
看到 /dev/mapper/centos-root 这种路径,基本可以确定是 LVM 布局:centos 是卷组名,root 是逻辑卷名。你再执行 vgs、lvs、pvs,就能把整条存储链路看得清清楚楚:
bash复制[root@vm ~]# vgs
VG #PV #LV #SN Attr VSize VFree
centos 1 2 0 wz--n- 49.00G 0
[root@vm ~]# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
root centos -wi-ao---- 47.00g
swap centos -wi-ao---- 2.00g
如果 df -hT 根目录这行显示的是 /dev/sda2 或 /dev/vda1,而且 lsblk -f 里也看不到任何卷组字样,那这就是普通分区布局。同样看 lsblk -f,注意输出里有没有 LVM 标签的列:
| 你看到的输出特征 | 结论 | 后续扩容路线 |
|---|---|---|
根路径带 /dev/mapper/xxx-yyy |
LVM 卷组 + 逻辑卷 | vgextend + lvextend + growfs |
根路径直接是 /dev/sda1、/dev/vda1 |
普通分区 | growpart / fdisk + resize 文件系统 |
根路径的磁盘名带有 nvme0n1p1 |
NVMe 普通分区 | 同上,注意分区名带 p |
根路径是 /dev/mmcblk0p2 |
SD/eMMC 设备 | 同上,注意 mmcblk 命名 |
1.3 文件系统类型决定扩容命令两码事
判断完存储布局,还要看根目录文件系统类型,这一步直接决定后续命令用哪个。df -hT 第二列已经写清楚了,最常见的现代发行版根目录是 xfs(RHEL 系默认)和 ext4(Ubuntu/Debian 系默认)。
XFS 和 ext4 的在线扩容命令完全不同:XFS 用 xfs_growfs,ext4 用 resize2fs。两个命令不能互换,在 XFS 上跑 resize2fs 会直接报“未知的文件系统特征”,在 ext4 上跑 xfs_growfs 同样不好使。这点非常基础,但我就见过有人因为没看类型导致扩了几次都没成功,还在群里反复问“是不是分区表坏了”。
如果实在不确定,再用 blkid 确认设备文件系统的 UUID 和类型:
bash复制blkid /dev/mapper/centos-root
blkid /dev/sda1
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LVM 路线:新磁盘、卷组、逻辑卷、文件系统,四步扩到根目录
2.1 为什么说 LVM 是“后悔药”:在线扩容的原理
LVM 的逻辑其实很好理解。传统普通分区表里,磁盘分区的边界是固定的,想扩大根分区必须动分区表,一旦起始位置动错就是数据灾难。LVM 则把物理磁盘抽象成三层:物理卷 PV、卷组 VG、逻辑卷 LV。物理卷划一块内存,卷组把多个物理卷汇成一个资源池,逻辑卷再从池子里切一块出来给文件系统用。
所以扩容就变成了一个“拆东墙补西墙”或者“给池子加水”的玩法。你的根目录所在逻辑卷空间不够,不需要动物理分区表,先给卷组加一块新硬盘,再把池子里的空闲容量拨给根目录所在逻辑卷,最后让文件系统把新空间消化掉。整个链路里不涉及破坏分区表,风险低,而且支持在线执行。
这也是为什么我强烈建议:新装 Linux 服务器时,哪怕现在硬盘够用,也尽量用 LVM 布局。等哪天空间真的告急,你就知道 LVM 多香了。
2.2 从空磁盘到根目录扩容的完整命令链
假设现在分给服务器的是一块全新的 50G 磁盘,在系统里显示为 /dev/sdb,根目录所在卷组名是 centos(不同系统可能叫 ubuntu-vg、vg00 等,用前面 vgs 查到的为准)。
第一步,先把新磁盘做成 LVM 物理卷。这里我推荐先建一个分区再 pvcreate,而不是直接把整块盘做成 PV。原因后面讲坑的时候会细说,分区操作可以用 fdisk 或 parted:
bash复制fdisk /dev/sdb
# 交互输入:
# n → p → 回车(默认分区号) → 回车(默认起始扇区) → 回车(默认结束扇区) → w
然后通知内核重读分区表:
bash复制partprobe /dev/sdb
接着创建物理卷:
bash复制pvcreate /dev/sdb1
pvs
pvs 输出里应该能看到新的 PV 以及大小。第二步,把这块物理卷加入到现有卷组:
bash复制vgextend centos /dev/sdb1
vgs
执行后再看 vgs,VFree 会从原来的 0 变成约 50G,这就是你可以分配的新资源。
第三步,扩逻辑卷。先确认根目录逻辑卷的完整路径:
bash复制lvextend -l +100%FREE /dev/mapper/centos-root
# 或者明确扩一个数值
lvextend -L +20G /dev/mapper/centos-root
两种方式区别很好理解:-l +100%FREE 表示把卷组剩余空闲空间全部并入根逻辑卷,一劳永逸但不够精细;-L +20G 表示只扩 20G,适合想留一部分空间给别的 LV 的场景。确认路径时可以用 lvdisplay 查看所有逻辑卷,避免写错。
第四步,也是最容易漏掉的一步:扩展文件系统。XFS 还是 ext4,命令不一样:
bash复制# XFS 文件系统
xfs_growfs /
# ext4 文件系统
resize2fs /dev/mapper/centos-root
xfs_growfs 可以直接跟挂载点 /,工具会自动找到对应设备;resize2fs 则必须跟设备路径,也就是 df -h 里根目录那行最左边的名字。执行完后 df -hT / 验证,根目录容量应当已经扩大。
2.3 扩容后为什么一定还要做文件系统扩展
很多新手有个误区:lvextend 执行完就认为扩容结束了,重启服务器一看空间还是老样子,然后怀疑自己操作错了。其实 lvextend 只是把逻辑卷设备扩大了,文件系统自身并不知道新设备空间的存在。
举一个类比:逻辑卷是一个停车场,lvextend 把停车场的围墙往外挪了一圈,但路面标线和出入口闸机还是原来的布置。xfs_growfs / resize2fs 才是重新画标线、把新增区域正式纳入管理范围的环节。只有完成这一步,df -h 才会反映出扩容后的真实空间。
值得一提的细节:在部分 RHEL 系发行版里,系统启动时会自动对 XFS 执行一次 xfs_growfs,所以出现“我明明没执行文件系统扩容,重启后空间却变大了”的错觉。但这不该成为依赖,手动执行 xfs_growfs 才是最稳妥的标准做法。
3. 非 LVM 路线:growpart 与 resize2fs/xfs_growfs 的配合
3.1 虚拟磁盘已经变大,为什么系统里还是老容量
如果你没有 LVM 布局,根目录直接放在普通分区上,比如 /dev/sda1、/dev/vda1,这时靠给卷组加盘是没用的,你得设法扩大分区本身。
最常见的场景是虚拟机:VMware 里把系统磁盘从 50G 调到 80G,或云厂商控制台里给云盘扩容,重启后你 lsblk 一看,磁盘 /dev/sda 已经显示 80G,但分区 /dev/sda1 还是 50G,剩下 30G 的空间在分区表之外,完全不可用。这就是分区表和文件系统都没有感知剩余空间的状态。
此时要做的就是三步:扩大分区 → 让内核重读分区表 → 扩展文件系统。三步里前两步都是离线操作的风险点,但只要方法对,完全是安全的。
3.2 growpart 操作步骤与分区号说明
现代系统里有一个特别好用的工具叫 growpart,专门用来在线扩大分区。先安装它:
bash复制# RHEL/CentOS/AlmaLinux/欧拉
yum install -y cloud-utils-growpart
# Debian/Ubuntu
apt install -y cloud-guest-utils
执行前必须看清楚根目录所在分区号,比如根分区是 /dev/sda1,磁盘是 /dev/sda,分区号就是 1;如果是 /dev/nvme0n1p1,磁盘是 /dev/nvme0n1,分区号同样是 1。growpart 命令格式是“磁盘 + 分区号”,注意不要写成 /dev/sda1:
bash复制growpart /dev/sda 1
growpart /dev/vda 1
growpart /dev/nvme0n1 1
这个工具做了什么?它的原理是把分区表中目标分区的结束位置向后移动,从分区起始位置开始的数据块一个都不动。分区最害怕的是起始位置被改动,只要起点不失效,文件系统数据就不会错乱。growpart 碰的就是终点这个安全参数,因此可以放心用。
不过要注意,growpart 要求磁盘本身有剩余空间。如果你的磁盘已经显示 80G,大小没问题,那直接执行上面的命令基本都能成功。执行完毕后再做文件系统扩展:
bash复制# ext4
resize2fs /dev/sda1
# xfs
xfs_growfs /
xfs_growfs 跟挂载点即可,工具会自行寻找设备。resize2fs 则要准确写设备路径。
3.3 手工 fdisk 重建分区表做兜底方案
某些精简系统里装不了 cloud-utils-growpart,或者出于特殊原因你更信任手工方式,也可以用 fdisk 重建分区表来完成同一目标。这个操作在线也可以做,但风险高于 growpart,建议在业务低峰执行。
关键原则:删除原分区重建时,起始扇区必须保持原值,一个数字都不能变。
具体流程如下:
bash复制fdisk /dev/sda
# p —— 打印分区表,记下根分区的起始扇区
# d —— 删除分区,输入根分区编号
# n —— 新建分区
# —— 分区类型选主分区
# —— 分区号沿用默认
# —— First sector: 直接回车,保持默认(即原来的起始位置)
# —— Last sector: 直接回车,用满整张磁盘
# w —— 写盘
然后 partprobe /dev/sda 让内核重新读取分区表,再按文件系统类型扩容:
bash复制resize2fs /dev/sda1 # ext4 场景
xfs_growfs / # xfs 场景
这中间最容易出事的细节就是 First sector。如果你手抖默认成了别的数字,文件系统起始偏移对了但分区起点了偏差,轻则扩容失败,重则整个文件系统不可挂载。我的建议是:能用 growpart 就别手动 fdisk,除非你对这台服务器已经备份过或者快照过。fdisk 这种方案,是“没有 gparted 图形界面、没有 growpart 工具、又非扩不可”时的兜底,而不是首选。
4. 虚拟机与云主机环境下的扩容笔记
4.1 VMware 虚拟机磁盘调大后的连锁检查
VMware ESXi 里调整虚拟磁盘大小很简单,“编辑设置 → 硬盘 → 扩展”,输入目标大小即可。很多管理员在宿主机缩完容量就跑回去敲系统命令,发现 df -h 没有变化,就认为没生效。其实宿主机只是把磁盘设备容量增大了,系统内的分区没有变更,所以必须回到系统里执行前面说的 growpart 与文件系统扩展。
操作前先在客户机里确认磁盘设备是否已经感知到新容量:
bash复制lsblk
如果磁盘列出的大小已经是新大小,比如 sda 显示 80G,那直接执行:
bash复制growpart /dev/sda 1
resize2fs /dev/sda1 # ext4
# 或者 xfs_growfs / # xfs
如果 lsblk 显示还是旧大小,说明客户机内核没有识别到热插拔容量变化。可以尝试扫描新设备:
bash复制echo 1 > /sys/class/scsi_host/host0/scan
不同虚拟机的 SCSI 控制器路径可能不一样,host0、host1 可以逐个尝试。如果扫不出来,最省事的就是一次计划内重启。重启后容量必然会正确显示,然后再做分区扩容。
4.2 云厂商在控制台扩容后你还要做什么
云主机场景和非 LVM 普通分区几乎一样,差别只在于磁盘设备名。阿里云、腾讯云、华为云等大部分 Linux 系统盘设备名是 /dev/vda1 或 /dev/sda1,部分新机型可能是 /dev/vdb1,总之以 lsblk 输出为准。
在控制台完成“云盘扩容”后,你会发现磁盘设备容量已经变大,但分区和文件系统都还没变。此时执行:
bash复制growpart /dev/vda 1
# 如果报错 "unexpected output in fdisk output" 之类,先确认分区号和磁盘名是否写反了
resize2fs /dev/vda1 # ext4
# xfs 则:xfs_growfs /
云厂商通常支持在线扩容,但也有部分盘需要先“挂载/卸载”后才能识别容量。如果你执行完上述步骤发现磁盘设备本身都没变,先回控制台确认云盘状态是不是“扩容中”,或者按厂商文档确认是否需要重启。
4.3 扩容过程中分区表刷新异常的处理
分区表刷新异常是扩容时最磨人的问题。典型表现是:growpart 或 fdisk 执行成功,但系统报错 Kernel still using the old table,或者执行 resize2fs 时提示找不到新分区大小。
原因是内核还在用旧分区表,需要主动刷新:
bash复制partprobe /dev/sda
如果 partprobe 没反应,再试 partx -u /dev/sda,或者对指定分区刷新:
bash复制partx -u /dev/sda1
再不行,blockdev --rereadpt /dev/sda 也是可选手段。但注意,如果你的根分区正处于使用中,部分内核版本即使执行了刷表命令也不会立即生效,此时安全的选择是计划一次重启。重启不是丢脸的方案,硬着头皮继续操作才是风险最大的做法。
5. 扩容后的“治本”:日志、Docker、旧内核的清理和预防
5.1 日志与大文件排查:到底谁吃掉了根目录
扩完容是不是就完事了?当然不是。如果不把根目录被塞满的根源找出来,几个月后还得再如来一次。排查根目录占用最合理的命令是:
bash复制du -x --max-depth=1 / 2>/dev/null | sort -k1 -rh | head -20
-x 参数非常重要,它表示不跨文件系统统计。如果不加,du 遇到 /proc、/sys、/dev 这些虚拟文件系统时会产生大量假数据,干扰你的判断。sort -k1 -rh 按第一个字段数字倒序排,一眼就能看出哪个目录最大。
实际经验里,根目录空间流失的大头通常集中在这些目录:
/var/log:各类 service 日志、syslog、kern.log/var/lib/docker:镜像层、容器层、overlay2/var/tmp和/tmp:临时文件长期不清理/usr/local:手动安装的大型软件或数据/var/cache:apt、dnf 缓存/boot:旧内核镜像堆积
还有一种兜底排查法:找出已删除但被占用的文件。进程打开了一个已删除的日志文件,空间不会释放,直到进程退出。用下面命令可看到:
bash复制lsof | grep deleted
如果发现某进程占着一个 deleted 大文件,安全的做法是重启该服务或者让该进程重新打开日志文件,而不是粗暴 kill。
5.2 journald 日志长期积攒的封顶设置
systemd 的 journald 日志是根目录清洁度的大敌。它的默认配置通常只受磁盘空间总量限制,在长时间跑着的服务器上,journal 目录能达到几个 G 甚至几十 G。
先看当前占用:
bash复制journalctl --disk-usage
清理已产生的日志:
bash复制journalctl --vacuum-size=500M
journalctl --vacuum-time=30d
但更重要的是一次性杜绝后患,编辑 /etc/systemd/journald.conf,加入或修改这几行:
code复制SystemMaxUse=512M
SystemKeepFree=1G
MaxRetentionSec=30day
然后重启 journald:
bash复制systemctl restart systemd-journald
注意:重启 journald 不会删除已有日志,只是让新配置在后续写入时生效。因此“设置上限 + vacuum 一次”这两步都要做才能达到立刻见效的效果。
5.3 Docker 数据目录迁移与旧内核清理
容器多的时候,/var/lib/docker 是根目录的无底洞。最省事的方案是把 Docker 数据目录迁到独立大盘或独立分区。
如果 /var/lib/docker 里还没什么数据,直接修改 /etc/docker/daemon.json:
json复制{
"data-root": "/data/docker"
}
然后 systemctl restart docker。
如果 Docker 已经跑了一阵子,目录内部数据很多,迁移过程要谨慎:
bash复制systemctl stop docker
mkdir -p /data/docker
rsync -aP /var/lib/docker/ /data/docker/
# 修改 daemon.json 设置 data-root
systemctl start docker
确认 Docker 正常后再删旧目录:
bash复制rm -rf /var/lib/docker
不过实际操作中我更倾向于保留旧目录几天,确认业务稳定无回归故障后,离线再清。这是个人习惯,损失一点空间换一点安全感。
旧内核清理也是一个经典套路。系统经过长时间 yum update、apt upgrade,/boot 目录下压缩了一堆 vmlinuz-*、initrd.img-* 旧版本镜像。清理前务必确认当前内核版本:
bash复制uname -r
RHEL 系可以批量删除除当前和最近一两版之外的内核:
bash复制dnf remove $(dnf repoquery --installonly --latest-version=-1 | head -n 1)
Debian/Ubuntu 系用 dpkg --list | grep linux-image 列出所有内核包,手动 apt purge 老版本。删除旧内核时一定要保留至少一个可用的次新版本,防止当前内核出问题后无法启动。
5.4 轻量监控:别等告警才想起扩容
把根目录容量纳入基础监控,不是可选项。如果公司没有完整监控平台,手动写一条 crontab 也能解决大部分问题:
bash复制*/10 * * * * df -h / | awk 'NR==2 {if ($5+0 >= 80) print $5}' | xargs -I{} curl -fsS '你的告警推送接口'
这只是个示意,实际环境可以换成钉钉 Webhook、微信机器人或 Zabbix sender。监控阈值建议 80% 预警,85% 再踩一次,90% 就要通知运维值守。等到了 95% 再处理,很多事情就很被动了。
6. 我踩过的三个坑和最终检查清单
6.1 扩容操作前,比备份还重要的一件事
扩容前给虚拟机或云主机做一个快照,这算是我用真金白银换回来的教训。物理机没法快照,至少要记录下当前分区表、文件系统类型和挂载信息:
bash复制df -hT > /root/disk-before-$(date +%F).txt
lsblk -f >> /root/disk-before-$(date +%F).txt
vgs && lvs && pvs >> /root/disk-before-$(date +%F).txt
cat /etc/fstab >> /root/disk-before-$(date +%F).txt
这些记录贴出来,万一操作中出现意外,不管是找人协助还是回滚,都能快速对齐现场状态。相比盲目操作,多花两分钟记录是成本最低的风控手段。
远程操作扩容还有一个细节:用 tmux 或 screen 开一个会话再操作。因为你不知道扩容过程中会不会因为网络波动断开 SSH,一旦命令执行到一半掉线,恢复连接的难度远高于事前多敲一行 tmux。
6.2 最容易翻车的三个细节
第一个坑是关于 XFS 与 ext4 的命令混用。刚接触 Linux 的新手最容易从网上复制一段 resize2fs 的代码就往 XFS 上跑,结果报错后开始怀疑分区表。把“XFS 用 xfs_growfs、ext4 用 resize2fs”这一条刻烟吸肺,基本能过滤掉一半的反向操作序。
第二个坑是把整块新盘直接 pvcreate 而不建分区表。虽然技术上完全支持,也能正常使用,但后续排障时容易造成困惑:一块没有任何分区表的磁盘,既可能被误判为空盘,也可能被误认为坏了。所以我强烈建议,即使是一块全新磁盘,也先 fdisk 或 parted 建一个分区,把分区类型设成 Linux LVM,再 pvcreate。这样 lsblk 一眼就能看出盘的结构,不会留隐患。
第三个坑是扩容成功但 df -h 没变化就急着重启。前面说过,lvextend 之后文件系统还没扩大,真正的空间释放要看 xfs_growfs / resize2fs 是否执行成功。先执行完文件系统扩展,再用 df -hT / 验证,确认容量变化之后再考虑重启或者收工。
6.3 收尾验证与一条小习惯
扩容操作全部完成后,验证不要只看 df -h 一个维度,建议组合执行:
bash复制df -hT /
lsblk
pvs && vgs && lvs
df -hT / 确认容量和文件系统类型,lsblk 确认分区结构,LVM 视角里 pvs 确认物理卷正常、vgs 确认卷组大小、lvs 确认逻辑卷目标容量。三组命令看下来,整个存储链路是否健康基本就有底了。
我个人还有个习惯:定期对根文件系统做一次快速健康检查。ext4 可以用 e2fsck -n,XFS 可以用 xfs_repair -n。注意 -n 参数代表只读检查、不修复,不会对正在运行的系统造成破坏。检查输出一切正常,心里的弦才能完全松开。
扩容这件事本身并不难,难的是你在告警声中能不能保持冷静,不慌不忙地把现状摸清,再一条命令一条命令地往下走。希望这篇内容能让你下次面对“根目录空间不足”时,从容不少。
