ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

KVM虚拟机磁盘在线扩容全流程:从qcow2到LVM的文件系统扩展

KVM虚拟机磁盘在线扩容全流程:从qcow2到LVM的文件系统扩展 很多搞KVM虚拟化运维的人应该都有过这种体验业务跑得好好的突然监控报警说某台虚拟机磁盘使用率超过90%。我排了一圈短期内的日志和临时文件也没法清干净最稳妥的办法就是给磁盘扩容。但麻烦就麻烦在那台虚拟机跑着的是晚上不能停的关键业务。以前碰到这种情况只能约维护窗口关机、扩容、开机、重新挂载一套流程走下来半小时起步还得守着屏幕生怕机器起不来。后来我把KVM虚拟机磁盘的在线扩容整个流程练熟了才发现这事儿其实能在不停机的状态下做完操作得当的话业务侧几乎无感知。这篇东西我打算把整个在线扩容的链路从头到尾捋一遍包括宿主机侧怎么把虚拟磁盘变大、虚拟机内部怎么把分区和文件系统真正撑开、以及我踩过的一些坑。适合刚接手KVM环境没多久的运维、虚拟化平台管理员以及自己在家用KVM折腾实验环境的朋友。全程基于我实际在物理机上操作过的流程命令可以直接参考但请务必结合你自己的环境版本和磁盘布局来做调整。1. 在线扩容前先搞清楚三个问题能不能扩、怎么扩、扩完会怎样不是所有磁盘都能随随便便在线扩容。动手之前我建议你先想明白三件事虚拟磁盘格式是什么、虚拟机内部用的什么分区方案、文件系统类型是什么。这三个因素直接决定了扩容的路径和风险。先说虚拟磁盘格式。KVM环境下最常见的格式是qcow2还有raw。如果你用的是qcow2恭喜你这是对在线扩容最友好的格式之一宿主机上执行qemu-img resize就能在线扩大磁盘镜像qcow2的写时复制机制决定了它在镜像扩大时不会破坏已有的块映射。raw格式也能扩但raw文件本质上是一个完整镜像扩容其实就是把文件末尾补零操作简单但要注意raw文件不支持一些qcow2的快照特性后续做备份会麻烦一点。再说虚拟机内部的分区布局。这里有个很关键的判断你的虚拟机根分区到底是不是LVM。我个人强烈建议KVM虚拟机内部都用LVM来管理根分区原因很简单——LVM天生就是为在线扩展设计的它的物理卷、卷组、逻辑卷三层结构让扩磁盘-扩PV-扩LV-扩文件系统这条链路变得非常顺滑。如果你虚拟机内部直接用的是传统分区文件系统比如/dev/vda1直接是ext4或xfs没有套LVM在线扩容也不是不行但要手动改分区表大小稍微多几步而且分区表操作的风险比LVM高。然后是文件系统类型。目前主流Linux发行版默认的根文件系统基本是ext4或xfs。ext4用resize2fs在线扩容xfs用xfs_growfs在线扩容两者都能做到不卸载、不停机直接扩大。但有个死命令xfs只支持扩大不支持缩小所以千万别拿xfs的逻辑卷做缩容实验。如果你用的是btrfs、zfs这类文件系统扩容逻辑会完全不同本文讲的命令就不适用了。最后还有一个容易被忽略的virtio驱动。KVM虚拟机默认推荐的虚拟磁盘类型是virtio-blk它支持在线识别磁盘容量的变化也就是说宿主机把磁盘扩大后虚拟机内部不需要重启就能看到新容量。如果虚拟机还在用老式的IDE虚拟磁盘有些老系统镜像会默认IDE在线扩容后内核不一定能立刻感知容量变化可能得重启一次才生效那就不能称得上在线了。确认方法很简单在虚拟机里执行lsblk或者lspci | grep -i virtio看到vda设备名的基本就是virtio。2. 宿主机侧扩容操作让虚拟磁盘文件真正变大且不打断运行先强调一个前提做任何扩容操作前建议你确认一下虚拟机当前没有正在拍摄快照、没有备份任务在跑屏幕输出上也没有异常IO告警。在线扩容虽然不关机但也不算零风险操作尽量挑业务低峰期做。2.1 定位虚拟机的磁盘镜像第一步是在宿主机上找到虚拟机对应的磁盘镜像文件路径。你可以在宿主机上用virsh命令查看virsh list --all virsh dumpxml vm-name | grep -A 1 diskvirsh dumpxml的输出会明确告诉你磁盘文件的完整路径还有磁盘的总线类型bus type确认是virtio才能保证后续在线生效。我在实际操作中见过不少人图省事直接去/var/lib/libvirt/images/下面翻文件如果环境比较大、虚拟机迁移过这个目录不一定可靠。用dumpxml查出来的路径才是libvirt真正在用的那个。2.2 执行磁盘扩容qemu-img 还是 virsh blockresize宿主机侧扩容有两个入口一个是直接的qemu-img resize命令另一个是libvirt的virsh blockresize。我两种都用过差别在于virsh blockresize会顺便把libvirt内部的磁盘状态同步掉对运行中的虚拟机感知更友好一些。命令格式如下注意--size单位是KB这个是网上最容易载跟头的地方virsh blockresize --domain vm-name --path /path/to/vm-disk.qcow2 --size 52428800上面命令把磁盘扩容到大概50GiB52428800 KB如果原盘已经是40G想加10G可以用qemu-img resize的相对增长方式来加这样更不容易算错qemu-img resize /path/to/vm-disk.qcow2 10G用qemu-img resize时10G表示在现有基础上增加10G50G则表示直接把磁盘整体扩到50G。我个人的习惯是想加量的时候永远用相对写法因为绝对数值还要先看一眼当前大小多一步容易算错。2.3 resize 之后必须确认镜像完整性无论是用哪条命令扩完之后在宿主机上再跑一次检查确认虚拟磁盘确实比之前大了qemu-img info /path/to/vm-disk.qcow2看输出的virtual size字段是不是已经变成目标值。这里顺带提一个很多人不知道的细节qemu-img resize并不会直接lock住正在运行的虚拟机文件对于qcow2格式在虚拟机运行状态下扩容是相对安全的官方也支持这个操作但我不建议你在对raw格式做同样操作时完全放松警惕最好先确认没有其他进程在异常读写这个raw镜像。提示扩容虚拟磁盘和扩容文件系统是两回事。宿主机上这一步做完了虚拟机内部看到的只是一块变大了的裸磁盘分区表和文件系统还停在旧大小。下一章才是真正决定能不能在线用上这10G的关键。3. 虚拟机内部感受新容量分区表怎么在不重启的情况下更新正常情况下virtio磁盘容量变大之后虚拟机内部通过lsblk或者fdisk -l已经能看到新容量了。如果看不到先别急着重启虚拟机检查一下内核是否重新扫描了磁盘。最常用的方法是echo 1 /sys/class/block/vda/device/rescan或者用partprobe让内核重新读取分区表。不过老实说现代内核配合virtio容量变化通常会自动感知这一步大多数时候用不上。真正需要花心思的是分区表。虚拟机里面的磁盘通常是这样划分的vda1通常是/boot引导分区vda2通常是LVM的物理卷PV所在分区LVM卷组里再划分出/root、/home、/swap等逻辑卷在这种情况下宿主机扩容后新空间其实还是未分配状态好消息是LVM的分区比如/dev/vda2占用了磁盘的一部分空间而新空间直接接在分区后面。我们接下来要做的就是把/dev/vda2这个分区本身撑大让它吃满新空间然后再把这个变化同步给LVM。这里有个必须注意的分区表格式问题。老一点的环境还在用MBR分区表MBR对单分区大小的上限是2T如果你的虚拟磁盘要扩到超过2TMBR会直接不认你得提前规划转GPT。现在新装的Linux系统基本都是GPT用parted -l或者fdisk -l看一眼就能分辨。更新分区表最稳的工具是growpart它出自cloud-utils包专门干把分区扩展到磁盘末尾这件事比手动删分区重建安全得多。用法很直观growpart /dev/vda 2这条命令的意思是把/dev/vda上的第2个分区扩展到磁盘末尾。它做得相当保守会先检查分区表边界如果已经在末尾会提示不用动。执行完之后再用partprobe /dev/vda刷新分区表。我头一回操作的时候不知道有growpart手动用fdisk删掉旧分区再重新建一个同起始扇区、新结束扇区的分区虽然也能成功但心惊胆战万一中途断电或者起始扇区对不上数据就全没了。所以这里我强烈建议能用growpart就别手动删分区。如果你所在发行版没装用包管理器装一下即可CentOS系是yum install cloud-utils-growpartDebian系是apt install cloud-guest-utils。如果你的场景不是LVM而是直接把分区挂成文件系统也就是没有PV/LV那一层那么growpart之后要紧接着用resize2fs /dev/vdaX或者xfs_growfs /挂载点去扩大文件系统顺序绝对不能反。是LVM的场景则继续往下走。4. LVM的接力棒物理卷扩展、卷组空间分配与逻辑卷调整的顺序如果虚拟机内部用的是LVM那么你距离真正用上新空间只差三步。这三步的顺序很严格错了就会报错或者空间分配不均匀。4.1 第一步pvresize 让物理卷吃满分区分区/dev/vda2更新之后LVM还不知道它变大了需要执行pvresize /dev/vda2执行完可以用pvdisplay确认一下输出里的PE Size和Total PE应该已经变大PV Size会显示为新的分区大小。这一步相当于告诉LVM嘿物理卷现在多了一块地。4.2 第二步vgs / lvs 确认卷组的空闲空间物理卷变大后卷组VG里应该自动多出一部分空闲空间。查看命令vgs lvsvgs输出里的VFree字段就是当前卷组可用空间这个值如果已经大于0说明LVM已经收到了新空间。如果这里显示还是0说明pvresize没生效回头检查一下分区表有没有真正刷进去。4.3 第三步lvextend 把空间分配给目标逻辑卷现在到了最核心的一步。假设我想把新空间全部加到根分区所在逻辑卷lvextend -l 100%FREE /dev/mapper/vg0-root这里-l 100%FREE的意思是分配卷组里所有空闲空间给该逻辑卷如果你只想给一部分可以用-L 5G这种相对写法。注意lvextend只扩大逻辑卷本身文件系统还是旧大小所以紧接着是最后一步。扩展完逻辑卷后文件系统在线扩容这道工序就登场了。文件系统的差异在这里体现得很明确ext4和xfs是两条完全不同的命令用错了会直接报wrong fs type之类的错误。4.4 文件系统类型分岔口resize2fs 还是 xfs_growfs先确认你的文件系统类型。上面已经有lsblk -f的输出里面的FSTYPE列会写清楚每个挂载点的文件系统类型。如果是ext4resize2fs /dev/mapper/vg0-rootresize2fs不指定大小参数时自动扩展到逻辑卷的最大空间。注意resize2fs在ext4挂了之后是可以对挂载中的文件系统安全执行的这是ext4的在线扩容特性不需要卸载。如果是xfsxfs_growfs /xfs_growfs的参数是挂载点也就是/而不是设备路径这是和resize2fs最大的区别。执行时它会把挂载中的xfs文件系统扩展到设备最大容量同样不需要卸载。xfs和ext4在线扩这种不掉线的能力是生产环境敢做在线扩容的真正底气。这里再多说一句如果你的某个逻辑卷挂着swap千万别去扩它swap也不需要扩保持原样就好。我见过有人把swap逻辑卷扩了之后系统挂载swap时反而出问题的案例主要是UUID和swap签名错乱导致的这类问题排查起来非常费时间。5. 执行后的验证环节确认扩容真的生效且数据没坏这一章看起来简单但我认为它跟扩容操作本身一样重要。虚拟化运维里最怕的不是操作失败而是操作貌似成功却留下隐患。5.1 容量验证三板斧扩容完成后我会按顺序执行三条命令做完整确认lsblk df -h pvslsblk看设备层级关系vda、vda2、vg0-root之间的尺寸是否已经成链条式变大了df -h看的是最终文件系统可用空间pvs看一下物理卷状态有没有出现unknown device之类的异常提示。如果df -h显示的还是旧容量但lsblk已经显示逻辑卷变大了说明最后一步resize2fs或xfs_growfs没有执行成功或者执行顺序错了。回去补跑一次即可不需要从头再来。5.2 文件系统一致性检查线上环境做扩容后不做一次文件系统检查总归不踏实。ext4可以用e2fsck -f来强制检查但我建议在业务低峰期做而且检查过程会短暂占用IO。xfs则可以用xfs_repair -n做只读检查。前面说过在线扩容总体安全但每个环境的数据状态不同多一步检查多一分安心。这里也提个经验如果你在扩容前做过LVM层的快照比如lvconvert --merge场景、或者lvcreate -s快照那么在线扩容时要格外注意快照和原逻辑卷的关系。LVM快照本质上是追踪原始逻辑卷的差异如果你在快照存在的情况下直接扩大文件系统快照的大小也要对应调整否则快照会因空间不足而失效。我踩过一次这个坑之后习惯是在扩容前删掉不用的LVM快照确保链路干净。5.3 扩容后的业务验证我最习惯的方式是到跑在虚拟机里的业务日志里看一眼确认没有IO报错如果虚拟机里跑着数据库最好再执行一次简单的读写测试。虽然在线扩容不影响文件系统挂载状态但底层块设备大小发生变化这件事对于有些对IO路径敏感的应用比如Oracle ASM、某些高性能数据库来说可能会触发识别问题。如果你管的是数据库虚拟机建议扩容后盯一段时间监控别扩完就走人。6. 我在实际扩容中踩过的坑快照、Windows虚机、以及分区表残留在线扩容这件事光看文档步骤走一般不会有大问题但真实环境总是比教程复杂。我把这几个坑单独整理出来都是实际环境中验证过的经验。6.1 qcow2快照与resize的冲突如果你管理的虚拟机是qcow2格式而且存在内部快照virsh snapshot-list能看到qemu-img resize会直接报错不允许扩容。解决方法是先删除快照再扩容。但是删除快照本身是个IO密集操作尤其快照累积了很大的增量数据合并过程可能让虚拟机卡顿一下生产环境要谨慎评估窗口。如果在用的不是内部快照而是libvirt外部快照external snapshot那么磁盘镜像会形成backing chain这时候resize操作要小心——应该对最顶层的镜像执行resize但底层镜像的大小可能不一致会导致麻烦。这种情况我建议先把快照链提交合并了再扩容别硬来。6.2 Windows虚拟机的在线扩容细节KVM环境里也有不少Windows虚机。Windows的情况比Linux复杂Windows磁盘驱动虽然通常也能识别容量变化但老一些的Windows Server比如2008、2012在qemu-img resize之后经常出现磁盘管理里看得到未分配空间但无法扩展卷的情况。这是因为Windows的在线扩容对分区表刷新时机很敏感有时候需要去设备管理器里禁用再重新启用磁盘或者用diskpart的rescan命令扫一遍。还有一点Windows虚拟机建议提前装好qemu-guest-agent有了它宿主机和虚拟机之间的通信会顺畅很多很多和磁盘、网络相关的外部变更都能被guest感知得更快。不装也能跑但遇到扩容后系统不认新磁盘的问题时排查成本高不少。6.3 growpart 执行后分区表不生效我遇到过一种情况在虚拟机内部执行growpart /dev/vda 2显示成功了但lsblk看分区大小还是没变。这时候通常是因为内核没有重新读取分区表。解决思路是先partprobe如果还不行试一下partx -u /dev/vda。如果连partx都不行那就得在确认业务可中断的前提下来一次重启不然内核手上拿的还是旧分区表。这类问题在MBR分区表的老机器上更常见GPT分区表配合现代内核一般很少碰到。所以如果你的环境跑着很老的发行版建议扩容前就把内核和util-linux升级一下。6.4 在线扩容后的IO高峰最后提醒一个很多人忽略的点扩容后如果文件系统里原本有大量数据碎片resize2fs或xfs_growfs在做元数据调整的瞬间会产生短暂的IO高峰。对于负载很高的业务虚拟机可能会出现一次轻微的IO抖动。我的习惯是扩容时间尽量避开整点任务、备份任务、定时批处理哪怕在线扩容再安全也别跟业务高峰硬碰。我在实际维护KVM环境的这些年间先后经历过用fdisk手动改分区表扩容、用LVM在线扩根分区、处理Windows虚机扩容后不认盘这些场景。回头再看在线扩容这事本身并不难难的是对整个链路有清晰的认识宿主机镜像变大只是第一步虚拟机内部的分区、物理卷、逻辑卷、文件系统每一层都需要对应更新顺序不能乱工具不能错验证不能省。最后分享一个我个人的经验习惯每次做完在线扩容我都会把虚拟机的配置做一个备份具体操作是把当前的XML配置导出来保存一份做法是执行virsh dumpxml vm-name vm-name.xml.bak。这个文件很小却能让你在后续误操作导致虚拟机定义丢失时快速恢复属于花十秒钟买一份保险的做法。在线扩容用到的核心链路以LVM加ext4/xfs的组合最为顺手如果你还没给KVM虚拟机内部的系统盘规划过LVM布局后面新建系统时强烈建议优先考虑这个方案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进