存储管理与快照迁移
存储管理 && 快照迁移 && 高级运维
[TOC]
🧱 知识点总结
从”手动挡”到”自动挡”
在上篇笔记📚中,我们用 qemu-img create + virsh define 完成批量创建
- 但这些操作 ==libvirt 并不完全”知情”==
- 你用
qemu-img创建的磁盘、用rm删除的镜像,libvirt 的存储池并不会自动感知
- 你用
🧱 为什么 libvirt “不知情”?
libvirt 内部维护了一份元数据”登记表”,它并不直接盯着文件系统看:
qemu-img create 创建磁盘:/guest_images/vm-disk1.qcow2 ← 文件确实在磁盘上 ✅️但 libvirt 的"登记表"里没有它 ← libvirt 不知道 ❌️
virsh vol-create-as 创建磁盘:/guest_images/vm-disk1.qcow2 ← 文件在磁盘上 ✅️libvirt 同时在登记表里记了一笔 ← libvirt 知道 ✅️通俗类比:
qemu-img create= 你偷偷把一箱货搬进仓库,仓库管理员没收到入库单,==账本上没有==
virsh vol-create-as= 你走正规流程入库,管理员登记了货物名称、大小、位置,==账本上记了==📌 ==virsh pool-refresh== 就是管理员去仓库实地盘点一圈,把账本和实物对齐 —> 发现多了一箱货,补登进去
本质区别是
qemu-img==绕过了 libvirt 的”记账”环节==,libvirt 的元数据没有被更新
✅️ 把”管理权”交还给 libvirt —— 用受管的存储池 和 存储卷替代 qemu-img 的裸操作,让一切变得可控、可追踪
📌 管理工具演进路线:
| 阶段 | 创建磁盘方式 | 管理方式 | 可见性 |
|---|---|---|---|
| 原始版 | qemu-img create | 手动 rm | libvirt 不可见 ❌️ |
| 受管版 | virsh vol-create-as | virsh vol-delete | libvirt 全可见 ✅️ |
📦 存储池 (Storage Pool)
概念
==存储池== 就是 libvirt 管理的”仓库”,虚拟机磁盘、ISO 镜像都放在池子里
通俗解释:qemu-img 创建的磁盘就像你自己在仓库里堆货物 —— 仓库管理员 (libvirt) 不知道你放了什么
- 存储池就是让管理员接手管理,他知道仓库里有什么、还剩多少空间、哪个货物归谁
- 就是
libvirt对磁盘的”正式管理方式”
存储池类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
dir | 目录型,最常用 | 本地磁盘镜像存放 |
lvm | 基于 LVM 卷组 | 需要快照、高性能的场景 |
nfs | 基于 NFS 共享 | 共享存储、迁移场景 |
💡 这里我们以 ==dir 目录型== 为重点,生产环境中 LVM 和 NFS 同样重要
创建目录型存储池
1)定义存储池[root@vhost1 ~]# virsh pool-define-as guest_images_dir dir --target "/guest_images"Pool guest_images_dir defined# pool-define-as: 定义存储池# guest_images_dir: 存储池名称# dir: 类型为目录型# --target: 存储池对应的物理目录路径
2)构建存储池(自动创建目标目录)[root@vhost1 ~]# ls /guest_imagesls: cannot access '/guest_images': No such file or directory '目录还不存在'[root@vhost1 ~]# virsh pool-build guest_images_dirPool guest_images_dir built[root@vhost1 ~]# ls -ld /guest_imagesdrwx--x--x. 2 root root 6 Jun 1 08:44 /guest_images '目录已自动创建'
3)启动存储池[root@vhost1 ~]# virsh pool-start guest_images_dirPool guest_images_dir started
4)设置开机自启[root@vhost1 ~]# virsh pool-autostart guest_images_dirPool guest_images_dir marked as autostarted
5)查看存储池详细信息[root@vhost1 ~]# virsh pool-info guest_images_dirName: guest_images_dirUUID: 0f07bb21-b726-4da1-86fd-34ed7fcdb884State: running '✅️ 活跃状态'Persistent: yes '✅️ 持久化'Autostart: yes '✅️ 开机自启'Capacity: 69.94 GiB '总容量'Allocation: 21.94 GiB '已分配'Available: 48.00 GiB '可用空间'📌 四步走:pool-define-as → pool-build → pool-start → pool-autostart
💡 类比对照:存储池的操作和上篇笔记 ==虚拟网络== 的操作几乎一模一样
| 操作 | 虚拟网络 | 存储池 |
|---|---|---|
| 定义 | virsh net-define | virsh pool-define-as |
| 启动 | virsh net-start | virsh pool-start |
| 自启 | virsh net-autostart | virsh pool-autostart |
| 查看 | virsh net-list --all | virsh pool-list --all |
| 停止 | virsh net-destroy | virsh pool-destroy |
| 删除 | virsh net-undefine | virsh pool-undefine |
默认存储池
[root@vhost1 ~]# virsh pool-list --all Name State Autostart---------------------------------------- default active yes guest_images_dir active yes images active yes
[root@vhost1 ~]# virsh pool-dumpxml default | grep path <path>/var/lib/libvirt/images</path># 这就是 day02 中我们一直放磁盘的地方!📌 default 池会自动管理目录中的镜像:重启后或 pool-refresh 后,目录里的 .qcow2 文件会被自动识别为存储卷
--------------格式有要求吗??? 上篇笔记并没有要求的!! .qcow2 ???
这就是为什么 day02 中我们用 qemu-img create 创建的磁盘,在 virsh vol-list default 里也能看到
📦 存储卷 (Storage Volume)
概念
==存储卷== 是存储池中的具体”货物” —— 一个虚拟磁盘文件
通俗解释:池子是仓库,卷就是仓库里的每个箱子 (磁盘镜像)
🧱 与 day02 的联动:
| day02 方式 (手动) | day03 方式 (受管) |
|---|---|
qemu-img create -f qcow2 node.img 15G | virsh vol-create-as --pool guest_images_dir --name vm-disk1 --capacity 20GB --format qcow2 |
qemu-img create -f qcow2 -b node.img node1.qcow2 20G | virsh vol-create-as --pool ... --backing-vol vm-disk1 --backing-vol-format qcow2 |
rm -rf /var/lib/libvirt/images/node1.qcow2 | virsh vol-delete vm-disk1 guest_images_dir |
创建存储卷
1)在存储池中创建卷[root@vhost1 ~]# virsh vol-create-as \ --pool guest_images_dir \ --name vm-disk1 \ --capacity 20GB \ --format qcow2Vol vm-disk1 created
2)验证卷已创建[root@vhost1 ~]# ls /guest_images/vm-disk1[root@vhost1 ~]# qemu-img info /guest_images/vm-disk1image: "api"file format: qcow2virtual size: 18.6 GiB (20000000000 bytes)disk size: 196 KiB '精简置备,实际只占 196K'
3)查看存储池中的卷列表[root@vhost1 ~]# virsh vol-list guest_images_dir Name Path------------------------------------ vm-disk1 /guest_images/vm-disk1
4)查看卷详细信息[root@vhost1 ~]# virsh vol-info vm-disk1 guest_images_dirName: vm-disk1Type: fileCapacity: 18.63 GiBAllocation: 196.00 KiB📌 virsh vol-create-as 参数速查:
| 参数 | 含义 |
|---|---|
--pool | 目标存储池名称 |
--name | 卷名称 |
--capacity | 容量 (支持 G/M/K 单位) |
--format | 格式 (qcow2 / raw) |
--backing-vol | 后端盘名称 (创建前端盘时用) |
--backing-vol-format | 后端盘格式 |
使用 virsh 创建前端盘
回顾 day02 COW 概念:前端盘依赖后端盘,只存差异数据
[root@vhost1 ~]# virsh vol-create-as \ --pool guest_images_dir \ --name vm-disk1-front \ --capacity 30G \ --format qcow2 \ --backing-vol vm-disk1 \ --backing-vol-format qcow2Vol vm-disk1-front created
[root@vhost1 ~]# qemu-img info /guest_images/vm-disk1-frontimage: "api"file format: qcow2virtual size: 30 GiB (32212254720 bytes)disk size: 196 KiBbacking file: /guest_images/vm-disk1 '后端盘已关联 ✅️'backing file format: qcow2✅️ 对比 day02 的 qemu-img create -b:效果完全一样,但 libvirt 现在”知道”这个前端盘的存在
使 qemu-img 创建的磁盘受管
1)用 qemu-img 手动创建磁盘[root@vhost1 ~]# cd /guest_images/[root@vhost1 /guest_images]# qemu-img create -f qcow2 test.qcow2 10G[root@vhost1 /guest_images]# lstest.qcow2 vm-disk1 vm-disk1-front
2)当前 libvirt 看不到这个磁盘[root@vhost1 /guest_images]# virsh vol-list guest_images_dir Name Path------------------------------------------------ vm-disk1 /guest_images/vm-disk1 vm-disk1-front /guest_images/vm-disk1-front# test.qcow2 不在列表中 ❌️
3)刷新存储池[root@vhost1 /guest_images]# virsh pool-refresh guest_images_dirPool guest_images_dir refreshed
4)再次查看——已经受管了[root@vhost1 /guest_images]# virsh vol-list guest_images_dir Name Path------------------------------------------------ test.qcow2 /guest_images/test.qcow2 '✅️ 出现了!' vm-disk1 /guest_images/vm-disk1 vm-disk1-front /guest_images/vm-disk1-front
5)删除卷[root@vhost1 /guest_images]# virsh vol-delete test.qcow2 guest_images_dirVol test.qcow2 deleted📌 pool-refresh:让 libvirt 重新扫描存储池目录,把不认识的文件”登记入册”
- default 池在重启后会自动 refresh,自定义池需要手动执行
⚙️ XML 配置管理 (高级)
三种修改 XML 的方式
在 day02 中我们用
vim直接编辑 XML 文件,day03 介绍更优雅的方式
| 方式 | 命令 | 优点 | 缺点 |
|---|---|---|---|
| 直接 vim | vim /etc/libvirt/qemu/test.xml | 最灵活 | 语法错误风险高、需手动生效 |
| virsh edit | virsh edit test | 自动语法校验 | 还是手写 XML |
| virt-xml | virt-xml test --edit --memory 4096 | ==不用写 XML==,一行命令搞定 | 部分复杂场景仍需手写 |
1)语法校验[root@vhost1 ~]# virt-xml-validate /etc/libvirt/qemu/test.xml/etc/libvirt/qemu/test.xml validates '✅️ 语法正确'内存管理
1)查看当前内存配置[root@vhost1 ~]# virsh dumpxml test | grep -i mem <memory unit='KiB'>2097152</memory> '最大内存 2G' <currentMemory unit='KiB'>2097152</currentMemory> '当前内存 2G'
2)关机状态下修改最大内存[root@vhost1 ~]# virsh setmaxmem --size 1G test[root@vhost1 ~]# virsh dumpxml test | grep -i mem <memory unit='KiB'>1048576</memory> '最大内存 → 1G' <currentMemory unit='KiB'>1048576</currentMemory>
3)开机后动态调整当前内存(不能超过 maxmem)[root@vhost1 ~]# virsh start test[root@vhost1 ~]# virsh setmem --size 512M test '运行时缩小到 512M ✅️'[root@vhost1 ~]# virsh dumpxml test | grep -i mem <memory unit='KiB'>1048576</memory> '最大 还是 1G' <currentMemory unit='KiB'>524288</currentMemory> '当前 512M'
[root@vhost1 ~]# virsh setmem --size 2G test '❌️ 超过 maxmem'error: invalid argument: cannot set memory higher than max memory⚠️ 关键区别:
| 命令 | 作用 | 运行时可用? |
|---|---|---|
virsh setmaxmem | 修改最大内存上限 | ❌️ 关机才能改 |
virsh setmem | 修改当前使用内存 | ✅️ 可以热调整 (但不能超 maxmem) |
CPU 管理
1)查看当前 vCPU 配置[root@vhost1 ~]# virsh dumpxml test | grep -i vcpu <vcpu placement='static' current='1'>1</vcpu>
2)关机状态下修改最大 vCPU[root@vhost1 ~]# virsh setvcpus test 3 --config --maximum[root@vhost1 ~]# virsh dumpxml test | grep -i vcpu <vcpu placement='static' current='1'>3</vcpu># 最大 3 核,当前使用 1 核
3)修改当前使用的 vCPU 数量[root@vhost1 ~]# virsh setvcpus test 2 --current[root@vhost1 ~]# virsh dumpxml test | grep -i vcpu <vcpu placement='static' current='2'>3</vcpu># 当前 2 核,最大 3 核
4)查看 vCPU 数量[root@vhost1 ~]# virsh vcpucount testmaximum config 3current config 2📌 参数区分:
| 参数 | 含义 |
|---|---|
--config | 修改持久化配置 (关机生效) |
--current | 修改当前状态 |
--maximum | 修改的是最大上限而非当前值 |
--live | 热修改 (需 VM 运行中) |
virt-xml 一行搞定
==virt-xml== 让你不用手写 XML 就能修改虚拟机配置
# 基本语法virt-xml DOMAIN XML-ACTION XML-OPTION [选项]
1)修改内存[root@vhost1 ~]# virt-xml test --edit --memory 4096Domain 'test' defined successfully.[root@vhost1 ~]# virsh dumpxml test | grep -i mem <memory unit='KiB'>4194304</memory># 一行命令,内存变成 4G ✅️
2)添加磁盘(磁盘自动创建)[root@vhost1 ~]# virt-xml test --add-device --disk /var/lib/libvirt/images/newdisk.qcow2,format=qcow2,size=20 --updateAllocating 'newdisk.qcow2' | 20 GB 00:00:00Domain 'test' defined successfully.# 磁盘自动创建 + 附加,一步到位
3)修改网卡[root@vhost1 ~]# virt-xml test --edit --network network=defaultDomain 'test' defined successfully.# 把 VM 从 vbr 网络切到 default 网络
4)移除磁盘设备[root@vhost1 ~]# virsh shutdown test[root@vhost1 ~]# virsh detach-disk test --config --target sdaDisk detached successfully# --config: 持久化删除# --target: 指定磁盘设备名
5)热添加磁盘(VM 运行中)[root@vhost1 ~]# virsh vol-create-as --pool default --name vm-disk-test --capacity 20GB --format qcow2[root@vhost1 ~]# virsh attach-disk test /var/lib/libvirt/images/vm-disk-test vdbDisk attached successfully# 在 VM 里 lsblk 就能看到新磁盘 vdb ✅️📌 virt-xml 四大动作:
| 动作 | 作用 | 示例 |
|---|---|---|
--edit | 修改已有配置 | --edit --memory 4096 |
--add-device | 添加新设备 | --add-device --disk ... |
--remove-device | 移除设备 | --remove-device --disk target=vdb |
--build-xml | 只输出 XML 不修改 | 预览用 |
⚠️ 磁盘设备名与 bus 类型的关系:
<disk type="file" device="disk"> <target dev="vda" bus="virtio"/> '设备名 vda ← bus=virtio'</disk><disk type="file" device="disk"> <target dev="sda" bus="sata"/> '设备名 sda ← bus=sata'</disk># 同样的磁盘,bus 不同 → 在 VM 里看到的设备名不同📸 快照与克隆
快照 (Snapshot)
==快照== 就是给虚拟机当前状态拍一张”照片”,以后随时可以回到这个状态
通俗解释:打游戏时存个档 —— 后面玩崩了就读档重来
1)创建快照[root@vhost1 ~]# virsh snapshot-create-as test snap1Domain snapshot snap1 created[root@vhost1 ~]# virsh snapshot-create-as test snap2Domain snapshot snap2 created# snap1 → snap2 形成链式关系
2)查看快照树[root@vhost1 ~]# virsh snapshot-list --tree testsnap1 | +- snap2# snap2 是在 snap1 基础上创建的
3)查看当前快照[root@vhost1 ~]# virsh snapshot-current test --namesnap2
4)恢复到 snap1[root@vhost1 ~]# virsh snapshot-revert test snap1Domain snapshot snap1 reverted[root@vhost1 ~]# virsh snapshot-current test --namesnap1# 回到 snap1 后,snap2 还在但不再是"当前"状态
5)删除快照[root@vhost1 ~]# virsh snapshot-delete test snap2Domain snapshot snap2 deleted
6)查看快照 XML 详情[root@vhost1 ~]# virsh snapshot-dumpxml test snap1📌 快照命令速查:
| 操作 | 命令 |
|---|---|
| 创建 | virsh snapshot-create-as <VM> <快照名> |
| 查看列表 | virsh snapshot-list <VM> |
| 查看树 | virsh snapshot-list --tree <VM> |
| 恢复 | virsh snapshot-revert <VM> <快照名> |
| 当前快照 | virsh snapshot-current <VM> --name |
| 删除 | virsh snapshot-delete <VM> <快照名> |
| 查看详情 | virsh snapshot-dumpxml <VM> <快照名> |
💡 与 day02 virt-sysprep 的区别:
virt-sysprep:永久清理个性化信息,用于制作模板virsh snapshot:临时保存状态,用于实验/回滚
克隆 (Clone)
==克隆== 就是基于一台现有虚拟机制作一份完整的副本
1)自动克隆(libvirt 自动命名、自动分配磁盘路径)[root@vhost1 ~]# virt-clone --original test --auto-cloneAllocating 'test-clone.qcow2' | 20 GB 00:00:13Clone 'test-clone' created successfully.
2)手动指定克隆参数[root@vhost1 ~]# virt-clone \ --original example-VM-2 \ --name example-VM-3 \ --file /var/lib/libvirt/images/disk-1-example-VM-2.qcow2 \ --file /var/lib/libvirt/images/disk-2-example-VM-2.qcow2📌 virt-clone 会自动处理:UUID 去重、MAC 地址去重、个性化信息清理 —— 不用再手动搞
⚠️ 克隆 vs 前端盘方案:
| virt-clone 克隆 | day02 前端盘 + COW | |
|---|---|---|
| 磁盘占用 | ==完整复制== (每台独立一份数据) | ==增量== (共享后端盘) |
| 空间效率 | 低 ❌️ | 高 ✅️ |
| 独立性 | 完全独立 ✅️ | 依赖后端盘 |
| 适合场景 | 少量、完全隔离的场景 | 批量快速部署 |
# 验证克隆的磁盘是完整复制[root@vhost1 ~]# du -sh /var/lib/libvirt/images/test-clone.qcow21.5G /var/lib/libvirt/images/test-clone.qcow2# 1.5G → 完整复制,不是前端盘(前端盘一般只有几百K)🐟 guestfish 和 guestmount
概念
==libguestfs 工具集== 让你 ==不启动虚拟机就能操作磁盘内部==
在 day02 知识补充 中我们提到了这个工具集,day03 深入实战
📌 一句话:VM 死机了?照样能看日志、改配置、捞数据
| 工具 | 模式 | 适合场景 |
|---|---|---|
guestmount | 挂载模式 (类似 mount) | 浏览文件、结合宿主机命令操作 |
guestfish | 交互式 Shell | 脚本化操作、复杂定制 |
guestmount —— 把镜像挂载成目录
1)安装工具[root@vhost1 ~]# yum -y install libguestfs
2)创建挂载目录[root@vhost1 ~]# mkdir /vmdir
3)挂载镜像(-a 指定磁盘, -i 自动检测并挂载分区)[root@vhost1 ~]# guestmount -a /var/lib/libvirt/images/test-clone.qcow2 -i /vmdir[root@vhost1 ~]# df -h /vmdirFilesystem Size Used Avail Use% Mounted on/dev/fuse 19G 1.5G 17G 8% /vmdir
4)现在可以像操作普通目录一样操作镜像内部了[root@vhost1 ~]# ls /vmdir/afs boot etc lib media opt root sbin sys usrbin dev home lib64 mnt proc run srv tmp var
[root@vhost1 ~]# mkdir /vmdir/testdir[root@vhost1 ~]# echo 123 > /vmdir/testdir/123.txt[root@vhost1 ~]# cp /vmdir/etc/hosts /mnt
5)卸载[root@vhost1 ~]# guestunmount /vmdir💡 注意:guestmount 底层会创建临时 VM 来访问磁盘 (在 virt-manager 中可见),卸载后自动删除
guestfish —— 交互式定制磁盘
这次我们在镜像里安装 httpd、创建用户、定制网站首页
1)进入 guestfish 交互式 Shell[root@vhost1 ~]# guestfish -i --network -a /var/lib/libvirt/images/test-clone.qcow2# -i: 自动挂载# --network: 启用网络 (安装软件需要)# -a: 指定磁盘镜像
2)查看文件(Fish 自带命令)><fs> wc-l /etc/hosts7
3)调用操作系统的命令(需加 command 前缀)><fs> command "wc -l /etc/hosts"7 /etc/hosts
4)安装 httpd 服务(走网络, 较慢)><fs> command "yum -y install httpd"
5)定制首页><fs> touch /var/www/html/index.html><fs> vi /var/www/html/index.html 'fish 内置 vi 编辑器'><fs> cat /var/www/html/index.htmlindex
6)创建用户并设密码><fs> command "useradd user1"><fs> command "echo redhat | passwd --stdin user1"
7)设置 httpd 开机自启><fs> command "systemctl enable httpd"><fs> command "systemctl is-enabled httpd"enabled '✅️ 已设为自启'
8)重新打 SELinux 标签(重要!)><fs> selinux-relabel /etc/selinux/targeted/contexts/files/file_contexts /
9)退出><fs> exit⚠️ guestfish 的两套命令体系:
| 类型 | 来源 | 示例 | 特点 |
|---|---|---|---|
| Fish 内置命令 | libguestfs | cat, vi, touch, wc-l | 直接操作文件 |
| 系统命令 | VM 磁盘内的 OS | command "yum install ..." | 需要 command 前缀 |
command "vim ..." 不能用 —— guestfish 不会给 vim 分配终端,会卡住
要编辑文件请用 Fish 内置的 vi 命令
📌 guestfish vs guestmount 对比:
| 对比维度 | guestfish | guestmount |
|---|---|---|
| 核心特性 | 交互式 Shell | 文件系统挂载点 |
| 底层原理 | 基于 libguestfs 库 | 基于 FUSE |
| 使用方式 | 单一工具内操作 | 挂载后用任何宿主机命令 |
| 适合场景 | 脚本化批量处理 | 浏览文件、结合外部工具 |
验证定制结果
# 基于定制好的磁盘创建 VM[root@vhost1 ~]# ssh root@192.168.122.207 '登进去验证'[root@localhost ~]# curl 127.0.0.1/index.htmlindex '首页 ✅️'[root@localhost ~]# id user1uid=1001(user1) gid=1001(user1) groups=1001(user1) '用户 ✅️'🌐 桥接网络 (非 NAT)
概念
回顾 day01:
default网络使用 NAT 模式,虚拟机通过virbr0(192.168.122.0/24) 上网,==外面无法直接访问虚拟机==day02 我们创建了自定义
my_net(192.168.219.0/24),仍然基于 NAT==桥接网络== 让虚拟机直接”插”在物理交换机上,获取和宿主机同网段的 IP
创建桥接网卡
1)查看宿主机物理网卡[root@vhost1 ~]# ip a s ens16124: ens161: <BROADCAST,MULTICAST,UP,LOWER_UP> ... link/ether 00:0c:29:8d:09:0c ...# 这是我们的物理网卡,后面要作为 br0 的 port
2)创建桥接(br0)[root@vhost1 ~]# nmcli connection add type bridge ifname br0 con-name br0[root@vhost1 ~]# nmcli connection add type ethernet port-type bridge ifname ens161 con-name br0-ens161 controller br0[root@vhost1 ~]# nmcli connection up br0
3)验证 br0 获取到了教室网段的 IP[root@vhost1 ~]# ip a s br025: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> ... inet 10.2.35.218/22 brd 10.2.35.255 ... '教室网段 IP ✅️'📌 nmcli 三步建桥:
| 步骤 | 命令 | 作用 |
|---|---|---|
| ① | nmcli connection add type bridge | 创建桥设备 br0 |
| ② | nmcli connection add type ethernet port-type bridge ... controller br0 | 把物理网卡 ens161 设为 br0 的端口 |
| ③ | nmcli connection up br0 | 激活桥接 |
🧱 类比:桥接 br0 就像是把虚拟机和宿主机插在同一个交换机上 —— IP 同网段、互相可见
将 VM 网络改为桥接
1)VM 当前网络是 NAT (vbr)[root@vhost1 ~]# virsh domiflist test Interface Type Source Model MAC------------------------------------------------------------ - network vbr virtio 52:54:00:48:da:9d
2)使用 virt-xml 改为桥接[root@vhost1 ~]# virt-xml test --edit --network bridge=br0,model=virtioDomain 'test' defined successfully.
3)验证网卡已切换[root@vhost1 ~]# virsh domiflist test Interface Type Source Model MAC----------------------------------------------------------- - bridge br0 virtio 52:54:00:48:da:9d# Type 从 network 变成 bridge ✅️
4)开机验证[root@vhost1 ~]# virsh start test[root@vhost1 ~]# virsh console test[root@localhost ~]# ip a s2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> ... inet 10.2.33.2/22 ... '获取到了教室网段 IP ✅️'# 此时跨节点的虚拟机之间可以直接通信!💡 NAT vs 桥接 适用场景:
| NAT (virbr0 / my_net) | 桥接 (br0) | |
|---|---|---|
| IP 来源 | libvirt DHCP (192.168.x.x) | 教室/公司 DHCP |
| 外部访问 VM | ❌️ 需要端口转发 | ✅️ 直接可达 |
| VM 间跨节点通信 | ❌️ 需要额外配置 | ✅️ 同网段直接通 |
| 安全性 | 高 (隔离在内网) | 低 (暴露在物理网络) |
🚢 虚拟机迁移
概念
==迁移== 就是把一台物理机上的虚拟机”搬”到另一台物理机上
📌 迁移的本质:
vhost1 (源主机) vhost2 (目标主机)┌──────────────┐ ┌──────────────┐│ test (运行) │ ───迁移───> │ test (运行) ││ 内存状态 │ │ 内存状态 ││ 磁盘数据 │ │ 磁盘数据 │└──────────────┘ └──────────────┘迁移类型对比
| 类型 | 存储 | VM 状态 | 停机时间 | 命令参数 |
|---|---|---|---|---|
| 非共享存储热迁移 | 各自独立 | running | 极短 | --live --persistent |
| 共享存储实时迁移 | NFS 共享 | running | 几乎为零 | --live --persistent |
| 离线迁移 | 共享存储 | shut off | 整个迁移过程 | --offline --persistent |
非共享存储热迁移
磁盘在源主机上,需要手动把镜像拷贝到目标主机
# ===== 准备工作 =====
1)源主机和目标主机都需要同样的后端盘[root@vhost1 ~]# scp /var/lib/libvirt/images/.node.img vhost2:/var/lib/libvirt/images/[root@vhost2 ~]# virsh pool-refresh default
2)两边要有同样的网络[root@vhost1 ~]# scp /etc/libvirt/qemu/networks/vbr.xml vhost2:/etc/libvirt/qemu/networks/vbr.xml[root@vhost2 ~]# virsh net-define /etc/libvirt/qemu/networks/vbr.xml[root@vhost2 ~]# virsh net-start vbr[root@vhost2 ~]# virsh net-autostart vbr
3)迁移以 qemu 用户身份执行(uid=107)[root@vhost1 ~]# chown -R qemu:qemu /var/lib/libvirt/images/[root@vhost2 ~]# chown -R qemu:qemu /var/lib/libvirt/images/
4)放行迁移端口[root@vhost1 ~]# firewall-cmd --add-port=49152/tcp[root@vhost1 ~]# firewall-cmd --add-port=49152/tcp --permanent[root@vhost2 ~]# firewall-cmd --add-port=49152/tcp[root@vhost2 ~]# firewall-cmd --add-port=49152/tcp --permanent
5)手动拷贝前端盘到目标主机[root@vhost1 ~]# scp /var/lib/libvirt/images/test.qcow2 vhost2:/var/lib/libvirt/images/test.qcow2
# ===== 执行迁移 =====# 在 virt-manager 中: 右键运行中的 VM → 迁移 → 选目标主机 → 勾选 "Allow unsafe" → 点击 Migrate
# ===== 验证 =====[root@vhost1 ~]# virsh -c qemu+ssh://root@vhost2/system list Id Name State---------------------- 1 test running '在 vhost2 上了 ✅️'
[root@vhost1 ~]# virsh list --all Id Name State-------------------- 'vhost1 上已经没了 ✅️'共享存储实时迁移 (NFS)
磁盘放在 NFS 共享上,两边都能访问,不用手动拷贝
# ===== NFS 服务端 (vhost1) =====1)创建共享目录并放好后端盘[root@vhost1 ~]# mkdir /shared_images[root@vhost1 ~]# cp -p /var/lib/libvirt/images/.node.img /shared_images[root@vhost1 ~]# cp -p /var/lib/libvirt/images/* /shared_images
2)配置 NFS 导出[root@vhost1 ~]# cat /etc/exports/shared_images 192.168.100.0/24(rw)
3)启动 NFS 并放行防火墙[root@vhost1 ~]# systemctl restart nfs-server[root@vhost1 ~]# firewall-cmd --add-service=nfs[root@vhost1 ~]# firewall-cmd --add-service=nfs --permanent[root@vhost1 ~]# firewall-cmd --add-service=rpc-bind[root@vhost1 ~]# firewall-cmd --add-service=rpc-bind --permanent[root@vhost1 ~]# firewall-cmd --add-service=mountd[root@vhost1 ~]# firewall-cmd --add-service=mountd --permanent[root@vhost1 ~]# exportfs -rv
# ===== 两边都挂载 NFS =====[root@vhost1 ~]# mount vhost1:/shared_images /var/lib/libvirt/images/[root@vhost2 ~]# mount vhost1:/shared_images /var/lib/libvirt/images/# 两边看到的是同一份磁盘数据! ✅️
# ===== 迁移(在 virt-manager 中操作) =====# 右键 VM → 迁移 → 选目标主机 → 点击 Migrate# 不用再手动 scp 磁盘了 ✅️
# ===== 命令行热迁移 =====[root@vhost1 ~]# virsh migrate --live test qemu+ssh://root@vhost2/system --persistent --undefinesource# --live: 在线迁移# --persistent: 目标端持久化# --undefinesource: 迁移后从源端删掉定义
[root@vhost1 ~]# virsh list 'vhost1 空了 ✅️'[root@vhost1 ~]# virsh -c qemu+ssh://root@vhost2/system list Id Name State---------------------- 3 test running 'vhost2 上运行中 ✅️'
# ===== 命令行离线迁移 =====[root@vhost2 ~]# virsh shutdown test[root@vhost2 ~]# virsh migrate test qemu+ssh://root@vhost1/system --offline --persistent# --offline: 离线迁移 (VM 必须关机)📌 迁移命令参数速查:
| 参数 | 含义 |
|---|---|
--live | 在线/热迁移 (VM 保持运行) |
--offline | 离线迁移 (VM 关机状态) |
--persistent | 目标端持久化配置 |
--undefinesource | 迁移成功后在源端删除 VM 定义 |
--verbose | 显示迁移进度 |
☁️ 官方云镜像
概念
OS 官方提供预装好的云镜像 (Cloud Image),体积小、即下即用
和 day02 模板制作 的区别:不用自己 virt-install 安装了,直接拿官方做好的镜像就行
使用流程
1)下载云镜像(以 Rocky Linux 10 为例)[root@vhost1 ~]# ls /images/*qcow2/images/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2
2)复制到存储池[root@vhost1 ~]# cp /images/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2 /var/lib/libvirt/images/rocky10.qcow2[root@vhost1 ~]# virsh pool-refresh default
3)在 virt-manager 中导入# 选择 "Import existing disk image"# 选择 rocky10.qcow2,系统类型选 Rocky Linux 10# 其余配置自定义
4)此时不知道 root 密码 ❌️[root@vhost1 ~]# virsh console testrockylocalhost login: rootPassword: ??? '云镜像没有预设密码!'virt-customize 设置密码
1)先关机[root@vhost1 ~]# virsh shutdown testrocky
2)使用 virt-customize 设置 root 密码[root@vhost1 ~]# cd /var/lib/libvirt/images/[root@vhost1 /var/lib/libvirt/images]# virt-customize -a rocky10.qcow2 --root-password password:redhat123[ 0.0] Examining the guest ...[ 33.0] Setting a random seed[ 33.1] Setting passwords[ 35.0] SELinux relabelling[ 39.9] Finishing off
3)开机验证[root@vhost1 ~]# virsh start testrocky[root@vhost1 ~]# virsh console testrockylocalhost login: rootPassword: redhat123[root@localhost ~]# '✅️ 成功登录'📌 云镜像三种破密码方案:
| 方法 | 工具 | 特点 |
|---|---|---|
| 破密码 | 进入单用户模式 | 操作繁琐,不推荐 |
| cloud-init | 注入 cloud-init 配置 | ==云环境首选==,批量自动化 |
| virt-customize | virt-customize --root-password | ==最直接==,一行命令搞定 |
💡 virt-customize 和 guestfish 的关系:底层都基于 libguestfs,virt-customize 是更高层的封装,专为”定制云镜像”这个场景设计
📝 作业练习
基于 day03 所学内容,完成以下练习
练习清单
- 使用 Rocky Linux 10 云镜像,将 root 密码设为
huawei123 - 添加一块硬盘,创建基于 LVM 的存储池
guest_images_lvm - 将官方云镜像复制到存储池目录中,使其受管
- 在存储池中,基于官方云镜像创建前端盘 (使用 virsh 命令创建受管的前端盘)
- 在 virt-manager 中使用前端盘创建虚拟机 (导入已存在的镜像),采用默认配置
- 创建一个新的磁盘
testdisk.qcow2(使用 virsh 命令创建受管的磁盘),将磁盘添加到虚拟机中,名为/dev/vdc - 添加一张仅主机的网卡,创建网桥
br1,桥接到此仅主机的网卡 - 为虚拟机添加一张网卡,使用
br1网桥 - 使用 virsh 或 virt-xml 命令,调整 CPU 和内存
- 运行虚拟机验证,验证后关机删除
参考命令速查
# 设置密码virt-customize -a <镜像> --root-password password:<密码>
# 创建 LVM 存储池virsh pool-define-as guest_images_lvm logical --source-dev=<PV> --source-name=<VG> --target /dev/<VG>
# 存储池四步走virsh pool-define-as <池名> dir --target "<目录>"virsh pool-build <池名>virsh pool-start <池名>virsh pool-autostart <池名>
# 创建受管磁盘virsh vol-create-as --pool <池名> --name 磁盘名 --capacity 大小 --format qcow2
# 创建受管前端盘virsh vol-create-as --pool <池名> --name 前端盘名 --capacity 大小 --format qcow2 \ --backing-vol 后端盘名 --backing-vol-format qcow2
# 附加磁盘(需指定正确的 target 设备名)virsh attach-disk <VM> <磁盘路径> vdc
# 调整 CPU/内存virt-xml <VM> --edit --memory <大小>virsh setvcpus <VM> <数量> --config --maximum🔁 Day01 → Day03 知识串联
| 能力 | day01 | day02 | day03 |
|---|---|---|---|
| 安装 VM | virt-install 手动安装 | 模板机装一次,后续克隆 | 直接用官方云镜像 |
| 管理磁盘 | qemu-img create | qemu-img create -b 前端盘 | virsh vol-create-as 受管 |
| 网络 | default NAT (virbr0) | 自定义 NAT (my_net) | ==桥接 (br0)== |
| 管理方式 | 本地 virsh | 远程 qemu+ssh:// | 远程 + 迁移 |
| 批量 | ❌️ | shell 脚本 for 循环 | 脚本 + 受管存储池 |
| 备份恢复 | ❌️ | ❌️ | ==快照 + 克隆== |
| 高级运维 | ❌️ | guestfish 初识 | ==guestfish + guestmount 实战== |
⚠️ 常见坑点
| 问题 | 原因 | 解决 |
|---|---|---|
qemu-img 创建的磁盘 virsh vol-list 看不到 | 存储池没有 refresh | virsh pool-refresh <池名> |
virsh setmem 报 “cannot set memory higher than max memory” | 当前内存不能超过 maxmem | 先关机 → setmaxmem → 开机 → setmem |
virt-clone 磁盘占用和原 VM 一样大 | 克隆是完整复制,不是 COW | 用 day02 前端盘方式替代 |
guestfish 中 command "vim ..." 卡住 | fish 不给 vim 分配终端 | 用 fish 内置的 vi 命令 |
| 桥接 VM 获取不到 IP | br0 未正确桥接到物理网卡 | 检查 nmcli connection show br0 |
| 迁移报权限错误 | 镜像文件 owner 不是 qemu | chown -R qemu:qemu /var/lib/libvirt/images/ |
| 云镜像登录不了 | 官方镜像没有预设密码 | virt-customize --root-password 设置 |
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!




