适用场景
- 基于 QEMU 运行的 ARM 架构 Kylin V10 虚拟机(含 LVM 逻辑卷管理)
- 虚拟磁盘格式为 qcow2,需从宿主机扩展磁盘后,在虚拟机内同步扩容根分区
- 覆盖XFS(Kylin V10 默认)和ext4两种文件系统场景
前置说明
- 核心逻辑:磁盘扩容需分「宿主机扩展虚拟磁盘文件」和「虚拟机内扩展分区 / LVM / 文件系统」两步,缺一不可。
- 数据安全:操作前务必在宿主机备份虚拟磁盘(避免分区修改失误导致数据丢失),备份命令:
# 宿主机执行,替换路径为实际虚拟磁盘路径
cp /data/arm_kylin_10.0.2.15_node001/kylin_v10_sp3_arm64.qcow2 /data/arm_kylin_10.0.2.15_node001/kylin_v10_sp3_arm64_backup.qcow2
- 关键工具:
- 宿主机:
qemu-img
(扩展 qcow2 文件) - 虚拟机:
fdisk
(分区管理)、pvresize/lvextend
(LVM 扩容)、xfs_growfs/resize2fs
(文件系统扩容)
第一步:宿主机操作 —— 扩展虚拟磁盘文件
注:启动/停止/安装脚本
root@i-kg2tav5y:/data/arm_kylin_10.0.2.15_node001# cat start_kylin.sh
#!/bin/bash
ISO_FILE="/data/iso/Kylin-Server-V10-SP3-2403-Release-20240426-arm64.iso"
DISK_FILE="kylin_v10_sp3_arm64.qcow2"
MEM_SIZE="8G"
CPU_CORES="4"
SSH_PORT="2222"
VNC_DISPLAY="1"
PID_FILE="qemu_vm.pid"
# 函数:正常启动虚拟机(从硬盘启动,不加载ISO)
start_vm() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null; then
echo "虚拟机已在运行(PID: $pid),无需重复启动。"
return 0
else
echo "发现残留PID文件,但进程不存在,已清理。"
rm -f "$PID_FILE"
fi
fi
if [ ! -f "$DISK_FILE" ]; then
echo "错误:未找到虚拟磁盘文件 $DISK_FILE,请先使用 install 模式安装系统。"
return 1
fi
echo "正在启动虚拟机(从硬盘启动)..."
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp "$CPU_CORES" \
-m "$MEM_SIZE" \
-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
-device virtio-gpu-pci,id=vgpu \
-vnc :"$VNC_DISPLAY",display=vgpu \
-nographic \
-serial none \
-device usb-ehci,id=usb-controller \
-device usb-tablet,bus=usb-controller.0 \
-device usb-kbd,bus=usb-controller.0 \
-boot order=c,menu=on \
-device virtio-blk-device,drive=disk0 \
-drive file="$DISK_FILE",if=none,id=disk0,format=qcow2 \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::$SSH_PORT-:22 \
-accel tcg,thread=single &
echo $! > "$PID_FILE"
echo "虚拟机启动成功,进程ID已写入 $PID_FILE。"
}
# 函数:安装模式启动(加载ISO镜像)
install_vm() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null; then
echo "虚拟机已在运行(PID: $pid),请先停止再进行安装。"
return 0
else
echo "发现残留PID文件,但进程不存在,已清理。"
rm -f "$PID_FILE"
fi
fi
if [ ! -f "$ISO_FILE" ]; then
echo "错误:未找到ISO文件 $ISO_FILE,请检查文件是否存在。"
return 1
fi
if [ ! -f "$DISK_FILE" ]; then
echo "创建虚拟磁盘文件: $DISK_FILE"
qemu-img create -f qcow2 "$DISK_FILE" 50G
fi
echo "以安装模式启动虚拟机(从ISO启动)..."
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp "$CPU_CORES" \
-m "$MEM_SIZE" \
-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
-device virtio-gpu-pci,id=vgpu \
-vnc :"$VNC_DISPLAY",display=vgpu \
-nographic \
-serial none \
-device usb-ehci,id=usb-controller \
-device usb-tablet,bus=usb-controller.0 \
-device usb-kbd,bus=usb-controller.0 \
-boot order=d,menu=on \
-device virtio-blk-device,drive=disk0 \
-drive file="$DISK_FILE",if=none,id=disk0,format=qcow2 \
-device virtio-blk-device,drive=cdrom0 \
-drive file="$ISO_FILE",if=none,id=cdrom0,media=cdrom \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::$SSH_PORT-:22 \
-accel tcg,thread=single &
echo $! > "$PID_FILE"
echo "安装模式启动成功,进程ID已写入 $PID_FILE。"
echo "请通过VNC连接进行系统安装,完成后请先停止虚拟机,再用start模式启动。"
}
# 函数:停止虚拟机
stop_vm() {
if [ ! -f "$PID_FILE" ]; then
echo "未找到虚拟机进程ID文件,可能虚拟机未启动。"
return 1
fi
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null; then
echo "正在停止虚拟机(PID: $pid)..."
kill "$pid"
# 等待进程退出(最多等待10秒)
for i in {1..10}; do
if ! ps -p "$pid" > /dev/null; then
echo "虚拟机已停止。"
rm -f "$PID_FILE"
return 0
fi
sleep 1
done
echo "强制终止虚拟机进程..."
kill -9 "$pid"
rm -f "$PID_FILE"
echo "虚拟机已强制停止。"
else
echo "PID文件存在,但进程不存在,已清理PID文件。"
rm -f "$PID_FILE"
fi
}
# 函数:查看虚拟机状态
status_vm() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null; then
echo "虚拟机正在运行(PID: $pid)。"
else
echo "PID文件存在,但进程不存在,已清理。"
rm -f "$PID_FILE"
echo "虚拟机未运行。"
fi
else
echo "虚拟机未运行。"
fi
}
# 主逻辑:根据参数执行操作
case "$1" in
start)
start_vm
;;
install)
install_vm
;;
stop)
stop_vm
;;
restart)
stop_vm
start_vm
;;
status)
status_vm
;;
*)
echo "用法: $0 {start|install|stop|restart|status}"
echo " install: 从ISO启动,用于系统安装"
echo " start: 从硬盘启动,正常使用系统"
echo " stop: 停止虚拟机"
echo " restart: 重启虚拟机"
echo " status: 查看虚拟机状态"
exit 1
;;
esac
root@i-kg2tav5y:/data/arm_kylin_10.0.2.15_node001#
- 停止虚拟机(确保磁盘无写入,避免损坏):
# 进入宿主机的虚拟机配置目录(替换为实际路径)
cd /data/arm_kylin_10.0.2.15_node001
# 用脚本停止虚拟机
./start_kylin.sh stop
# 验证是否已停止(输出“虚拟机未运行”即为成功)
./start_kylin.sh status
- 扩展 qcow2 虚拟磁盘:
- 语法:
qemu-img resize 虚拟磁盘文件 新增容量
(+13G
表示新增 13G,100G
表示总容量 100G) - 示例(将磁盘从 50G 扩展到 100G,新增 50G):
qemu-img resize kylin_v10_sp3_arm64.qcow2 +50G
- 验证扩展结果(查看 “virtual size” 是否为目标容量):
qemu-img info kylin_v10_sp3_arm64.qcow2
- 启动虚拟机(进入下一步操作):
./start_kylin.sh start
# 通过SSH或VNC登录虚拟机(用户为root)
ssh -p 2222 root@宿主机IP
第二步:虚拟机内操作 —— 扩容分区与 LVM(通用步骤)
2.1 确认磁盘状态(定位未分配空间)
登录虚拟机后,先查看磁盘当前分区情况,确认宿主机扩展的空间已被识别:
# 查看磁盘分区结构(vda总容量应已扩展,如100G,但vda3仍为原大小,如48.4G)
lsblk /dev/vda
# 查看GPT分区表(若有“GPT PMBR大小不符”警告,需后续修复)
fdisk -l /dev/vda
- 预期结果:
/dev/vda
总容量 = 目标容量(如 100G),/dev/vda3
(LVM 物理分区)未扩展,存在 “未分配空间”。
2.2 修复 GPT 分区表(解决 “大小不符” 警告)
若fdisk -l /dev/vda
提示 “GPT PMBR 大小不符”“备份 GPT 表不在磁盘末尾”,需先修复分区表:
# 进入fdisk交互模式(操作/dev/vda)
fdisk /dev/vda
# 1. 输入p查看分区表(确认vda3的起始扇区,如3328000,后续不可修改!)
p
# 2. 无需修改分区,直接输入w保存(fdisk会自动修复GPT表)
w
# 3. 刷新分区表(无需重启)
partprobe /dev/vda
# 4. 再次查看,确认警告消失
fdisk -l /dev/vda
2.3 扩展 LVM 物理分区(vda3)
需将vda3
扩展到 “未分配空间”,核心是保留 vda3 的起始扇区(避免 LVM 数据丢失):
# 重新进入fdisk交互模式
fdisk /dev/vda
# 1. 删除原vda3分区(仅删除分区表记录,不删除数据)
d # 输入d删除分区
3 # 输入分区号3(对应vda3)
# 2. 新建vda3分区(扩展到未分配空间)
n # 输入n新建分区
p # 输入p选择“主分区”
3 # 输入分区号3(保持原编号,避免LVM识别异常)
# 起始扇区:直接按回车(默认使用原起始扇区,如3328000,关键!)
# 结束扇区:直接按回车(默认占用全部未分配空间)
N # 提示“是否移除LVM签名”,输入N保留(否则LVM数据失效)
# 3. (可选)修改分区类型(GPT下可能显示为Linux filesystem,不影响功能)
t # 输入t修改分区类型
3 # 输入分区号3
8e# 输入8e(LVM分区类型码)
p # 输入p验证:vda3大小=扩展后容量(如98.4G),类型可为Linux filesystem
# 4. 保存分区表并退出
w
# 5. 刷新分区表
partprobe /dev/vda
# 6. 验证vda3已扩展(大小应为扩展后容量,如98.4G)
lsblk /dev/vda
2.4 扩展 LVM 物理卷(PV)
LVM 需先识别vda3
的新增空间,通过pvresize
更新物理卷:
# 扩展vda3对应的LVM物理卷
pvresize /dev/vda3
# 验证结果(查看PFree列,应有新增空间,如50G)
pvs
- 预期结果:
/dev/vda3
的PFree
(剩余空间)= 宿主机新增的容量(如 50G)。
2.5 扩展 LVM 逻辑卷(LV)
将新增的 LVM 空间全部分配给根分区对应的逻辑卷(如klas-root
):
# 查看逻辑卷名称(根分区对应的LV通常为klas-root或centos-root)
lvs
# 扩展逻辑卷:将所有剩余空间(+100%FREE)分配给klas-root
lvextend -l +100%FREE /dev/mapper/klas-root
# 验证结果(查看LV Size列,klas-root容量应已扩展,如96.3G)
lvs
第三步:按文件系统类型扩容(二选一)
先确认根分区的文件系统类型,再选择对应工具:
# 查看根分区(/)的文件系统类型(Type列)
df -T /
3.1 场景 1:XFS 文件系统(Kylin V10 默认)
XFS 需使用xfs_growfs
工具,且必须指定根分区挂载点(/),不可指定设备路径:
# 扩容XFS文件系统(/为根分区挂载点)
xfs_growfs /
# 验证结果(根分区容量应=扩展后容量,如97G)
df -h /
- 成功标志:输出 “data blocks changed from XXX to XXX”,
df -h /
显示容量已扩展。
3.2 场景 2:ext4 文件系统
ext4 需使用resize2fs
工具,必须指定逻辑卷设备路径(如/dev/mapper/klas-root
):
# 扩容ext4文件系统(指定逻辑卷设备路径)
resize2fs /dev/mapper/klas-root
# 验证结果(根分区容量应=扩展后容量,如97G)
df -h /
- 成功标志:输出 “done”,
df -h /
显示容量已扩展,无 “超级块错误” 提示。
第四步:最终验证
执行以下命令,确认全流程扩容成功:
# 1. 查看磁盘分区(vda3已扩展)
lsblk /dev/vda
# 2. 查看LVM状态(物理卷、逻辑卷容量正常)
pvs && lvs
# 3. 查看根分区容量(已扩展到目标大小)
df -h /
- 预期结果:根分区(/)容量 = 宿主机扩展后的总容量(如 100G 扣除引导分区后约 97G),使用率下降。
常见问题与解决方案
- 问题 1:resize2fs 报错 “超级块中的幻数有错”
- 原因:用 ext4 工具扩容 XFS 文件系统,工具不匹配。
- 解决:改用
xfs_growfs /
。
- 问题 2:fdisk 修改 vda3 后,类型仍显示为 Linux filesystem
- 原因:GPT 分区表对 LVM 类型的显示兼容性问题,不影响 LVM 功能。
- 解决:无需处理,只要
pvs
能识别/dev/vda3
即可。
- 问题 3:扩展后 df -h 仍显示原容量
- 原因:未执行文件系统扩容步骤(漏了
xfs_growfs
或resize2fs
)。 - 解决:补充执行对应文件系统的扩容命令。
注意事项
- 起始扇区不可错:删除 vda3 后新建时,起始扇区必须与原扇区一致(如 3328000),否则 LVM 数据丢失。
- 工具必须匹配:XFS 用
xfs_growfs
(挂 – 载点),ext4 用resize2fs
(设备路径),不可混用。 - 操作顺序不可乱:宿主机扩展磁盘 → 虚拟机修复 GPT → 扩展 vda3 → LVM 扩容 → 文件系统扩容,步骤不可逆。