虚拟化管理与快速创建虚拟机
虚拟化管理 && 快速创建虚拟机
[TOC]
🧱 常用命令速查
| 命令 | 分类 | 主要功能 | 常用示例 |
|---|---|---|---|
virsh list --all | 生命周期 | ==列出所有虚拟机==(含关机) | virsh list --all |
virsh start | 生命周期 | ==启动虚拟机== | virsh start node |
virsh shutdown | 生命周期 | ==正常关机== | virsh shutdown node |
virsh destroy | 生命周期 | 强制断电(关机) | virsh destroy node |
virsh undefine | 生命周期 | 删除定义(XML 也删,==磁盘保留==) | virsh undefine node |
virsh define | 定义 | 从 XML 注册虚拟机 | virsh define node1.xml |
virsh domblklist | 查看 | 指定虚拟机(域)当前挂载的所有块设备(磁盘) | virsh domblklist centos7.0 |
virsh dominfo | 查看 | 查看虚拟机详细信息(UUID、内存、CPU…) | virsh dominfo node |
virsh dumpxml | 查看 | 导出 XML 配置 | virsh dumpxml node > backup.xml |
virsh console | 连接 | 串口直连 VM(不依赖网络) | virsh console node |
virsh domifaddr --source agent | 网络 | 通过 Guest Agent 查 VM 的 IP | virsh domifaddr --source agent node |
virsh domstate | 查看 | 看 VM 当前是 running 还是 shut off | virsh domstate node |
virsh uri | 连接 | 查看当前连接的是本地还是远程 | virsh uri |
virsh net-list | 网络 | 查看虚拟网络 | virsh net-list --all |
virsh net-edit | 网络 | 编辑网络配置 | virsh net-edit default |
virsh net-destroy | 网络 | 强制停止网络(用于重启) | “ |
virsh net-define/start | 网络 | 定义并启动虚拟网络 | virsh net-define vbr.xml && virsh net-start vbr |
virt-install | 创建 | 命令行安装新 VM | virt-install --name node --memory 2048 --disk ... |
qemu-img create | 磁盘 | 创建镜像(后端盘/前端盘) | qemu-img create -f qcow2 -b node.img node1.qcow2 |
qemu-img info | 磁盘 | 查看镜像信息 | qemu-img info node.qcow2 |
virt-sysprep | 模板 | 清理 VM 个性化信息(SSH 密钥、UUID 等) | virt-sysprep -d node |
⚙️ 远程连接管理
📌 核心目标:在一台机器上,管理另一台机器里的虚拟机
'环境准备'✅️ 两台虚拟机[root@Vir-01 ~]#[root@Vir-02 ~]#====================[root@Vir-01 ~]# rpm -qa qemu-kvm libvirt virt-installqemu-kvm-10.0.0-14.el10_1.5.x86_64libvirt-11.5.0-4.8.el10_1.x86_64virt-install-5.0.0-1.el10.noarch[root@Vir-01 ~]# systemctl is-active libvirtdactive
1)配置免密连接[root@Vir-01 ~]# ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_test -N ''[root@Vir-01 ~]# echo 'IdentityFile ~/.ssh/id_rsa_test' >> /etc/ssh/ssh_config[root@Vir-01 ~]# ssh-copy-id -i ~/.ssh/id_rsa_test.pub root@10.0.0.199[root@Vir-01 ~]# ssh 10.0.0.199 'hostname'Vir-02'成功' --> ✅️ 远程执行命令不需要输入密码📌 Vir-02 也要执行类似的操作 --> '互相免密登录' ✅[root@Vir-02 ~]# ssh 10.0.0.199 'hostname'Vir-01
2)配置hosts表# 每次输入那么长的IP, 太累了[root@Vir-02 ~]# cat >> /etc/hosts <<'EOF'10.0.0.198 v1 Vir-0110.0.0.199 v2 Vir-02EOF[root@Vir-02 ~]# scp /etc/hosts v1:/etc/
3)需要提前在Vir-01上面准备一台虚拟机✅️ Vir-01上操作[root@Vir-01 ~]# virsh list --all Id Name State------------------------------ 12 rocky10-test running 'Vir-01 上本地确认' ==================== [root@Vir-01 ~]# virsh -c qemu:///system list --all Id Name State------------------------------ 12 rocky10-test running # ⬆️上面这两条命令是完全等价的 1.`-c` 是 `--connect` 的缩写,告诉 virsh "我这次要连谁" 2.'不写 -c 时,libvirt 自动帮你补上 qemu:///system'(连本地)📌 其实每一条 virsh 命令背后都有一个 URI,你之前一直不写,就是用了默认值:
4)查看本地 libvirt 连接 URI[root@Vir-01 ~]# virsh uriqemu:///system'默认连接本地 qemu 系统实例'📌 核心要记住的 URI 格式:
| 连接目标 | URI |
|---|---|
| 本地系统实例 | qemu:///system |
| 本地会话实例 | qemu:///session |
| 远程 (SSH) ✅️ | qemu+ssh://root@<IP>/system |
5)Vir-02 上远程连过来查✅️ Vir-02操作[root@Vir-02 ~]# virsh -c qemu+ssh://root@v1/system list --all# 这里v1就是Vir-01的IP地址# 10.0.0.198 v1 Vir-01 Id Name State------------------------------ 12 rocky10-test running'通过 SSH 隧道远程管理 libvirt'===================='远程管理本质上就是换个地址而已'virsh -c qemu+ssh://root@v1/system list --all ↑ 把默认的本地 URI 换成远程 URI 整条命令的本质就是:`virsh --connect <目标机器> list --all`核心优势
virsh 和 libvirtd 是什么关系?
virsh (客户端) ──通信──> libvirtd (守护进程)'你敲的命令' '后台 systemd 服务'✅️总在你本机 ⚠️可以在本机,也可以在对面机器上virsh= 遥控器(你手里拿的工具)libvirtd= 电视机(真正干活的东西)- 你按遥控器 → 电视机收到信号 → 换台
完整流程(把 virsh 补进来):
你(Vir-02) 在终端敲 virsh ↓virsh(本机客户端) 解析 URI,发现目标是远程 ↓走 SSH 隧道 ↓Vir-01 上的 libvirtd(远程服务端) 收到指令 ↓libvirtd 真正干活:查虚拟机、启动、关机... ↓结果原路返回 → virsh → 显示在你终端上- 你之前本地操作时,
virsh自动连的是本地libvirtd(qemu:///system) - 远程管理就是把
virsh的目标换成对面机器上的libvirtd(qemu+ssh://)
==全程不需要先 SSH 登录到 Vir-01 再敲命令==
✅️ 相当于:你家服务器放客厅,你坐卧室用笔记本就能开关那台服务器上的==虚拟机==
| 传统方式 | virsh 远程 |
|---|---|
先 ssh v1 登录过去,在远程 shell 里敲命令 | 坐在自己机器上,-c qemu+ssh:// 一步到位 |
| 10 台机器?那就登录 10 次、敲 10 次 | 10 台机器?写个 for 循环,脚本一次性跑完 |
📌 一句话:让远程的 libvirtd 就像安装在本地一样
你写的脚本循环 100 台机器,每条命令自动通过 SSH 隧道飞过去执行、结果飞回来
virsh 交互式
前面每次都要带一长串
-c qemu+ssh://...,太累了==交互式模式==就是:==先挂上去,然后连续敲命令==,不用每条都加
-c
'对比两种方式'1)一次性命令(每条都要带 -c)virsh -c qemu+ssh://root@v1/system list --allvirsh -c qemu+ssh://root@v1/system dominfo rocky10-test ↑ 每条都带这串,烦死
2)交互式切换(进去,切一次,后面不用带了)virsh ✅️ '进入 virsh 小 shell'virsh # connect qemu+ssh://root@v2/system '切一次就行'virsh # list --all '不用带 -c'virsh # dominfo rocky10-test '不用带 -c'virsh # start rocky10-test '不用带 -c'virsh # connect qemu:///system '切回本地'virsh # exit '退出'🌏 ==场景类比==:
| 一次性命令 | 交互式切换 |
|---|---|
| 每次打电话都要拨完整的号码 | ✅️ 打开通讯录,点一下联系人就开始通话 |
'实际实验'[root@Vir-02 ~]# virshWelcome to virsh, the virtualization interactive terminal.Type: 'help' for help with commands 'quit' to quit
virsh # list --all Id Name State--------------------🈳 '因为Vir-02 机器上并没有虚拟机'
virsh # connect qemu+ssh://root@v1/system# 切到 Vir-01virsh # list --all Id Name State------------------------------ 12 rocky10-test runningvirsh # dominfo rocky10-testId: 12Name: rocky10-testUUID: 4b4478bd-d7d7-48c2-98b5-db9dd79db6c3OS Type: hvmState: running.......................
virsh # connect qemu:///system# 切回本地virsh # list --all Id Name State--------------------virsh # exit💡 交互式模式的好处:一次连接后可连续执行多条 virsh 命令,不用每条命令都带 -c 参数
🧱 嵌套虚拟化
概念
通俗解释:嵌套虚拟化就是 ==“虚拟机里跑虚拟机”==
- 你想在 VMware 虚拟机里再装一个 KVM 跑虚拟机?那就需要嵌套虚拟化
嵌套虚拟化术语:
| 层级 | 说明 |
|---|---|
| 第 0 级 (L0) | 物理主机,裸机 |
| 第 1 级 (L1) | 在 L0 上运行的标准虚拟机,可充当额外的虚拟主机 |
| 第 2 级 (L2) | 在 L1 虚拟主机上运行的嵌套虚拟机 |
⚠️ 注意:嵌套虚拟化==严重限制 L2 虚拟机的性能==,主要用于开发和测试场景
两层开关
我们不是在 VMware 里早就开了虚拟化吗,怎么这里还要开一次?
VMware --> 勾选"虚拟化 Intel VT-x/AMD-V" Vir-01 (L1) --> nested=1 rocky10-test (L2) --> 里面还能跑虚拟机| 开关 | 在哪设 | 作用 |
|---|---|---|
| 第一层 VMware 勾选 ✅️ | VMware 虚拟机设置 | 让 L1 能用 CPU 虚拟化功能,==没有它 KVM 根本用不了== |
第二层 nested=1 | Linux 内核模块参数 | 让 L1 把虚拟化能力继续往下传给 L2,==默认不开== |
📌 一句话:没有第一层 → KVM 不工作;没有第二层 → KVM 能工作,但虚拟机里不能再跑虚拟机
启用方法
无论 Intel 还是 AMD,流程一样:看 CPU 标志 → 看内核模块状态 → 没开 → 就开
1)验证 CPU 是否支持嵌套虚拟化[root@Vir-01 ~]# cat /proc/cpuinfo | grep -E 'vmx|svm''Intel 看 vmx,AMD 看 svm'
2)检查当前启用状态# 内核模块状态[root@Vir-01 ~]# cat /sys/module/kvm_intel/parameters/nested# ⬆️ Intel[root@Vir-01 ~]# cat /sys/module/kvm_amd/parameters/nested# ⬆️ AMDY 或 1 → '✅️ 已启用'N 或 0 → '❌ 未启用,需执行以下步骤'
3)启用嵌套虚拟化⚠️ 先停掉所有运行中的虚拟机📌 '为了让kvm中的虚拟机也可以嵌套虚拟机'# 临时:重启后失效'Intel' modprobe -r kvm_intel && modprobe kvm_intel nested=1'AMD' modprobe -r kvm_amd && modprobe kvm_amd nested=1
# 永久:写配置文件'Intel' echo 'options kvm_intel nested=1' >> /etc/modprobe.d/kvm.conf'AMD' echo 'options kvm_amd nested=1' >> /etc/modprobe.d/kvm.conf📌 Intel vs AMD 速查:
| Intel | AMD | |
|---|---|---|
| CPU 标志 | vmx | svm |
| 内核模块 | kvm_intel | kvm_amd |
| 配置行 | options kvm_intel nested=1 | options kvm_amd nested=1 |
📦 虚拟机组成 && 模版机制作
虚拟机 = XML 文件 + 镜像文件
通俗解释:
- XML 文件 → 相当于==机箱配置单==,定义了 CPU、内存、网卡、磁盘等信息
- 镜像文件 → 相当于==硬盘==,存操作系统和数据
[root@Vir-01 ~]# ls /etc/libvirt/qemu/autostart networks rocky10.xml 'XML 定义文件在这'[root@Vir-01 ~]# ls /var/lib/libvirt/images/rocky10.qcow2 '磁盘镜像文件在这'📌 关键目录速查:
| 内容 | 路径 |
|---|---|
| 虚拟机 XML 定义 | /etc/libvirt/qemu/ |
| 网络 XML 定义 | /etc/libvirt/qemu/networks/ |
| 自动启动链接 | /etc/libvirt/qemu/autostart/ |
| 磁盘镜像 | /var/lib/libvirt/images/ |
jiuzhao@Ubuntu 桌面$ ls /etc/libvirt/qemucentos7.0.xml networks ......jiuzhao@Ubuntu 桌面$ ls /etc/libvirt/qemu/networks/# Ubuntu的都在这个目录下autostart default.xml虚拟网络创建
默认的
default网络使用 NAT 模式,网段192.168.122.0/24,这里我们新建一个my_net网络
1)查看当前网络[root@Vir-01 ~]# virsh net-list --all Name State Autostart Persistent-------------------------------------------- default active yes yes
2)查看默认网络配置[root@Vir-01 ~]# cat /etc/libvirt/qemu/networks/default.xml<network> <name>default</name> <uuid>505069fc-7e13-4102-bb4c-ce60d33c822d</uuid> <forward mode='nat'/> <bridge name='virbr0' stp='on' delay='0'/> <mac address='52:54:00:74:59:5b'/> <ip address='192.168.122.1' netmask='255.255.255.0'> <dhcp> <range start='192.168.122.2' end='192.168.122.254'/> </dhcp> </ip></network>'libvirt 使用 dnsmasq 服务为虚拟机提供 DNS 和 DHCP'
3)复制并修改,创建 my_net 网络[root@Vir-01 ~]# ls -lh /etc/libvirt/qemu/networks/default.xml-rw------- 1 root root 576 May 28 18:54 /etc/libvirt/qemu/networks/default.xml[root@Vir-01 ~]# cp /etc/libvirt/qemu/networks/default.xml /etc/libvirt/qemu/networks/my_net.xml[root@Vir-01 ~]# vim /etc/libvirt/qemu/networks/my_net.xml📌 在这里面, 我手动更改了 'IP地址' 和 'DHCP分配的地址'[root@Vir-01 ~]# cat /etc/libvirt/qemu/networks/my_net.xml<network> <name>❌️default❌️</name> <uuid>❌️505069fc-7e13-4102-bb4c-ce60d33c822d❌️</uuid> <forward mode='nat'/> <bridge ❌️name='virbr0'❌️ stp='on' delay='0'/> <mac ❌️address='52:54:00:74:59:5b'❌️/> <ip address='192.168.219.1' netmask='255.255.255.0'> <dhcp> <range start='192.168.219.2' end='192.168.219.254'/> </dhcp> </ip></network>============================⚠️ 再来说问题❌️ --> <uuid>、<mac address>、<name>重复🔁🦄<name> 是 libvirt 管理的网络名称 --> virsh net-list 里显示的名字🦄<bridge name> 是系统实际创建的网桥设备名 --> ip addr show my_net 看到的网卡名============================[root@Vir-01 ~]# uuidgen3a9e9bcb-9313-49bf-b36e-822deb0d5c85# 手动生成新的UUID✅️ MAC 地址 — libvirt 默认用 52:54:00 开头# 把后面三段随便改就行✅️ 网络名称 --> my_net[root@Vir-01 ~]# vim /etc/libvirt/qemu/networks/my_net.xml<network> <name>my_net</name> <uuid>3a9e9bcb-9313-49bf-b36e-822deb0d5c85</uuid> <forward mode='nat'/> <bridge name='my_net' stp='on' delay='0'/> <mac address='52:54:00:aa:bb:cc'/> <ip address='192.168.219.1' netmask='255.255.255.0'> <dhcp> <range start='192.168.219.2' end='192.168.219.254'/> </dhcp> </ip></network>
4)定义并启动新网络[root@Vir-01 ~]# virsh net-define /etc/libvirt/qemu/networks/my_net.xmlNetwork my_net defined from /etc/libvirt/qemu/networks/my_net.xml✅️ 注册一个新网络让 libvirt 认识它[root@Vir-01 ~]# virsh net-list --all Name State Autostart Persistent-------------------------------------------- default active yes yes my_net ❌️'inactive' 'no'❌️ yes⚠️ 现在是不活跃的, 开机不会启动[root@Vir-01 ~]# virsh net-start my_netNetwork my_net started✅️ 启动(应用)自定义网络[root@Vir-01 ~]# virsh net-autostart my_netNetwork my_net marked as autostarted✅️ 使网络开机自启[root@Vir-01 ~]# virsh net-list --all Name State Autostart Persistent-------------------------------------------- default active yes yes my_net active yes yes
5)验证网卡已创建[root@Vir-01 ~]# ip a s my_net16: my_net: <NO-CARRIER,BROADCAST,MULTICAST,UP> ... inet 192.168.219.1/24 brd 192.168.219.255 scope global my_net💡 用 XML 定义出来的网络自带 DHCP(由 dnsmasq 服务提供)
- 如果用
nmcli或图形化工具创建 bridge,默认不带 DHCP,需要手动配置 ❌️- 所以在 KVM 里肯定用 XML 定义网络,方便太多 ✅️
| 操作 | 命令 | 含义 |
|---|---|---|
| 停止虚拟网络 | virsh net-destroy my_net | (不是”摧毁”,是”停止服务”) 停掉 bridge + dnsmasq |
| 删除虚拟网络 (含 XML) | virsh net-undefine my_net | 删 XML 文件,自动消失,不用手动 rm |
-
网络和虚拟机不一样,没有”优雅关闭”
shutdown的说法,因为网络里没系统、没数据 -
要彻底删除一个网络,==两步都要执行==:先
net-destroy停止,再net-undefine删除定义
[root@Vir-01 ~]# virsh net-destroy my_netNetwork my_net destroyed[root@Vir-01 ~]# virsh net-undefine my_netNetwork my_net has been undefined[root@Vir-01 ~]# virsh net-list --all Name State Autostart Persistent-------------------------------------------- default active yes yes[root@Vir-01 ~]# ls /etc/libvirt/qemu/networks/my_net.xmlls: cannot access ...xxx No such file or directory后端盘 (Backing File)
1)创建后端盘(没有系统,'后缀随意,格式为 qcow2')[root@Vir-01 ~]# cd /var/lib/libvirt/images/[root@Vir-01 images]# lsrocky10-test.qcow2[root@Vir-01 images]# qemu-img create -f qcow2 node.img 15GFormatting 'node.img', fmt=qcow2 cluster_size=65536 ... size=8589934592⚠️ '8G系统盘空间太少了 --> 15G'📌 ==参数拆解==:
| 选项 | 含义 |
|---|---|
qemu-img create | qemu-img 的 create 子命令,用来创建新镜像 |
-f qcow2 | -f = format,指定镜像格式为 qcow2(精简置备 + COW 支持) |
node.img | 镜像文件名,后缀无所谓,libvirt 不靠后缀识别格式 |
15G | 虚拟磁盘上限,实际不会立即占满(精简置备) |

- 空间给小了❓️
- 重新给空间试试 给15G 这次肯定够用了
[root@Vir-01 images]# ls -lh ./total 3.1G ✅️-rw------- 1 qemu qemu '21G' May 30 17:26 rocky10-test.qcow2-rw-r--r-- 1 root root 193K May 30 17:29 node.img'虽然创建了 15G,实际只占 196K — 这就是 qcow2 的精简置备'[root@Vir-01 images]# du -sh rocky10-test.qcow23.1G rocky10-test.qcow2'上面写的21G实际上也没有占用那么多'
2)查看镜像信息[root@vhost1 images]# qemu-img info node.imgimage: "./Virt02/image-20260530175843424.png"file format: qcow2virtual size: 15 GiB (16106127360 bytes)disk size: 196 KiB'virtual size → 虚拟大小 15G''disk size → 实际占用 196K'📌 qemu-img 常用子命令:
| 子命令 | 作用 |
|---|---|
create | ==创建镜像== |
info | ==查看镜像信息== |
convert | 转换镜像格式 |
snapshot | 快照管理 |
resize | 调整镜像大小 |
模版机安装与配置
基于创建好的后端盘
node.img和虚拟网络my_net,用 virt-install 安装虚拟机
- 重点讲 ==安装后的模版配置==
[root@Vir-01 ~]# virt-install \ --name node \ --memory 2048 \ --vcpus 2 \ --disk path=/var/lib/libvirt/images/node.img \ --osinfo rocky10 \ --network network=my_net \ --location ftp://192.168.219.1/rocky10/ \ --graphics vnc,listen=0.0.0.0 \ --wait -1📌 关键选项说明:
| 选项 | 含义 | ⚠️ 易错点 |
|---|---|---|
--disk path=.../node.img | 使用已创建的后端盘 | 不能写 --disk size=20,那是创建新磁盘👆等于白做了 qemu-img create |
--network network=my_net | 指定虚拟机接入 my_net | 不写默认走 default 网络 (192.168.122.0/24) |
--location ftp://192.168.219.1/... | FTP 安装源地址 | IP 必须跟指定的网络一致 ( my_net 网关 = 192.168.219.1) |
--graphics vnc,listen=0.0.0.0 | VNC 图形控制台,监听所有地址 | 方便远程连接虚拟机图形界面 |
--wait -1 | 等待安装完成后才退出 | 不加的话命令立刻返回,看不到安装进度 |


1)SSH 连接到新安装的虚拟机[root@Vir-01 ~]# ssh root@192.168.219.10root@192.168.219.10's password': <-- 1Last login: Sun May 31 09:17:39 2026[root@jiuzhao ~]# ping -W2 -c2 www.baidu.com'测试连通性'PING www.a.shifen.com (180.101.49.44) 56(84) 字节的数据。64 字节,来自 180.101.49.44: icmp_seq=1 ttl=127 时间=27.9 毫秒64 字节,来自 180.101.49.44: icmp_seq=2 ttl=127 时间=25.5 毫秒
--- www.a.shifen.com ping 统计 ---已发送 2 个包, 已接收 2 个包, 0% packet loss, time 1002msrtt min/avg/max/mdev = 25.502/26.718/27.935/1.216 ms[root@jiuzhao ~]# ip route'查看网关'default via 192.168.219.1 dev enp1s0 proto dhcp src 192.168.219.10 metric 100
2)来波系统优化[root@jiuzhao ~]# mkdir -p /server/{sh,repo}[root@jiuzhao ~]# touch /server/sh/base_config.sh[root@jiuzhao ~]# vi /server/sh/base_config.sh#!/bin/bash# author=Jiuzhao
cd /etc/yum.repos.d/ls | xargs -i cat {} > /server/repo/Rocky.repo# 把原来的源都写到一个文件中cd -
# yum仓库优化sed -e 's|^mirrorlist=|#mirrorlist=|g' \ -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.sjtug.sjtu.edu.cn/rocky|g' \ -i.bak \ /etc/yum.repos.d/rocky*.repodnf clean alldnf makecachednf install epel-release -y# 扩展仓库dnf makecachednf install -y vim-enhanced tree wget bash-completion lrzsz net-tools sysstat iotop htop unzip nmap-ncat nmap telnet bc psmisc httpd-tools bind-utils openssh-clients expect cowsay sl# 安装必要的软件
# PS1变量echo 'PS1="[\[\e[34;1m\]\u@\[\e[0m\]\[\e[32;1m\]\H\[\e[0m\]\[\e[31;1m\] \W\[\e[0m\]]\\$ "' >> /etc/profilesource /etc/profile
# ssh优化cat >>/etc/ssh/sshd_config<<EOFUseDNS no#相当于网络命令的-n选项,这个就是说不解析为主机名,直接成IP地址.GSSAPIAuthentication no#关闭GSS认证.EOFsystemctl restart sshd
# 防火墙&&SElinuxsed -i 's#enforcing#disabled#g' /etc/selinux/config# 重启生效systemctl disable --now firewalld &> /dev/null
# 文件描述符echo "* - nofile 65535" >> /etc/security/limits.conf
# 其他优化> /etc/issue && > /etc/issue.netcat > /etc/motd << 'EOF' (@) * (@) * (@) : * (@) * (@) * .; (@) * (@) * (@) * (@) * ; * ; (@) * ; * : ;\ \ \ \| / / /; \\ \ Y/ / / `_\ |/ _' ' / \\Y// \ ( ,-}={-, ) \_//((\_/ //))(\ (/ )) (/EOF
reboot# 重启生效配置[root@jiuzhao ~]# sh /server/sh/base_config.sh'执行脚本'.................完毕![root@jiuzhao ~]# Connection to 192.168.219.10 closed by remote host.Connection to 192.168.219.10 closed.
QEMU Guest Agent
==QEMU Guest Agent== 在虚拟机内部运行,就像是给宿主机开了一个通向虚拟机内部的”传话通道”
- 使其能从外部获取虚拟机 IP、冻结文件系统等
[root@jiuzhao ~]# dnf -y install qemu-guest-agent[root@jiuzhao ~]# systemctl enable --now qemu-guest-agent[root@jiuzhao ~]# poweroff
1)手动启动验证[root@Vir-01 ~]# virsh list --all Id Name State------------------------------- - node shut off[root@Vir-01 ~]# virsh start nodeDomain 'node' started[root@Vir-01 ~]# virsh domifaddr --source agent nodedomifaddr = domain interface address# 👆 子命令 --> 查看IP--source # 指定从哪获取 IP 信息agent = 走 Guest Agent 通道node # 虚拟机名称================================ Name MAC address Protocol Address-------------------------------------------------------- lo 00:00:00:00:00:00 ipv4 127.0.0.1/8 enp1s0 52:54:00:97:9d:96 ipv4 192.168.219.10/24📌 通过 Guest Agent 获取信息的两种方式:
| 方式 | 本质 | 适合场景 |
|---|---|---|
virsh domifaddr --source agent <VM> | virsh 封装好的子命令,直接查 IP | 日常简单查询 |
virsh qemu-agent-command <VM> '{"execute":"..."}' | 直接发 JSON 指令给 Guest Agent 更底层,功能更全 | 查 IP、查磁盘、冻结文件系统…啥都能干 |
2)更底层的命令[root@Vir-01 ~]# virsh qemu-agent-command node '{"execute":"guest-ping"}'qemu-agent-command # 原始 QEMU Agent 命令接口node # 虚拟机名称'{"execute":"..."}' # 发给 Agent 的 JSON 指令================================{"return":{}}'返回 return 说明通信正常'
3)查 IPvirsh qemu-agent-command node '{"execute":"guest-network-get-interfaces"}'
4)冻结文件系统(做快照前用)virsh qemu-agent-command node '{"execute":"guest-fsfreeze-freeze"}'💡 domifaddr --source agent 是高层封装,✅️ qemu-agent-command 更底层,能做的事更多
cloud-init
- ==cloud-init== 用于在虚拟机==首次启动时==自动完成主机名、用户、SSH 密钥、网络和软件包等初始化配置
- cloud-init 不只是装一下,它解决的是一个实际问题:
模板机 --克隆--> node1 --开机--> cloud-init 自动帮你: ├── 设个新主机名(node1,不是 localhost) ├── 注入 SSH 公钥(不用每次手动 ssh-copy-id) ├── 配好网络 └── 装好你指定的软件包不装 cloud-init → 每克隆一台都得手动登进去改主机名、配 SSH…100 台要疯 装了之后 → 模板做好、virt-sysprep 清理完,克隆出来的机器第一次开机自动搞定,开机即用
[root@Vir-01 ~]# ssh root@192.168.219.10root@192.168.219.10's password':[root@jiuzhao ~]# dnf install -y cloud-init完毕!📌 一句话:cloud-init 就是每台新虚拟机的”==自动入职流程==“,开机即用
virsh console 串口控制台
console 配置是配在 ==KVM 虚拟机内部==(不是配在 Vir-01 上)
配好之后,宿主机 (Vir-01) 可以==绕过网络==直接连进去
SSH 是前门:宿主机 → 网络 → VM 的 IP → 进去 (网络挂了就歇菜)console 是后门:宿主机 → virsh console → 直接进去 (只要 VM 活着就能进)===================================================🌰 层级关系:你的笔记本 (物理机)└── VMware 虚拟机 (Vir-01) ← 对 KVM 来说,它就是"宿主机" └── KVM 虚拟机 (node) ← console 配在这台里面1)先检测:内核参数里有没有 console❓️[root@jiuzhao ~]# grubby --info=ALL | grep console | wc -l0'如果没有,才需要配置:'(在 KVM 虚拟机里面执行,配一次就行)[root@jiuzhao ~]# grubby --update-kernel=ALL --args="console=ttyS0,115200n8"'grubby 本身安全,参数已存在就不会重复添加'[root@jiuzhao ~]# grubby --info=ALL | grep consoleargs="... console=ttyS0,115200n8" ✅ 已经有了,跳过配置args="... console=ttyS0,115200n8"# 显示两条是因为 --info=ALL 显示了两个内核条目(正常 + rescue),不是配重了[root@jiuzhao ~]# reboot
2)等待重启后,在宿主机上通过 console 连接[root@Vir-01 ~]# virsh console nodeConnected to domain 'node'Escape character is ^] (Ctrl + ]) ✅️ 退出 <-- '回车'jiuzhao login: root密码:Last login: Sun May 31 10:06:44 from 192.168.219.1 (@) * (@) * (@) : * (@) * (@) * .; (@) * (@) * (@) * (@) * ; * ; (@) * ; * : ;\ \ \ \| / / /; \\ \ Y/ / / `_\ |/ _' ' / \\Y// \ ( ,-}={-, ) \_//((\_/ //))(\ (/ )) (/[root@jiuzhao ~]# ip a s'按 Ctrl+] 退出 console'⚠️ 退出 console 和退出 shell 是两回事:
| 操作 | 效果 | 之后你在哪 |
|---|---|---|
exit | ==退出登录==,下次再连要重新输密码 同时退出 KVM 虚拟机里的 shell 会话 | 回到登录界面,console 还连着虚拟机 |
Ctrl+] | 彻底断开 virsh console 连接 | 回到宿主机 也就是 Vir-01 |
[root@jiuzhao ~]# exit注销jiuzhao login: 'console 还挂着,没真正退出去'✅️ '要再按 Ctrl+] 才回到宿主机'3)启动时直接从 console 看启动过程[root@Vir-01 ~]# virsh shutdown node# 先关机[root@Vir-01 ~]# virsh start --console node'可以在终端看到完整的启动过程'💡 virsh console 的优势:不需要等网络配好、不需要 SSH,机器只要能启动就能连进去,排障利器
开机自动扩容
⚠️ 这个配置不是给模板机用的,是给未来所有克隆机用的
后端盘 node.img = 15G(模板机)├── 模板机本身:15G 刚好够用,根分区 12.5G ✅└── 克隆机 node1.qcow2 = 30G └─ 开机 → 根分区还是 12.5G?❌ 浪费了 17.5G!===================================================配了 rc.local → 每台克隆机开机自动撑满自己的磁盘上限不配 → 给多少 G 都只能用到模板机的 12.5G1)安装分区扩展工具(在KVM虚拟机中操作)[root@jiuzhao ~]# yum -y install cloud-utils-growpart
2)查看当前分区布局[root@jiuzhao ~]# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTSvda 252:0 0 15G 0 disk├─vda1 252:1 0 1M 0 part├─vda2 252:2 0 1G 0 part /boot└─vda3 252:3 0 14G 0 part ├─rl-root 253:0 0 12.5G 0 'lvm'✅️/'根' '使用LVM逻辑卷管理技术进行扩充' ⬇️ 交换分区不要管它 └─rl-swap 253:1 0 1.5G 0 lvm [SWAP] ❌️[root@jiuzhao ~]# df -hT文件系统 类型 大小 已用 可用 已用% 挂载点/dev/mapper/rl-root xfs 13G 1.8G 11G 14% /👆 '逻辑卷名称' ⬆️ 文件类型 ⚠️ 如果是 ext4 文件系统,用 resize2fs 命令扩容 # 扩文件系统(重写账本,让文件系统认领新空间)① growpart 扩分区 → ② pvresize 扩 PV → ③ lvextend 扩 LV → ④ xfs_growfs 扩文件系统*(重写账本,让文件系统认领新空间)*
📌 为什么先 growpart? 磁盘是一整块,分区是上面画的”格子”:
克隆前 vda = 15G: 克隆后 vda = 30G:┌─────────────────┐ ┌──────────────────────────────┐│ vda1│vda2│ vda3 │ │ vda1│vda2│ vda3 │ 空着 ││ 1M │ 1G │ 14G │ │ 1M │ 1G │ 14G │ 15G │└─────────────────┘ │ growpart 把格子推到这 → │ └──────────────────────────────┘磁盘 30G 但 vda3 卡在 14G → 后面三步根本没空间可扩growpart 先把"格子"推满 → pvresize/lvextend/growfs 才有空间可用3)编辑 rc.local 实现开机自动扩容[root@Vir-01 ~]# ll /etc/rc.locallrwxrwxrwx. 1 root root 13 2025年 8月15日 /etc/rc.local → /etc/rc.d/rc.local '这次我们直接更改本体' ✅️ (软连接) 📌 (本体)⚠️ 软连接 lrwxrwxrwx 这个 rwx 是虚假的❌️,软连接权限永远 777'最后别忘记给本体赋 执行x 权限'[root@jiuzhao ~]# vim /etc/rc.d/rc.local.....................touch /var/lock/subsys/local###/usr/bin/growpart /dev/vda 3# ①扩分区:把 vda3 这个"格子"推到磁盘末尾/usr/sbin/pvresize /dev/vda3# ②扩 PV:让 LVM 知道"格子"变大了/usr/sbin/lvextend -l +100%FREE /dev/mapper/rl-root# ③扩 LV:逻辑卷吃掉新增空间/usr/sbin/xfs_growfs /dev/mapper/rl-root# ④让文件系统识别新空间 --> 根分区真正能用上新空间/usr/bin/sed '/^###/,$d' -i /etc/rc.d/rc.local # /^###/ --> 匹配 以###开头的行 # $d &代表最后一行,d代表删除 --> 删除 ### 以下,至最后一行# 执行后删除自身,防止重复扩容
4)赋权# Please note that you must run 'chmod +x /etc/rc.d/rc.local'[root@jiuzhao ~]# ls -l /etc/rc.d/rc.local-rw-r--r-- 1 root root 485 5月31日 17:49 /etc/rc.d/rc.local'它并没有执行权限'[root@jiuzhao ~]# chmod +x /etc/rc.d/rc.local[root@jiuzhao ~]# poweroff'关机'🤡 到这里建议拍个快照 🤡
virt-sysprep 制作模板
==virt-sysprep== 它处理的是镜像文件 (node.img),==不是 XML==
- 它直接读写磁盘 —> 清理里面的个性化信息
- 我们的 UUID/MAC 是后来==简化XML模版==后才清理干净的
- 让克隆出来的每台虚拟机都是”干净”的
⚠️ 在宿主机 (Vir-01) 上执行,VM 必须关机 (shut off)
开机执行会损坏磁盘数据,因为 virt-sysprep 直接读写镜像文件
1)确认 VM 已关机[root@Vir-01 ~]# virsh list --all | grep node - node shut off '必须是 shut off,不能是 running'
2)安装工具(宿主机上执行)[root@Vir-01 ~]# yum -y install guestfs-toolsjiuzhao@Ubuntu 桌面$ sudo apt install libguestfs-tools
3)列出所有能做的清理项目[root@Vir-01 ~]# virt-sysprep --list-operations'带 * 的是默认会执行的' --> 比如: ssh-hostkeys * → 默认清除 SSH 密钥 bash-history * → 默认清除 bash 历史✅️ 你可以 --enable '只执行'某几个、--disable 排除某几个
4)预演模式(只显示,不执行)[root@Vir-01 ~]# virt-sysprep --dry-run -d node-d = --domain # 指定虚拟机名字-n = --dry-run # 演练(干跑)
5)执行清理(过程较长,中途不要结束命令)[root@Vir-01 ~]# virt-sysprep -d node......[ 20.3] Performing "lvm-uuids" ...
6)也可直接对镜像文件操作[root@Vir-01 ~]# virt-sysprep -a /var/lib/libvirt/images/node.img📌 virt-sysprep 常用参数速查:
| 类别 | 参数 | 核心说明 |
|---|---|---|
| 指定目标 | -a, --add <磁盘镜像> | 直接指定磁盘镜像文件 |
| 指定目标 | -d, --domain <名称/UUID> | 指定 libvirt 管理的虚拟机 |
| 预演 | -n, --dry-run | 仅显示将执行的操作,不实际修改 |
| 自定义选择 | --enable <操作列表> | 精确启用指定操作 |
| 自定义选择 | --disable <操作列表> | 从默认操作中排除指定操作 |
| 系统配置 | --hostname <主机名> | 设定客户机主机名 |
| 系统配置 | --root-password <密码文件> | 设置 root 密码 |
| 系统配置 | --ssh-inject <用户:密钥文件> | 注入 SSH 公钥 |
| 文件操作 | --upload <文件:目标路径> | 上传文件到客户机 |
| 文件操作 | --delete <路径> | 删除客户机内文件/目录 |
| 文件操作 | --run <脚本> | 在客户机内执行自定义脚本 |
| 高级 | --selinux-relabel | 强制重新标记 SELinux 文件 |
| 高级 | -v, --verbose | 输出详细执行过程日志 |
- 默认操作集:不加
--enable/--disable时- virt-sysprep 运行一组标记为”默认”的安全清理操作(SSH 主机密钥、MAC 地址等)
✅️ 预演优先:执行关键操作前,强烈建议先用 --dry-run 确认操作范围
通过 --enable 可以精确指定==只运行==哪些操作,--disable 则在默认操作集基础上排除某些操作
📦 前端盘 && 批量创建
前端盘 (Frontend Disk)
通俗解释 COW (Copy-On-Write):
- 三个人共用一本教科书,谁都不在书上写字 → 一本就够了,==共享==
- 某天小明要在第 10 页做笔记 → 把那页==复印一份==给他,他在复印件上写,其他人继续用原书
- 只读时共享,修改时才复制 → 省空间、省内存、提升效率
通俗解释:
- ==后端盘== (
node.img) → 装了系统的基础”模具”,==所有虚拟机共享== - ==前端盘== (
node1.qcow2) → 每个虚拟机自己的”增量层”,==只存差异数据==
✅️ 前端盘依赖后端盘,前端盘记录了对后端盘的”修改”
📌 COW 在磁盘上的体现:
- 读: 前端盘有数据?→ 直接用自己的
- 前端盘没有?→ 去后端盘找
- 写: 永远只写前端盘,后端盘不变
1)创建前端盘[root@Vir-01 ~]# cd /var/lib/libvirt/images/[root@vhost1 images]# qemu-img create -f qcow2 -b node.img -F qcow2 node1.qcow2 20GFormatting 'node1.qcow2', fmt=qcow2 ... backing_file=node.img backing_fmt=qcow2===================================参数解读:-f qcow2 '前端盘的格式' --> (正在创建的这个新镜像)-b node.img '指定后端盘(模板)文件'-F qcow2 '后端盘的格式' --> (-b 指定的那个模板)node1.qcow2 '镜像文件名,后缀无所谓,.qcow2 自己好认'20G '前端盘的虚拟大小上限(可以大于后端盘,配合开机自动扩容)'[root@Vir-01 images]# ls | grep nodenode1.qcow2
2)查看前端盘信息[root@vhost1 images]# qemu-img info node1.qcow2image: "./Virt02/image-20260530175843424.png"file format: qcow2virtual size: 20 GiB (21474836480 bytes) '虚拟上限 20G'disk size: 196 KiB '实际才 196K,基于 COW 按需增长'backing file: node.img '对应的后端盘'backing file format: qcow2 '后端盘的格式'📌 后端盘 vs 前端盘对比:
| 后端盘 (Backing) | 前端盘 (Frontend) | |
|---|---|---|
| 角色 | ==模板/模具==,存操作系统 | ==增量层==,存差异数据 |
| 是否独立 | 独立完整镜像 | 依赖后端盘 |
| 修改 | ==不会变==(被所有前端盘共享) | 只记录本机的修改 |
XML 模板 && 定义虚拟机
1)复制模板机的 XML 作为模板(⚠️ 在 undefine 之前执行!)[root@Vir-01 ~]# cp -p /etc/libvirt/qemu/node.xml /home/template.xml[root@Vir-01 ~]# ls /home/template.xml/home/template.xml[root@Vir-01 ~]# sed -i 's@<name>node</name>@<name>template</name>@g' /home/template.xml✅️ 把里面的 <name>node</name> 改成 <name>template</name> '占位符'[root@Vir-01 ~]# sed -ri 's#(/var/lib/libvirt/images/)node.img#\1template.qcow2#' /home/template.xml⚠️ 改磁盘路径为 template.qcow2(占位符)📌 原因:sed 'template→node1' 时,磁盘路径也自动变成 node1.qcow2(前端盘)
2)用 sed 替换模板中的占位符,生成新虚拟机的 XML[root@Vir-01 ~]# sed 's@template@node1@' /home/template.xml'把 template 替换成实际 VM 名''默认全部输出至屏幕'[root@Vir-01 ~]# sed 's@template@node1@' /home/template.xml > /etc/libvirt/qemu/node1.xml'把屏幕中的内容重定向到新的XML模版中'
3)基于 XML 定义虚拟机[root@Vir-01 ~]# cd /etc/libvirt/qemu/'先切换至XML文件目录中'[root@Vir-01 qemu]# virsh define node1.xmldomain 'node' is already defined with 'uuid' ❌️3f4...xxx❌️❌️ UUID 重复[root@Vir-01 qemu]# uuidgena8a2a4e2-f918-44a6-a720-f635101d830e[root@Vir-01 qemu]# grep uuid node1.xml <uuid>a8a2a4e2-f918-44a6-a720-f635101d830e</uuid>⚠️ 进行更换[root@Vir-01 qemu]# virsh define node1.xmlDomain 'node1' defined from node1.xml✅️ '基于XML文件定义虚拟机' 📌 拿着这份"配置单" --> 告诉 libvirt:"记住这台虚拟机"[root@Vir-01 qemu]# virsh list --all Id Name State------------------------ - node shut off - node1 shut off
5)启动并验证[root@Vir-01 ~]# virsh start --console node1......jiuzhao login: root密码:[root@jiuzhao ~]# ip a s | grep 192.168.219 inet 192.168.219.113/24 brd 192.168.219.255 scope[root@jiuzhao ~]# lsblk | grep vda3└─vda3 252:3 0 19G 0 part'分区已自动扩容' 👆[root@jiuzhao ~]# df -h /文件系统 大小 已用 可用 已用% 挂载点/dev/mapper/rl-root 18G 1.8G 16G 11% /'文件系统也已扩容' 👆[root@jiuzhao ~]# ping -W2 -c2 www.baidu.comPING www.a.shifen.com (180.101.51.73) 56(84) 字节的数据。64 字节,来自 180.101.51.73: icmp_seq=1 ttl=127 时间=28.3 毫秒64 字节,来自 180.101.51.73: icmp_seq=2 ttl=127 时间=29.2 毫秒
--- www.a.shifen.com ping 统计 ---已发送 2 个包, 已接收 2 个包, 0% packet loss, time 1002msrtt min/avg/max/mdev = 28.300/28.767/29.234/0.467 ms'网络正常'[root@localhost ~]# poweroff批量创建与删除
✅️ 保留模板机,不隐藏镜像 策略:模板机 node 不变,node.img 可见 好处:随时 virsh start --console node 开机调整镜像,调完 virt-sysprep 重新清理[root@Vir-01 ~]# virsh list --all Id Name State-------------------------- - node shut off '模板机保留' - node1 shut off[root@Vir-01 ~]# ls /var/lib/libvirt/images/node.img node1.qcow2 '后端盘可见,方便调整''环境准备'# 为后面批量创建做准备
1)取消 node1 定义[root@Vir-01 ~]# virsh undefine node1Domain 'node1' has been undefined[root@Vir-01 ~]# ls /etc/libvirt/qemu/ | grep node1 | wc -l0 ---> 'XML文件也被删除'[root@Vir-01 ~]# virsh list --all | grep node1 | wc -l0
2)手动删除前端盘[root@Vir-01 ~]# ls /var/lib/libvirt/images/ | grep node1 | wc -l1[root@Vir-01 ~]# rm -rf /var/lib/libvirt/images/node1.qcow2⚠️ 迭代修改流程(模板机 node 保留不动,随时可开机调整):
① virsh undefine node1 取消定义(XML 一起被删,无需手动处理)
② rm -rf /var/lib/libvirt/images/node1.qcow2 (前端盘由 qemu-img 创建,不受 libvirt 管理,需手动删除)
③ virsh start --console node 开启模板机,修正问题
④ virt-sysprep -d node 重新清理镜像
⑤ 从重新生成前端盘和 XML 进行测试
创建
[root@Vir-01 ~]# vim /home/create_vm.sh# ===== 批量创建(模板机 node 不动、node.img 不隐藏) =====for i in $(seq 3)do # 1)创建前端盘 qemu-img create -f qcow2 -b /var/lib/libvirt/images/node.img -F qcow2 \ /var/lib/libvirt/images/node${i}.qcow2 20G > /dev/null
# 2)生成 XML(sed 替换占位符 template → nodeN) sed "s@template@node${i}@" /home/template.xml > /etc/libvirt/qemu/node${i}.xml
# 3)替换 UUID(sed 替换完每台 XML 的 UUID 还跟模板一样,必须换) NEW_UUID=$(uuidgen) sed -i "s@<uuid>.*</uuid>@<uuid>${NEW_UUID}</uuid>@" /etc/libvirt/qemu/node${i}.xml
# 4)定义虚拟机 virsh define /etc/libvirt/qemu/node${i}.xml > /dev/nulldone[root@Vir-01 ~]# sh /home/create_vm.sh[root@Vir-01 ~]# virsh list --all Id Name State------------------------ - node shut off - node1 shut off - node2 shut off - node3 shut off ✅️ '瞬间创建三台'[root@Vir-01 ~]# virsh start --console node3'随便登进去一台看看怎样?' ✅️ 能上网、也能自动扩容 ❌️ 就是 IP地址重复❓️ MAC地址重复❓️(IPv4 & IPv6)'优化后的 template.xml 已删硬编码 UUID/MAC,define 时 libvirt 自动生成,不会冲突'删除
[root@Vir-01 ~]# vim /home/clean_vm.sh# ===== 批量删除(模板机 node 保留不动) =====for i in $(seq 3)do virsh undefine node${i} # 删定义 + XML rm -rf /var/lib/libvirt/images/node${i}.qcow2 # 删前端盘done[root@Vir-01 ~]# sh /home/clean_vm.shDomain 'node1' has been undefinedDomain 'node2' has been undefinedDomain 'node3' has been undefined[root@Vir-01 ~]# ls /var/lib/libvirt/images/node.img[root@Vir-01 ~]# ls /etc/libvirt/qemu/autostart networks node.xml# 把模板机的配件都保留下来了📝 练习
优化 template.xml
⚠️ 原始模板的问题:15 个 PCIe root port、所有设备带硬编码 address、多余的 CD-ROM / 音频 / 看门狗 / 平板输入
精简原则:==删掉 libvirt 能自动生成的、删掉用不上的设备==
<domain type='kvm'> <name>template</name> <memory unit='KiB'>2097152</memory> <currentMemory unit='KiB'>2097152</currentMemory> <vcpu placement='static'>2</vcpu> <os> <type arch='x86_64' machine='pc-q35-rhel10.0.0'>hvm</type> <boot dev='hd'/> </os> <features> <acpi/> <apic/> </features> <cpu mode='host-passthrough' check='none' migratable='on'/> <clock offset='utc'> <timer name='rtc' tickpolicy='catchup'/> <timer name='pit' tickpolicy='delay'/> <timer name='hpet' present='no'/> </clock> <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>destroy</on_crash> <devices> <emulator>/usr/libexec/qemu-kvm</emulator>
<!-- 磁盘:占位符 template.qcow2,sed 替换成 node1.qcow2 等 --> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/var/lib/libvirt/images/template.qcow2'/> <target dev='vda' bus='virtio'/> </disk>
<!-- 网络 --> <interface type='network'> <source network='my_net'/> <model type='virtio'/> </interface>
<!-- console 串口 --> <serial type='pty'> <target type='isa-serial' port='0'> <model name='isa-serial'/> </target> </serial> <console type='pty'> <target type='serial' port='0'/> </console>
<!-- Guest Agent 通道 --> <channel type='unix'> <target type='virtio' name='org.qemu.guest_agent.0'/> </channel>
<!-- VNC 图形 --> <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'> <listen type='address' address='0.0.0.0'/> </graphics> <video> <model type='virtio' heads='1' primary='yes'/> </video>
<memballoon model='virtio'/> <rng model='virtio'> <backend model='random'>/dev/urandom</backend> </rng> </devices></domain>📌 精简前后对比:
| 项目 | 精简前 | 精简后 | 效果 |
|---|---|---|---|
| PCIe root port | 15 个 | 0 个(libvirt 自动) | MAC/UUID 冲突风险大降 |
| 硬编码 address | 每条设备一个 | 全部删除 | libvirt 自动分配 |
| CD-ROM / SATA | 有 | 删 | 用不到 |
| tablet/mouse/keyboard | 3 条 | 删 | 用不到 |
| audio / watchdog | 有 | 删 | 用不到 |
| metadata / pm | 有 | 删 | 不影响功能 |
| UUID 和 MAC | 固定值 | 删(define 时自动生成) | 批量不用手动换 |
📌 精简效果:从 170 行臃肿 XML 砍到 70 行,核心就 7 件东西:
磁盘 + 网络 + console + Guest Agent + VNC + memballoon + rng
删掉的:PCIe 地址、15 个 root port、CD-ROM、USB 控制器、输入设备、音频、看门狗、metadata、pm、硬编码 UUID/MAC
原因:==libvirt 能自动生成的,就别写死——写了反而冲突==
1)更换我们的xml[root@Vir-01 ~]# vim /home/template.xml
2)优化我们的创建脚本[root@Vir-01 ~]# vim /home/create_vm.sh# ===== 批量创建(模板机 node 不动、node.img 不隐藏) =====# 优化后的 template.xml 已删硬编码 UUID/MAC,define 时 libvirt 自动生成,不会冲突for i in $(seq 3)do # 1)创建前端盘 qemu-img create -f qcow2 -b /var/lib/libvirt/images/node.img -F qcow2 \ /var/lib/libvirt/images/node${i}.qcow2 20G > /dev/null
# 2)生成 XML(sed 替换占位符 template → nodeN) sed "s@template@node${i}@" /home/template.xml > /etc/libvirt/qemu/node${i}.xml
# 3)定义虚拟机(UUID 和 MAC 自动生成) virsh define /etc/libvirt/qemu/node${i}.xml > /dev/nulldone[root@Vir-01 ~]# sh /home/create_vm.sh[root@Vir-01 ~]# virsh list --all Id Name State------------------------ - node shut off - node1 shut off - node2 shut off - node3 shut off
1)node3[root@Vir-01 ~]# virsh start --console node3jiuzhao login: root密码:[root@jiuzhao ~]# hostname -I192.168.219.134 📌[root@jiuzhao ~]# lsblk | grep vda3└─vda3 252:3 0 19G 📌 0 part[root@jiuzhao ~]# ping -W2 -c2 www.baidu.comPING www.a.shifen.com (180.101.51.73) 56(84) 字节的数据。64 字节,来自 180.101.51.73: icmp_seq=1 ttl=127 时间=30.4 毫秒64 字节,来自 180.101.51.73: icmp_seq=2 ttl=127 时间=29.4 毫秒
2)node2[root@Vir-01 ~]# virsh start --console node2[root@jiuzhao ~]# hostname -I192.168.219.35 ✅️ IP变了[root@jiuzhao ~]# ping -W2 -c2 www.baidu.comPING www.a.shifen.com (180.101.51.73) 56(84) 字节的数据。64 字节,来自 180.101.51.73: icmp_seq=1 ttl=127 时间=27.6 毫秒64 字节,来自 180.101.51.73: icmp_seq=2 ttl=127 时间=28.0 毫秒✅️ 也能ping通外网
3)UUID也各不相同[root@Vir-01 ~]# virsh dominfo node1 | grep UUIDUUID: ccd0fa09-ff7c-4eae-b0f8-9c2f5801d9b5[root@Vir-01 ~]# virsh dominfo node2 | grep UUIDUUID: fa248514-3cea-45fb-83e1-9de69a9c6687[root@Vir-01 ~]# virsh dominfo node3 | grep UUIDUUID: e8ffb621-655b-4d8d-b960-7d396e000aff批量管理 Shell 脚本
📌 两个脚本、功能不重叠:
| 脚本 | 用途 | 用法 |
|---|---|---|
create_vm.sh | 创建 | sh create_vm.sh (3台) / sh create_vm.sh web01 db01 (按名) |
clean_vm.sh | 删除 | sh clean_vm.sh node1 / sh clean_vm.sh all (全删,保留模板机) |
📌 脚本设计要点:
- 两个脚本各管一面:
create_vm.sh管建、clean_vm.sh管删- 启动和关机用
virsh start/virsh shutdown直接敲就行,没必要写脚本- 真正的批量场景(100 台),for 循环足够了
# ===== /home/create_vm.sh =====# 用法:sh create_vm.sh → 批量创建 3 台 (node1~node3)# sh create_vm.sh web01 db01 → 按名称创建#!/bin/bashTEMPLATE="/home/template.xml"BACKING="/var/lib/libvirt/images/node.img"COUNT=${1:-3}
if [ $# -eq 0 ]; then for i in $(seq $COUNT); do echo -n "创建 node${i} ... " # 1)创建前端盘(> /dev/null 屏蔽成功输出,错误仍会显示) qemu-img create -f qcow2 -b $BACKING -F qcow2 \ /var/lib/libvirt/images/node${i}.qcow2 20G > /dev/null # 2)生成 XML sed "s@template@node${i}@" $TEMPLATE > /etc/libvirt/qemu/node${i}.xml # 3)定义虚拟机 virsh define /etc/libvirt/qemu/node${i}.xml > /dev/null && echo "✅️ 完成" || echo "❌️ 失败" doneelse for name in "$@"; do echo -n "创建 ${name} ... " qemu-img create -f qcow2 -b $BACKING -F qcow2 \ /var/lib/libvirt/images/${name}.qcow2 20G > /dev/null sed "s@template@${name}@" $TEMPLATE > /etc/libvirt/qemu/${name}.xml virsh define /etc/libvirt/qemu/${name}.xml > /dev/null && echo "✅️ 完成" || echo "❌️ 失败" donefi# ===== /home/clean_vm.sh =====# 用法:sh clean_vm.sh node1 node2 → 删指定 VM# sh clean_vm.sh all → 删全部(模板机 node 除外)#!/bin/bashIMAGES="/var/lib/libvirt/images"
if [ "$1" = "all" ]; then for name in $(virsh list --all --name | grep -v '^node$'); do echo -n "删除 ${name} ... " virsh destroy $name 2>/dev/null # ①强制关机(running → shut off) virsh undefine $name > /dev/null # ②取消定义(删 XML) rm -rf $IMAGES/${name}.qcow2 # ③删前端盘 echo "✅️ 完成" doneelse for name in "$@"; do echo -n "删除 ${name} ... " virsh destroy $name 2>/dev/null # ①强制关机 virsh undefine $name > /dev/null # ②取消定义(同时删除 XML) rm -rf $IMAGES/${name}.qcow2 # ③删前端盘 echo "✅️ 完成" donefi🔁 全流程串联
📌 从零到批量部署的完整链路:
① 创建虚拟网络 my_net --> virsh net-define/start,配好 DHCP② 创建后端盘 node.img --> qemu-img create -f qcow2 node.img 15G③ 安装模板机 node --> virt-install --disk path=node.img --network=my_net ...④ 模板机内部配置 --> qemu-guest-agent、cloud-init、console、rc.local 开机扩容⑤ virt-sysprep 清理 --> 去个性化,模板机 node 保留不动(方便日后开机调整)⑥ 备份优化模板 XML --> cp node.xml --> /home/template.xml --> 精简瘦身⑦ 批量创建 --> sh create_vm.sh --> for 循环:前端盘 + XML + define⑧ 按需迭代 --> 模板机开机 --> 调整 --> virt-sysprep --> 重新批量模板机 node 始终保留、node.img 不隐藏,可随时开机修正,整个流程闭环
📚 知识补充:两大工具集
day02 实验中用到的绝大多数是 libvirt 系命令,但 libguestfs 系同样重要——==无需启动虚拟机就能操作磁盘==
📌 一句话:==libvirt 系管生命周期==,==libguestfs 系管磁盘内部==
libvirt 管理工具
- 以
virsh和virt-install为核心
| 类别 | 命令 | 实验中用到 |
|---|---|---|
| 生命周期 | virsh list start shutdown destroy undefine define | ✅️ 全部 |
| 查看 | virsh dominfo dumpxml domstate domifaddr | ✅️ 全部 |
| 连接 | virsh uri console | ✅️ 全部 |
| 网络 | virsh net-list net-edit net-define net-start net-destroy | ✅️ 全部 |
| 资源 | virsh setmem setvcpus | 了解即可 |
| 图形 | virt-viewer | 了解即可 |
libguestfs 工具集
- 无需启动 VM 就能动磁盘
| 命令 | 作用 | 一句话 |
|---|---|---|
virt-cat | 不启动 VM 查看磁盘内文件 | 类似 cat,但目标是 VM 磁盘 |
virt-edit | 不启动 VM 编辑磁盘内文件 | 类似 vim,VM 死机也能改配置 |
virt-ls | 不启动 VM 列出磁盘内目录 | 类似 ls |
virt-copy-in/out | 不启动 VM 向磁盘复制/复制出文件 | VM 死机了也能捞出数据 |
virt-sysprep | 清理 VM 个性化信息 | ✅️ 实验中已用到 |
virt-resize | 调整磁盘镜像大小 | 线下扩容 |
virt-sparsify | 回收磁盘空闲空间 | 节省宿主机存储 |
virt-inspector | 深入分析磁盘镜像 | 查看 OS 版本、文件系统等 |
virt-p2v / virt-v2v | 物理机/其他平台 → KVM | 迁移场景 |
💡 核心优势:VM 死机了?直接在宿主机 virt-cat 看日志、virt-edit 改配置,相当于给虚拟机开了一个”后门”
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!




