1. 线上扩容这件事,先想清楚再动手
机房里的服务器磁盘告警,是运维最不想看到的画面之一。业务跑得好好的,日志突然开始报No space left on device,数据库写入卡住,应用开始 5xx。这个时候你手上只有两条路:停机换盘、重装迁移,或者在线把文件系统撑大。前者意味着业务中断和一堆协调工作,后者只要方案对、命令熟,几分钟就能把空间补上,业务几乎无感。这篇内容聊的就是后者——在 openEuler 上对 LVM 分区类型的文件系统做在线扩容的完整实战。
“文件系统在线扩容”这六个字听起来有点抽象,拆开看其实就三件事:把底层块设备的可用空间变大、把 LVM 逻辑卷的容量变大、把逻辑卷上的文件系统变大。这三步缺一不可,而且顺序不能乱。LVM 分区类型之所以值得单独拿出来讲,是因为它把“物理磁盘”和“文件系统”之间加了一层抽象,正是这层抽象让我们可以在业务不停机的前提下调整容量。如果你用的是传统裸分区直接格式化,扩容往往要卸载、重建分区表、再恢复数据,风险和停机时间完全不是一个量级。
这篇文章适合谁看?我把它定位成三类人:第一类是会装系统、会敲df -h,但没真正在线上做过扩容的新手;第二类是做过扩容但踩过坑、想系统梳理一下排查思路的初中级运维;第三类是准备 openEuler 认证或者课程小作业,需要一个能照着复现的完整案例。文中所有命令都经过实机验证,虚拟机的盘符、卷组名、逻辑卷名我会用通用写法,你按自己环境替换即可。我不会只给你一串命令,而是把每一步“为什么这么做”“做之前要确认什么”“做完怎么验证”都讲透,这样换个环境你也能自己判断。
先泼一盆冷水:在线扩容不是零风险操作。它风险低,但不等于没有。分区表操作失误、扩错逻辑卷、文件系统类型判断错,任何一个都可能让数据出问题。所以我在正文里会反复强调检查、备份和验证,这些看起来“啰嗦”的步骤,恰恰是区分老手和新手的地方。下面进入正题。
2. 整体设计与思路拆解
2.1 为什么选 LVM 而不是直接扩分区
很多人会问,磁盘不够了,直接把分区调大不就行了?理论上可以,但传统分区方案有几个硬伤。第一,分区必须连续。你不可能把一个分区从磁盘中间开始往前扩,空闲空间必须在它后面且相邻,否则就得挪数据,而挪数据基本等于停机。第二,分区表修改后需要内核重新读取,过程中稍有闪失就是分区表损坏。第三,多个磁盘的空间没法合并使用,一块盘满了另一块盘还空着,你只能干瞪眼。
LVM 把这个问题解决了。它在物理磁盘和文件系统之间插入三层结构:物理卷 PV、卷组 VG、逻辑卷 LV。PV 是磁盘或分区的“LVM 化”封装,VG 是这些 PV 组成的存储池,LV 是从池子里切出来的“虚拟分区”。扩容的时候,你只需要从 VG 的剩余空间里再切一块给 LV,或者往 VG 里加新 PV,完全不用关心底层物理磁盘是否连续。对文件系统来说,它看到的 LV 就是一个连续块设备,容量变大了,它只管在这块变大的设备上扩展自己的元数据即可。
这就是在线扩容能成立的根本原因:扩容动作发生在 LVM 层和文件系统层,物理分区表可以不动,业务挂载可以不断。我在实际项目里做过对比,同样是把一个 100G 的业务分区扩到 200G,裸分区方案平均要停机 20 到 40 分钟,还要承担数据搬迁风险;LVM 方案从确认到验证完成,熟练的话 5 分钟内结束,业务侧最多感知到一次短暂的 IO 抖动。
2.2 三种扩容路径,先判断你属于哪一种
在线扩容不是一个固定流程,它取决于“空间从哪来”。我把常见情况归成三类,你可以对照自己的环境先做判断。
| 场景 | 特征 | 核心动作 | 复杂度 |
|---|---|---|---|
| VG 有剩余空间 | vgs显示 VFree 大于 0 | lvextend直接扩 LV | 低 |
| VG 空间不足,但磁盘有未分配空间 | 同一块盘分区没占满 | 扩分区、pvresize、再扩 LV | 中 |
| VG 空间不足,需要加新盘 | 物理机加盘或虚拟机加虚拟磁盘 | pvcreate、vgextend、再扩 LV | 中 |
第一种最省事,也是课程作业里最常见的设定。第二种容易在“分区表重建”这一步翻车,尤其是 MBR 分区表,删除重建时起始扇区必须和原来完全一致,否则文件系统直接认不出来。第三种在虚拟化环境里很常见,但很多人会遇到“新盘加上了,系统里看不到”的问题,需要手动触发 SCSI 总线扫描。
判断方法很简单,登录机器先跑三条命令:
df -hT vgs lsblkdf -hT告诉你目标挂载点的文件系统类型和当前容量,vgs告诉你卷组还有多少空闲,lsblk告诉你磁盘和分区的整体布局。这三条命令的输出,基本决定了你后面走哪条路。我养成的习惯是把这三条命令的输出直接贴到操作记录里,扩容前后各留一份,出问题的时候对比起来非常直观。
2.3 文件系统类型决定了最后一步怎么写
LVM 扩容只负责把“块设备”变大,真正让df数字变化的是文件系统层的扩展工具。openEuler 上常见的文件系统是 XFS 和 ext4,两者的扩容命令完全不同,而且有一个关键差异必须记住。
XFS 的扩展工具是xfs_growfs,它接收的参数是挂载点,不是设备路径。也就是说你要写xfs_growfs /data,而不是xfs_growfs /dev/vg_data/lv_data。XFS 只支持在线增大,不支持缩小,这一点在设计阶段就要想清楚,别指望以后能缩回去。ext4 的扩展工具是resize2fs,它既可以接收设备路径,也可以接收挂载点,并且支持在线扩容,缩小则需要先卸载。
还有一个容易忽略的点:LVM 自带的lvextend有一个-r参数,可以在扩 LV 的同时自动调用对应的文件系统扩展工具。这个参数在 openEuler 自带的 lvm2 版本里是可用的,用起来很省事,但我一般不建议新手一上来就用。原因是你得先搞清楚它到底调用了什么、有没有成功,否则一旦自动扩展失败,你连问题出在哪一层都定位不到。我的建议是前期老老实实分两步走,等对流程足够熟了,再用-r提效。
3. 核心细节解析与实操要点
3.1 PV、VG、LV 三层结构,用仓库类比一次讲透
很多人命令敲得挺顺,但一遇到报错就懵,根本原因是没建立起 LVM 的结构模型。我用仓库来打个比方。
物理磁盘或者分区,经过pvcreate处理之后变成 PV,相当于一块“已经登记入库的存储地块”。多个 PV 用vgextend加进同一个 VG,VG 就是一个“大仓库”,它把所有地块的面积加在一起统一管理。LV 是从仓库里划出来的一块“独立库房”,分配给某个业务使用,格式化成文件系统后挂载到目录上。业务看到的是库房,不是地块,所以库房要变大,你可以从仓库剩余面积里再划一块给它,也可以先往仓库里新增地块。
这个模型解释了几件事。第一,lvextend能不能成功,取决于 VG 这个仓库还有没有余量,用vgs看VFree那一列。第二,仓库没余量的时候,你要么加新地块(新盘做 PV 再vgextend),要么把现有地块的实际面积扩大(分区扩容后pvresize)。第三,pvs看的是地块层面,vgs看的是仓库层面,lvs看的是库房层面,三条命令各管一层,排查问题时要按层往下看。
还有一个概念叫 PE,物理扩展块,是 LVM 分配的最小单位,默认 4MiB。lvextend -l +100%FREE里的-l就是按 PE 个数来扩,-L则是按容量来扩。两者区别在于,用-L指定一个不是 4MiB 整数倍的容量时,LVM 会自动向上取整到 PE 边界,实际扩容可能比你要的多一点点。日常用-L就够了,只有在做精确容量规划时才需要关心 PE。
3.2 扩容前的五项检查,一项都不能省
我见过太多事故,根源都是“上来就敲命令”。在线扩容前,这五项检查请务必做完,养成肌肉记忆。
第一项,确认目标挂载点和设备对应关系。df -hT /data看文件系统类型和挂载点,lvs看 LV 路径,两者要能对上。别出现“以为在扩 /data,实际扩了 /var”这种低级错误。第二项,确认 VG 剩余空间。vgs看 VFree,如果不够,先规划是扩分区还是加新盘。第三项,确认文件系统类型。df -hT的输出里 Type 那一列,XFS 和 ext4 的后续命令不一样。第四项,确认备份或快照。有条件的对 LV 做一次 LVM 快照,或者至少把关键数据备份到其他存储。第五项,确认业务低峰期。扩容本身很快,但resize2fs在数据量大、碎片多的情况下可能耗时较长,放在低峰期操作心里踏实。
注意:如果目标 LV 上存在 LVM 快照,扩容前建议先评估快照的 COW 空间是否充足。原 LV 扩容本身通常不受影响,但快照空间不足会导致快照失效,别在扩容之后才发现备份没了。
检查命令我习惯一次性跑完,输出留档:
df -hT /data lvs vgs pvs lsblk3.3 场景一:VG 还有余量,两条命令收工
这是最理想的情况。假设目标挂载点是/data,对应的 LV 是/dev/vg_data/lv_data,文件系统是 XFS,VG 还有 50G 空闲,现在要扩 20G。
第一步,扩逻辑卷:
lvextend -L +20G /dev/vg_data/lv_data这里-L +20G的加号很关键。带加号表示“在现有基础上增加 20G”,不带加号表示“把总容量设置为 20G”。后者如果当前容量已经大于 20G,命令会直接报错拒绝执行,算是一种保护,但新手很容易看错。如果你想一次性把 VG 剩余空间全用掉,用lvextend -l +100%FREE /dev/vg_data/lv_data。
第二步,扩展文件系统。XFS 用挂载点:
xfs_growfs /dataext4 用设备路径或挂载点都行:
resize2fs /dev/vg_data/lv_data执行完再跑一次df -hT /data,看到容量变成扩容后的数字,这一步才算真正完成。我见过有人只做了lvextend就以为完事,结果df没变化,回头查了半天,其实就是漏了文件系统扩展这一步。记住:lvs变大只代表块设备变大,df变大才代表文件系统变大。
3.4 场景二:VG 没余量,先扩分区再 pvresize
假设磁盘/dev/sdb上有一个分区/dev/sdb1做成了 PV,加入了vg_data,但这块盘还有未分配空间。这时候不能直接lvextend,得先把分区撑大。
分区扩容用fdisk或gdisk。核心操作是:删除原分区、以相同起始扇区重建一个更大的分区、保存退出。听起来吓人,但只要起始扇区一致,分区上的文件系统数据不会丢。操作前务必确认分区表类型,MBR 用fdisk,GPT 用gdisk,两者命令不完全一样。
进入fdisk /dev/sdb后,先按p打印分区表,记下/dev/sdb1的 Start 扇区值,这个数字是命根子。然后按d删除分区,按n新建分区,类型选主分区,分区号保持一致,First sector 直接回车用默认值(通常就是原来的起始扇区,但一定要核对),Last sector 输入更大的值或者直接回车用全部剩余空间。最后按p再确认一遍 Start 扇区没变,按w保存。
保存后内核的分区表可能还没更新,需要通知内核重新读取:
partprobe /dev/sdb如果partprobe报设备忙,可以用partx -u /dev/sdb替代,或者重启(线上环境一般不选重启)。分区表更新后,用lsblk确认/dev/sdb1的容量已经变大,然后执行:
pvresize /dev/sdb1pvresize的作用是让 PV 重新读取底层分区的实际大小,把新增空间纳入 VG。执行完再跑vgs,你会看到 VFree 变大了,接下来就回到场景一,lvextend加文件系统扩展。
注意:如果分区表是 MBR 且分区已经用到磁盘末尾,没有后续空闲空间,那这条路走不通,只能加新盘。另外,
pvresize只能扩容不能缩容,缩小 PV 需要先把数据迁走,操作风险高,不在在线扩容的讨论范围内。
3.5 场景三:虚拟机加新盘,别忘了扫描总线
虚拟化环境里扩容,常见的做法是给虚拟机热添加一块虚拟磁盘,然后在系统里把它加入 VG。流程是:lsblk确认新盘符,pvcreate初始化,vgextend加入卷组,最后lvextend加文件系统扩展。
pvcreate /dev/sdc vgextend vg_data /dev/sdc lvextend -L +100G /dev/vg_data/lv_data xfs_growfs /data但很多人卡在第一步:虚拟化平台显示磁盘已经添加成功,系统里lsblk就是看不到新盘。这不是 openEuler 的问题,是 SCSI 总线没有重新扫描。解决办法是遍历扫描所有 SCSI host:
for host in /sys/class/scsi_host/host*/scan; do echo "- - -" > $host; done执行后再lsblk,新盘一般就出来了。如果用的是多路径环境,还需要执行multipathd resize map让多路径层重新识别容量。这一步在物理机接新盘、存储扩容 LUN 的场景里同样适用,属于通用技巧,值得记下来。
4. 实操过程与核心环节实现
4.1 一次完整的扩容现场记录
我把一次真实操作过程完整记下来,你可以对照自己的环境复现。环境设定:openEuler 系统,目标挂载点/data,LV 路径/dev/vg_data/lv_data,文件系统 XFS,VG 剩余空间充足,计划扩容 30G。
第一步,采集扩容前基线:
[root@node01 ~]# df -hT /data Filesystem Type Size Used Avail Use% Mounted on /dev/mapper/vg_data-lv_data xfs 100G 82G 18G 83% /data [root@node01 ~]# vgs VG #PV #LV #SN Attr VSize VFree vg_data 1 1 0 wz--n- 200.00g 100.00g [root@node01 ~]# lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert lv_data vg_data -wi-ao---- 100.00g从输出能读出三个关键信息:文件系统是 XFS,当前 100G 用了 83%,VG 还剩 100G,LV 当前 100G。VG 余量远大于要扩的 30G,走场景一。
第二步,扩逻辑卷:
[root@node01 ~]# lvextend -L +30G /dev/vg_data/lv_data Size of logical volume vg_data/lv_data changed from 100.00 GiB (25600 extents) to 130.00 GiB (33280 extents). Logical volume vg_data/lv_data successfully resized.注意输出里的 extents 变化,从 25600 变成 33280,差值 7680 个 PE,每个 4MiB,正好 30G。这一步成功只代表块设备变大了。
第三步,扩展 XFS 文件系统:
[root@node01 ~]# xfs_growfs /data meta-data=/dev/mapper/vg_data-lv_data isize=512 agcount=4, agsize=6553600 blks = sectsz=512 attr=2, projid32bit=1 = crc=1 finobt=1, sparse=1, rmapbt=0 = reflink=1 data = bsize=4096 blocks=26214400, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0, ftype=1 log =internal log bsize=4096 blocks=16384, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =none extsz=4096 blocks=0, rtextents=0 data blocks changed from 26214400 to 34078720输出里的data blocks changed就是文件系统实际扩展的证据。这个命令执行时间取决于文件系统大小和服务器性能,100G 级别基本是秒级完成。
第四步,验证:
[root@node01 ~]# df -hT /data Filesystem Type Size Used Avail Use% Mounted on /dev/mapper/vg_data-lv_data xfs 130G 82G 48G 64% /data [root@node01 ~]# lvs LV VG Attr LSize lv_data vg_data -wi-ao---- 130.00gdf显示 130G,使用率从 83% 降到 64%,lvs也是 130G,两层都对齐了,扩容完成。整个过程业务侧挂载没有中断,应用日志里也没有异常。
4.2 容量计算:到底该扩多少
扩容不是拍脑袋加个数,容量规划错了,过两周又告警。我的算法是:先看当前使用率和增长速度,再留出安全余量。
假设/data当前 100G,已用 82G,过去 30 天从 60G 涨到 82G,月增长约 22G。如果希望这次扩容后至少能撑 6 个月,需要额外空间约 132G,加上 20% 安全余量,大约 160G。这时候你要判断:VG 剩余 100G 不够,得先加盘或扩分区,而不是硬扩 30G 图省事。
另一个要算的是文件系统扩展的耗时。XFS 扩容是元数据操作,通常很快,但在超大文件系统(比如几十 TB)上也会占用一定 IO。resize2fs在处理 ext4 时如果启用了online_resize,也是在线完成,但数据块多的时候耗时会明显增加。我的经验是,百 G 级别随便操作,T 级别尽量安排在业务低峰,并且提前和业务方打好招呼。
还有一点,lvextend -L +30G里的 30G 是按 GiB 计算的,和df显示的 G 口径一致,不用做单位换算。但如果你的监控系统按 GB(10 的 9 次方)统计,两者会有约 7% 的差异,做容量报表时要注意口径统一,别自己吓自己。
4.3 用 -r 参数提效,但要清楚它做了什么
流程熟练之后,可以用lvextend -r把两步合成一步:
lvextend -r -L +30G /dev/vg_data/lv_data-r的含义是 resize filesystem,lvm2 会根据 LV 上的文件系统类型自动调用xfs_growfs或resize2fs。用起来确实爽,但我建议你在两种情况下不要用它:一是文件系统类型不明确的时候,二是第一次在生产环境操作的时候。原因很简单,自动化的前提是判断准确,一旦它判断错了文件系统或者调用失败,你看到的只是一个报错,排查起来比自己分两步走更费劲。
我自己的习惯是,测试环境用-r练手,生产环境前几次仍然分两步,等对输出足够熟悉了再切到-r。这不是保守,是给故障排查留出清晰的边界。
5. 常见问题与排查技巧实录
5.1 扩容后 df 没变化,按层往下查
这是最高频的问题。lvextend显示成功,df还是原来的数字。排查顺序要按 LVM 的层级来,别乱猜。
先看lvs,确认 LV 的 LSize 有没有变大。如果没变大,说明lvextend根本没生效,回去看命令有没有写错 LV 路径、VG 空间够不够。如果lvs变大了但df没变,说明文件系统层没扩,大概率是漏了xfs_growfs或resize2fs。如果文件系统命令也执行了但还是没变,检查是不是扩错了挂载点,或者文件系统类型判断错了,比如实际是 ext4 却用了xfs_growfs,命令会直接报错而不是静默失败。
我把这个排查路径整理成一张表,出问题时按顺序对照:
| 检查项 | 命令 | 正常表现 | 异常含义 |
|---|---|---|---|
| LV 容量 | lvs | LSize 已增大 | 未增大说明 lvextend 未生效 |
| VG 余量 | vgs | VFree 相应减少 | 未减少说明扩容未动 VG |
| 文件系统 | df -hT | Size 已增大 | 未增大说明文件系统未扩展 |
| 挂载关系 | findmnt /data | 设备路径与 LV 一致 | 不一致说明扩错了对象 |
5.2 xfs_growfs 报错“不是挂载点”
这个报错很典型,完整信息类似xfs_growfs: /dev/vg_data/lv_data is not a mounted XFS filesystem。原因就是你传了设备路径,而 XFS 要求传挂载点。改成xfs_growfs /data即可。反过来,有些人用resize2fs /data也报错,那是因为 ext4 虽然支持挂载点参数,但某些老版本对路径解析有差异,稳妥起见 ext4 统一用设备路径。
还有一个变种:挂载点路径写错,比如实际挂载在/data01,你写了/data。xfs_growfs会报找不到挂载点或者不是 XFS。用findmnt确认一下真实挂载点,别凭记忆。
5.3 partprobe 失败与分区表不生效
分区扩容后partprobe报Device or resource busy,通常是因为分区正在被 LVM 或文件系统占用。这时候可以尝试partx -u /dev/sdb单独更新指定设备的分区表,或者用blockdev --rereadpt /dev/sdb。如果都不行,而你又不能重启,那就只能放弃在线扩分区这条路,改用加新盘的方式绕过。
还有一种情况是分区表改了但lsblk显示的大小没变,说明内核没接收到更新。这时候千万别急着重启,先确认fdisk -l /dev/sdb看到的分区大小是不是新的。如果fdisk看到的是新大小而lsblk是旧的,就是内核缓存问题,用前面说的partx或blockdev刷新即可。
注意:修改分区表期间绝对不要重启或断电,也不要在分区表未确认的情况下执行
pvresize。起始扇区一旦写错,文件系统超级块就对不上,恢复起来非常麻烦。
5.4 新盘识别不到与多路径的坑
虚拟机加盘后lsblk看不到,先扫 SCSI 总线,前面给过循环命令。如果扫完还看不到,检查虚拟化平台那边磁盘是不是真的挂到了这台虚机上,有没有挂错控制器。物理机接新盘的话,可能需要进 RAID 卡管理界面做逻辑盘配置,操作系统层面才能看到。
多路径环境更绕一点。存储端扩容 LUN 之后,操作系统层面lsblk可能还是旧容量,因为多路径层缓存了设备大小。需要先multipathd resize map <mpath>,再pvresize。如果顺序反了,pvresize读到的还是旧大小,你会以为扩容没生效。这个坑我在存储扩容项目里踩过一次,折腾了半小时才发现是多路径缓存的问题。
5.5 扩容失败能不能回滚
先说结论:文件系统扩容基本不可逆,尤其是 XFS,压根不支持缩小。ext4 理论上可以resize2fs缩小,但要求先卸载、先做文件系统检查,而且缩小的容量必须大于已用数据量,生产环境几乎不会这么干。
所以扩容前的备份不是形式主义。LVM 快照是一个轻量方案,扩之前给 LV 打个快照,万一文件系统扩展出问题,可以从快照恢复。但快照本身要占 VG 空间,扩容前要算好余量,别快照把 VG 空间吃光导致扩容失败。更稳妥的做法是关键数据异地备份,快照只作为快速回滚的补充手段。
如果只是lvextend扩多了想收回来,在文件系统还没扩展的前提下,可以用lvreduce把 LV 缩回去,但这一步风险极高,命令会反复警告,一旦 LV 上已经有文件系统且大小不匹配,数据直接损坏。我的建议是:扩之前算清楚,扩之后别回头,把精力放在容量规划上,而不是事后补救。
6. 我踩过的坑与几条实操建议
先说一个我早期的失误。有次给一台测试机扩/data,df -hT看到的是 XFS,我顺手就敲了xfs_growfs /data,结果报错。查了半天才发现,那台机器的/data其实是 ext4,df -hT输出里我看串了行,把另一块盘的 Type 当成了它的。从那以后我养成一个习惯:扩容前把目标挂载点单独跑一次df -hT /data,只输出一行,不给看错的机会。
另一个坑是lvextend -L 200G和-L +200G混用。有次想扩 200G,结果手滑写成了不带加号,命令直接把 LV 设成 200G,而它原本就是 200G,等于什么都没做,我还纳闷为什么df没变化。带加号和不带加号,语义完全不同,敲命令的时候多看一眼,能省掉很多无效排查。
还有一条关于验证的建议。扩容完成后,别只看df,至少跑一遍lvs、vgs、lsblk,确认三层数据一致。有条件的话,往目标目录写一个测试文件再删掉,确认读写正常。我见过扩容后文件系统只读的情况,原因是底层块设备有 IO 错误,df看着正常但业务写入失败,这种问题只有实际写一次才能发现。
最后说容量规划的体会。扩容这件事,最怕的不是操作本身,而是“扩完没多久又满”。我现在做扩容,一定会顺手看一眼历史增长曲线,把扩容容量和业务增长对齐,而不是只解决眼前的告警。如果 VG 余量本身就不多了,我会一次性把新盘加上、把 VG 撑大,避免一个月内做两次操作。在线扩容是救火手段,但真正省心的做法,是让容量始终跑在业务前面。