手里这台测试服务器跑着跑着磁盘又满了,df -h一看使用率99%,数据库写入直接开始报错,临时文件都腾不出空间。说实话,Linux磁盘扩容这个操作听起来挺基础,但真到自己动手的时候,很多人卡在分区表刷新、文件系统识别、云盘和虚拟机盘扩容逻辑不一样这些细节上。这篇就把我在生产环境和测试环境里实测过的一套扩容流程完整写出来,从原理到命令到坑位,一次性说清楚。
整套操作适用于三类人:刚接触Linux运维、正在折腾虚拟机磁盘空间的新手,以及偶尔接手服务器但不想反复查文档的老手。文里所有命令都在 CentOS 7/8、Ubuntu 20.04/22.04 环境验证过,ext4 和 xfs 文件系统都会覆盖到。
1. 先弄清楚问题:磁盘扩容到底在扩什么
1.1 三个高频场景:虚拟机、云主机、物理机
磁盘扩容不是单一操作,背后藏着至少三条完全不同的路径。
第一类是虚拟机场景。我工作中最常遇到的就是某台虚机磁盘不够用,去虚拟化平台把磁盘大小从200G调到500G,然后进系统做后续操作。这一类的特点是:宿主机层面把虚拟磁盘文件加大之后,虚拟机内部看到的磁盘设备本身没变化,必须手动让系统重新扫描SCSI设备或者重启才能识别新容量。
第二类是云主机场景。云厂商控制台上通常有“在线扩容”按钮,点了之后云盘的容量确实变大了,但云主机里的操作系统并没有自动感知到这个变化。有些厂商的镜像里预装了自动扩分区脚本,但大多数时候还是得自己动手。这里还有个隐藏细节:不同厂商的云盘类型不同,有的直接扩大原分区,有的会多出一个未分区空间,处理逻辑完全不一样。
第三类是物理机场景。一般是加了块新硬盘,插上SATA或者SAS口,进系统后fdisk -l能看到新盘,然后分区、格式化、挂载。这种其实最不复杂,因为磁盘和分区都是全新的,不存在“扩容”的兼容性问题,只要弄明白分区表和文件系统的关系就行。
1.2 扩容前必须搞懂的四个概念
这四个概念不搞清楚,后续命令大概率会翻车。
磁盘(Disk)、分区(Partition)、文件系统(Filesystem)和挂载点(Mount Point)是四个不同层级的对象。磁盘是物理存在的存储设备,在Linux里表现为/dev/sda、/dev/vda这类设备文件;分区是把磁盘划分成逻辑区块,表现为/dev/sda1、/dev/vda2;文件系统是建立在分区之上的数据组织格式,ext4、xfs、btrfs都是文件系统;挂载点则是把文件系统关联到目录树上的入口,比如/dev/vda1挂载到/根目录。
很多人扩容失败,就是因为跳过了中间某一层。比如直接把磁盘容量调大,却发现/dev/sda显示新容量,但/dev/sda1还是老样子,因为分区表还停留在扩容前的状态。又比如用fdisk删了分区重建,却忘了分区里的文件系统根本没有跟着分区边界变化,结果数据文件系统元数据错乱。
还有一个很重要的概念是分区表类型:MBR和GPT。MBR是传统的分区表格式,最多只能识别2T容量,超过2T的磁盘必须用GPT。另外MBR只支持4个主分区,GPT在理论上有128个分区,实际用途中完全够用。判断方法很简单:fdisk -l里如果显示Disklabel type: dos就是MBR,显示gpt就是GPT。
2. 扩容前的准备工作:从确认磁盘状态到方案选型
2.1 一套万能的初始排查命令组合
任何扩容操作之前,先把当前状态摸清楚,我用下面这组命令组合,基本上一条都不会少:
# 查看文件系统使用率 df -h # 查看块设备层级关系和容量 lsblk # 查看内核识别到的磁盘设备及分区表类型 fdisk -l # 查看指定磁盘的详细分区表 parted /dev/sda print # 查看挂载情况和文件系统类型 blkid这套组合分别解决不同问题:df -h看的是文件系统层面的使用率,判断“到底哪里满了”;lsblk看的是磁盘——分区——挂载点的树状关系,判断“新容量有没有被内核识别”;fdisk -l和parted看分区表类型和边界,判断“扩容应该动哪一层”;blkid看UUID和文件系统类型,制定格式化或resize策略。
以我处理过的一个案例来说,某台机器df -h显示/dev/mapper/centos-root使用率97%,lsblk显示/dev/sda总容量200G,但/dev/sda2只有100G。一眼就能判断出问题的关键在于/dev/sda的实际容量已经由虚拟化平台分配成200G,但分区/dev/sda2只占用了前100G,LVM物理卷和逻辑卷自然也只有100G可用。这个场景就是典型的“磁盘大了、分区没跟着大”。
实际操作里还有一个必须注意的点:如果是在虚拟机上,刚调整完磁盘大小,lsblk里可能还是显示老容量。这时候需要让系统重新扫描SCSI设备,不需要重启。之前系统有多个SCSI设备,得逐一重新扫描,比较长的命令如下:
echo 1 > /sys/class/scsi_device/0:0:0:0/device/rescan具体设备路径用ls /sys/class/scsi_device/查看,每个设备都执行一遍,然后lsblk一般就能看到新容量。如果是云主机,有些云厂商需要您在控制台上先“挂载”或“刷新”,重启是最稳但最暴力的办法。
2.2 扩容方案的选型逻辑:LVM、裸分区、新磁盘
搞清楚现状之后,就得决定怎么扩容。方案选型总结下来无非三条路。
第一条路是直接扩大已有分区。适用于虚拟机或云主机里原分区没有被LVM管理、文件系统在裸分区上的场景。操作路径是:调整分区边界(growpart或fdisk)→ 通知内核重新读取分区表(partprobe)→ 扩展文件系统(resize2fs或xfs_growfs)。这个方案好处是快、直观,坏处是分区边界操作有一定风险,操作不当会破坏文件系统,必须备份。
第二条路是LVM逻辑卷扩容,这是我认为最优雅的方案。LVM把物理磁盘抽象成物理卷(PV)、卷组(VG)和逻辑卷(LV)三层,逻辑卷可以跨物理卷、随时在线扩大缩小。在lsblk里看到/dev/mapper/centos-root这种设备名,说明系统用了LVM。扩容路径通常是:扩展或新增物理卷(pvresize或pvcreate)→ 扩展卷组(vgextend或自动吸收新容量)→ 扩展逻辑卷(lvextend)→ 扩展文件系统(resize2fs或xfs_growfs)。
第三条路是加新磁盘。适用于根分区已经被LVM或者裸分区占满、不想动原有分区的情况。新磁盘插上后,创建分区、建PV、加入VG、扩展LV,然后把新增容量挂载到某个目录。这条路对生产系统最安全,因为全程不碰原分区表。
选择逻辑就一句话:有LVM优先走LVM,磁盘上没重要数据且分区是最后一个分区,可以直接扩;生产环境稳字优先,能加盘就不动原分区。
3. 完整实操:三条典型扩容路径拆解
3.1 路径A:新磁盘加入,从分区到挂载一步到位
新磁盘场景是最容易上手的,但新手很容易在分区起始扇区和格式化上栽跟头。
接到一块新盘,比如/dev/sdb,先确认系统认到了:
lsblk # 输出中可见 sdb 8:16 0 500G 0 disk然后创建分区。2T以内的盘用fdisk就够了,创建分区时一路默认,唯一要仔细看的是起始扇区。现代fdisk默认从2048扇区开始,对齐4K物理扇区,性能没问题。老手会直接敲n、回车、回车、回车,再敲w保存,就是分区表边界先对齐了再说。
fdisk /dev/sdb # 输入 n 新建分区 # 输入 p 主分区 # 默认起始扇区(直接回车) # 默认结束扇区(直接回车,使用整个磁盘) # 输入 w 写入分区表分区建好了,用partprobe让内核刷新分区表,然后格式化:
partprobe /dev/sdb mkfs.ext4 /dev/sdb1这里有个值得说道的细节:mkfs.ext4不是非得等partprobe执行完才做,但保险起见还是先刷新再格式化,否则偶尔会出现“设备不存在”的诡异报错。格式化时如果想指定block大小,用mkfs.ext4 -b 4096 /dev/sdb1,大文件存储场景下4K块大小性能更稳。
挂载并设置开机自动挂载:
mkdir /data mount /dev/sdb1 /data echo '/dev/sdb1 /data ext4 defaults 0 0' >> /etc/fstab最后验证一下fstab没写错,这一步必须做,写错了开机会直接进紧急模式:
mount -a df -h这套流程走下来,一个500G新数据盘就挂好了。我踩过的一次坑是写完/etc/fstab之后直接重启,结果因为UUID没对上,系统起不来。现在我的习惯是给分区创建文件系统前先记录UUID,或者干脆用blkid /dev/sdb1查看UUID,再拿UUID去写fstab,比写设备名靠谱得多。
3.2 路径B:已有分区扩容量,growpart + resize一步到位
这是最常被问到的场景:虚拟化平台或者云控制台上把磁盘从100G调成200G,lsblk里/dev/sda显示200G,但/dev/sda1还是100G,文件系统也还是100G。
解决思路分成两步:先扩分区,再扩文件系统。扩分区最推荐用growpart工具,它是云镜像环境标配的工具,专门用来把分区扩展到磁盘末尾,处理GPT和MBR都很稳。
# 安装工具(CentOS) yum install -y cloud-utils-growpart # 扩容分区:/dev/sda 的第1个分区扩展到磁盘末尾 growpart /dev/sda 1growpart执行完,lsblk里/dev/sda1应该已经变成新容量大小。如果环境里没有growpart,也可以用fdisk手动操作,但要注意方法:删除分区再重建,起始扇区必须和原来完全一样,结束扇区用默认值覆盖整块盘。这里风险高,我用fdisk手动扩分区时都会先拿parted的unit s print把当前起始扇区记到纸上,然后d删除、n重建、w保存。
分区扩完之后,文件系统还得跟上。ext4用resize2fs,xfs用xfs_growfs,两者不能互换:
# ext4 resize2fs /dev/sda1 # xfs(需要挂载点) xfs_growfs /这里要解释一下为什么xfs的用法不一样:ext4文件系统有个在线resize的能力,直接传设备路径就行;xfs文件系统设计上必须通过挂载点来扩展,而且xfs不支持缩容,只能扩容。所以xfs_growfs /data和xfs_growfs -d /这样的语法是针对挂载点的。
一条命令验证所有结果:
df -h如果df -h仍然显示老容量,多半是文件系统resize没生效,或者前面的分区扩容根本没成功。这一套操作在根分区(/挂载在/dev/sda1)上可以直接在线执行,不需要重启,唯一要注意的是操作期间一定保持电源稳定,别扩容扩到一半掉电。
3.3 路径C:LVM逻辑卷扩容,最优雅的在线扩容
环境里用LVM管理磁盘的,扩容路径相对要绕几个弯,但灵活度也高很多。典型场景:df -h显示/dev/mapper/centos-root快满了,lsblk里/dev/sda已经从100G变成200G,但/dev/sda2(PV所在分区)还是100G。
第一步先看PV和VG的现状:
pvdisplay vgdisplay lvdisplay正常情况你会看到PV大小为100G,VG大小为100G,LV大小为90G左右,剩余空间可能为0。此时需要让PV吸收磁盘新增的容量,前提是PV所在分区先扩好,也就是3.2里的步骤:
growpart /dev/sda 2 partprobe /dev/sda pvresize /dev/sda2pvresize执行完后,pvdisplay应该显示PV大小为200G,VG的Free空间也应该变成100G左右。接下来扩展LV:
# 给根逻辑卷增加50G lvextend -L +50G /dev/centos/root # 或者直接使用全部空闲空间 lvextend -l +100%FREE /dev/centos/root然后扩展文件系统:
# ext4 resize2fs /dev/centos/root # xfs xfs_growfs /LVM的妙处在于,扩展LV这一步可以完全在线上执行,业务不停。而且如果将来PV不够了,还可以再加一块新盘并vgextend进VG,再继续扩LV,根本不碰根分区。这是我认为LVM值得推荐的核心原因。
实际操作中有一个容易被忽略的细节:lvextend -L +50G和-l +100%FREE里的加号最好不要省略。-L 50G表示扩展到50G大小,-L +50G表示增加50G,写错单位直接导致LV是原大小还是新大小,差之毫厘谬以千里。我已经见过不止一次因为少了个加号把LV直接缩到50G导致文件系统损坏的案例。
4. 常见坑位与排查实录
4.1 磁盘状态正常却看不到新容量
现象:虚拟化平台或云控制台显示磁盘已经是200G,lsblk里磁盘大小也是200G,但fdisk -l /dev/sda里分区还是100G。
排查链路:先用growpart扩分区 →partprobe刷新 →lsblk确认。如果分区还是100G,检查分区表类型是不是GPT。有一种情况比较隐蔽:磁盘设备头部的分区表没有同步,partprobe之后会提示“Re-reading the partition table failed”,这种一般是因为分区正在使用(比如根分区挂载在/上),内核拒绝重新读取。
处理办法分两种。在线状态下的做法是fdisk中删除重建分区,但起始扇区保持不变,然后partprobe时加-s参数强制读取,很多fdisk交互操作时必须接受一定风险。如果系统允许短暂窗口,reboot是最快的解法。生产环境我会先检查有没有快照备份,再决定用在线还是重启方式。
4.2 resize2fs 提示 "couldn't find valid filesystem superblock"
现象:分区已经扩容到新容量,执行resize2fs /dev/sda1时直接报找不到superblock。
原因一般是分区扩容时起始扇区变了,文件系统的superblock位置错乱。这个在手动fdisk删分区重建时风险最高,如果新分区的起始扇区跟原来有一点点偏差,文件系统直接认不出来。
排查思路:先用e2fsck -f /dev/sda1检查文件系统,能修就修;修复不了,只能从文件系统层面恢复备份。所以我不厌其烦地强调:动分区边界前,备份。哪怕用dd把MBR和分区起始的几个扇区导出来都行,出问题至少有个回退依据。
预防方法其实很简单:能用growpart就不要手动fdisk,growpart会保留原来的起始扇区,只调整结束位置,从机制上避免这类问题。这个教训是我在某次生产环境手动扩容时花了一晚上才换来的,现在写出来希望大家直接绕开。
4.3 xfs文件系统扩容时的注意事项
xfs和ext4在扩容上的区别,值得单独拿出一个板块说。一个是xfs_growfs必须传挂载点,不能传设备路径,另一个是xfs不支持缩容,一旦格式化成分区就基本上没有回头路。
还有一点:xfs_growfs参数里有个-d选项,表示把文件系统扩展到设备的最大可用空间。比如挂在根目录的xfs文件系统,执行xfs_growfs -d /就能一次扩满。如果分区比较大,输出内容会显示before和after的数据块数,观察after的块数就能确认扩容是否生效。
在LVM场景里,LV扩展后、xfs文件系统扩容前,先确认LV确实变大了:
lvs xfs_growfs -d /xfs扩容不需要先卸载文件系统,在线就能完成。但注意,xfs扩容前最好用xfs_repair -n做一次只读检查,尤其在机器之前发生过非正常关机的情况下。
4.4 集群节点、容器节点和数据库机器的特别注意
最后分享几个我在不同业务场景里扩容时注意到的差异点。
数据库服务器上扩容,我的第一原则是优先扩展数据目录所在的文件系统,而不是扩展根分区。数据库文件通常都很大,默认块大小4K的ext4在存储大文件时效率不如xfs,所以新数据盘或扩容场景我会尽可能用xfs。另外数据库对IO延迟敏感,扩容操作不要选业务高峰做,resize2fs和LVM扩展虽然能在线,但大量元数据更新还是会短暂拖慢IO。
容器节点上扩容,关注点在于Docker数据目录(一般是/var/lib/docker)所在的文件系统。遇到过的情况是根分区明明还有几十G可用,但Docker的镜像层全放在根分区,容器日志把/打满了,结果整个节点上的容器集体异常。这时候直接扩根分区所在文件系统就好,但要在扩容后重启下容器运行时确认一切正常。
集群环境里,如果是共享存储或者分布式文件系统,磁盘扩容通常集中在客户端挂载的容量初始化上,服务器本地磁盘扩容反而简单。只要遵循“先备份、再分区、再文件系统、最后挂载校验”的顺序,基本不会出错。
5. 一段个人经验的小总结
做运维这些年,磁盘扩容是我写过最多的操作之一,但几乎每次都能遇到新情况。总结下来,最核心的心法是把“磁盘、分区、文件系统、挂载点”这四层关系时刻放在脑子里,每一层变化之后都要确认上一层已经同步。
df -h永远看的是文件系统层,lsblk看的是设备层,工具报错的时候先判断是哪一层出了问题:是分区表没刷新,还是文件系统没resize,还是LVM的PV没吸收新容量。这三层只要有一层断层,最终表现就是磁盘显示大了但可用空间没变大。
工具使用上,growpart和parted是我现在最依赖的分区操作工具,尽量少手动fdisk删重建。文件系统扩容要分清ext4用resize2fs、xfs用xfs_growfs、btrfs用btrfs filesystem resize max,千万别混用。
数据安全的底线也不能忘。无论操作看起来多简单,动分区和文件系统之前至少打个快照或做一次关键数据备份。这点说起来容易,但真到了凌晨三点磁盘告警的时候,能坚持住这个习惯的人其实不多。希望看完这篇的朋友,以后扩容都能从容一些。