虚拟化管理与快速创建虚拟机

9493 字
47 分钟
虚拟化管理与快速创建虚拟机

虚拟化管理 && 快速创建虚拟机#

[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 的 IPvirsh domifaddr --source agent node
virsh domstate查看看 VM 当前是 running 还是 shut offvirsh 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创建命令行安装新 VMvirt-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

⚙️ 远程连接管理#

📌 核心目标在一台机器上,管理另一台机器里的虚拟机

Terminal window
'环境准备'
✅️ 两台虚拟机
[root@Vir-01 ~]#
[root@Vir-02 ~]#
====================
[root@Vir-01 ~]# rpm -qa qemu-kvm libvirt virt-install
qemu-kvm-10.0.0-14.el10_1.5.x86_64
libvirt-11.5.0-4.8.el10_1.x86_64
virt-install-5.0.0-1.el10.noarch
[root@Vir-01 ~]# systemctl is-active libvirtd
active
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-01
10.0.0.199 v2 Vir-02
EOF
[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'(连本地)
Note

📌 其实每一条 virsh 命令背后都有一个 URI,你之前一直不写,就是用了默认值:

Terminal window
4)查看本地 libvirt 连接 URI
[root@Vir-01 ~]# virsh uri
qemu:///system
'默认连接本地 qemu 系统实例'

📌 核心要记住的 URI 格式

连接目标URI
本地系统实例qemu:///system
本地会话实例qemu:///session
远程 (SSH) ✅️qemu+ssh://root@<IP>/system
Terminal window
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`

核心优势#

virshlibvirtd 是什么关系?

Terminal window
virsh (客户端) ──通信──> libvirtd (守护进程)
'你敲的命令' '后台 systemd 服务'
✅️总在你本机 ⚠️可以在本机,也可以在对面机器上
  • virsh = 遥控器(你手里拿的工具)
  • libvirtd = 电视机(真正干活的东西)
  • 你按遥控器 → 电视机收到信号 → 换台

完整流程(把 virsh 补进来):

Terminal window
你(Vir-02) 在终端敲 virsh
virsh(本机客户端) 解析 URI,发现目标是远程
SSH 隧道
Vir-01 上的 libvirtd(远程服务端) 收到指令
libvirtd 真正干活:查虚拟机、启动、关机...
结果原路返回 virsh 显示在你终端上
  • 你之前本地操作时,virsh 自动连的是本地 libvirtdqemu:///system
  • 远程管理就是把 virsh 的目标换成对面机器上的 libvirtdqemu+ssh://
Tip

==全程不需要先 SSH 登录到 Vir-01 再敲命令==

✅️ 相当于:你家服务器放客厅,你坐卧室用笔记本就能开关那台服务器上的==虚拟机==

传统方式virsh 远程
ssh v1 登录过去,在远程 shell 里敲命令坐在自己机器上,-c qemu+ssh:// 一步到位
10 台机器?那就登录 10 次、敲 10 次10 台机器?写个 for 循环,脚本一次性跑完
Note

📌 一句话:让远程的 libvirtd 就像安装在本地一样

你写的脚本循环 100 台机器,每条命令自动通过 SSH 隧道飞过去执行、结果飞回来

virsh 交互式#

  • 前面每次都要带一长串 -c qemu+ssh://...,太累了

  • ==交互式模式==就是:==先挂上去,然后连续敲命令==,不用每条都加 -c

Terminal window
'对比两种方式'
1)一次性命令(每条都要带 -c)
virsh -c qemu+ssh://root@v1/system list --all
virsh -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 '退出'

🌏 ==场景类比==:

一次性命令交互式切换
每次打电话都要拨完整的号码✅️ 打开通讯录,点一下联系人就开始通话
Terminal window
'实际实验'
[root@Vir-02 ~]# virsh
Welcome 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-01
virsh # list --all
Id Name State
------------------------------
12 rocky10-test running
virsh # dominfo rocky10-test
Id: 12
Name: rocky10-test
UUID: 4b4478bd-d7d7-48c2-98b5-db9dd79db6c3
OS Type: hvm
State: 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 里早就开了虚拟化吗,怎么这里还要开一次?

Terminal window
VMware --> 勾选"虚拟化 Intel VT-x/AMD-V"
Vir-01 (L1) --> nested=1
rocky10-test (L2) --> 里面还能跑虚拟机
开关在哪设作用
第一层 VMware 勾选 ✅️VMware 虚拟机设置让 L1 能用 CPU 虚拟化功能,==没有它 KVM 根本用不了==
第二层 nested=1Linux 内核模块参数让 L1 把虚拟化能力继续往下传给 L2,==默认不开==
Tip

📌 一句话:没有第一层 → KVM 不工作;没有第二层 → KVM 能工作,但虚拟机里不能再跑虚拟机

启用方法#

无论 Intel 还是 AMD,流程一样:看 CPU 标志 → 看内核模块状态 → 没开 → 就开

Terminal window
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
# ⬆️ AMD
Y 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 速查

IntelAMD
CPU 标志vmxsvm
内核模块kvm_intelkvm_amd
配置行options kvm_intel nested=1options kvm_amd nested=1

📦 虚拟机组成 && 模版机制作#

虚拟机 = XML 文件 + 镜像文件#

通俗解释

  • XML 文件 → 相当于==机箱配置单==,定义了 CPU、内存、网卡、磁盘等信息
  • 镜像文件 → 相当于==硬盘==,存操作系统和数据
Terminal window
[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/
Terminal window
jiuzhao@Ubuntu 桌面$ ls /etc/libvirt/qemu
centos7.0.xml networks ......
jiuzhao@Ubuntu 桌面$ ls /etc/libvirt/qemu/networks/
# Ubuntu的都在这个目录下
autostart default.xml

虚拟网络创建#

默认的 default 网络使用 NAT 模式,网段 192.168.122.0/24,这里我们新建一个 my_net 网络

Terminal window
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 ~]# uuidgen
3a9e9bcb-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.xml
Network 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_net
Network my_net started
✅️ 启动(应用)自定义网络
[root@Vir-01 ~]# virsh net-autostart my_net
Network 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_net
16: 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
Tip
  • 网络和虚拟机不一样,没有”优雅关闭” shutdown 的说法,因为网络里没系统、没数据

  • 要彻底删除一个网络,==两步都要执行==:先 net-destroy 停止,再 net-undefine 删除定义

Terminal window
[root@Vir-01 ~]# virsh net-destroy my_net
Network my_net destroyed
[root@Vir-01 ~]# virsh net-undefine my_net
Network 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.xml
ls: cannot access ...xxx No such file or directory

后端盘 (Backing File)#

Terminal window
1)创建后端盘(没有系统,'后缀随意,格式为 qcow2'
[root@Vir-01 ~]# cd /var/lib/libvirt/images/
[root@Vir-01 images]# ls
rocky10-test.qcow2
[root@Vir-01 images]# qemu-img create -f qcow2 node.img 15G
Formatting 'node.img', fmt=qcow2 cluster_size=65536 ... size=8589934592
⚠️ '8G系统盘空间太少了 --> 15G'

📌 ==参数拆解==:

选项含义
qemu-img createqemu-img 的 create 子命令,用来创建新镜像
-f qcow2-f = format,指定镜像格式为 qcow2(精简置备 + COW 支持)
node.img镜像文件名,后缀无所谓libvirt 不靠后缀识别格式
15G虚拟磁盘上限,实际不会立即占满(精简置备)

image-20260530180101188
image-20260530180101188

Note
  • 空间给小了❓️
    • 重新给空间试试 给15G 这次肯定够用了
Terminal window
[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.qcow2
3.1G rocky10-test.qcow2
'上面写的21G实际上也没有占用那么多'
2)查看镜像信息
[root@vhost1 images]# qemu-img info node.img
image: "./Virt02/image-20260530175843424.png"
file format: qcow2
virtual 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 安装虚拟机

  • 重点讲 ==安装后的模版配置==
Terminal window
[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.0VNC 图形控制台,监听所有地址方便远程连接虚拟机图形界面
--wait -1等待安装完成后才退出不加的话命令立刻返回,看不到安装进度

image-20260530175843424
image-20260530175843424

image-20260531092217136
image-20260531092217136

Terminal window
1)SSH 连接到新安装的虚拟机
[root@Vir-01 ~]# ssh root@192.168.219.10
root@192.168.219.10's password': <-- 1
Last 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 1002ms
rtt 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*.repo
dnf clean all
dnf makecache
dnf install epel-release -y
# 扩展仓库
dnf makecache
dnf 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/profile
source /etc/profile
# ssh优化
cat >>/etc/ssh/sshd_config<<EOF
UseDNS no
#相当于网络命令的-n选项,这个就是说不解析为主机名,直接成IP地址.
GSSAPIAuthentication no
#关闭GSS认证.
EOF
systemctl restart sshd
# 防火墙&&SElinux
sed -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.net
cat > /etc/motd << 'EOF'
(@) * (@) * (@)
: * (@) * (@) * .;
(@) * (@) * (@) * (@)
* ; * ; (@) * ; * :
;\ \ \ \| / / /;
\\ \ Y/ / /
`_\ |/ _' '
/ \\Y// \
( ,-}={-, )
\_//((\_/
//))(\
(/ ))
(/
EOF
reboot
# 重启生效配置
Terminal window
[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.

image-20260531093249756
image-20260531093249756

QEMU Guest Agent#

==QEMU Guest Agent== 在虚拟机内部运行,就像是给宿主机开了一个通向虚拟机内部的”传话通道”

  • 使其能从外部获取虚拟机 IP、冻结文件系统等
Terminal window
[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 node
Domain 'node' started
[root@Vir-01 ~]# virsh domifaddr --source agent node
domifaddr = 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、查磁盘、冻结文件系统…啥都能干
Terminal window
2)更底层的命令
[root@Vir-01 ~]# virsh qemu-agent-command node '{"execute":"guest-ping"}'
qemu-agent-command # 原始 QEMU Agent 命令接口
node # 虚拟机名称
'{"execute":"..."}' # 发给 Agent 的 JSON 指令
================================
{"return":{}}
'返回 return 说明通信正常'
3)查 IP
virsh qemu-agent-command node '{"execute":"guest-network-get-interfaces"}'
4)冻结文件系统(做快照前用)
virsh qemu-agent-command node '{"execute":"guest-fsfreeze-freeze"}'
Note

💡 domifaddr --source agent 是高层封装,✅️ qemu-agent-command 更底层,能做的事更多

cloud-init#

  • ==cloud-init== 用于在虚拟机==首次启动时==自动完成主机名、用户、SSH 密钥、网络和软件包等初始化配置
    • cloud-init 不只是装一下,它解决的是一个实际问题:
Terminal window
模板机 --克隆--> node1 --开机--> cloud-init 自动帮你:
├── 设个新主机名(node1,不是 localhost)
├── 注入 SSH 公钥(不用每次手动 ssh-copy-id)
├── 配好网络
└── 装好你指定的软件包
Caution

不装 cloud-init → 每克隆一台都得手动登进去改主机名、配 SSH…100 台要疯 装了之后 → 模板做好、virt-sysprep 清理完,克隆出来的机器第一次开机自动搞定,开机即用

Terminal window
[root@Vir-01 ~]# ssh root@192.168.219.10
root@192.168.219.10's password':
[root@jiuzhao ~]# dnf install -y cloud-init
完毕!

📌 一句话:cloud-init 就是每台新虚拟机的”==自动入职流程==“,开机即用

virsh console 串口控制台#

console 配置是配在 ==KVM 虚拟机内部==(不是配在 Vir-01 上)

配好之后,宿主机 (Vir-01) 可以==绕过网络==直接连进去

Terminal window
SSH 是前门:宿主机 网络 VM IP 进去 (网络挂了就歇菜)
console 是后门:宿主机 virsh console 直接进去 (只要 VM 活着就能进)
===================================================
🌰 层级关系:
你的笔记本 (物理机)
└── VMware 虚拟机 (Vir-01) ← 对 KVM 来说,它就是"宿主机"
└── KVM 虚拟机 (node) ← console 配在这台里面
Terminal window
1)先检测:内核参数里有没有 console❓️
[root@jiuzhao ~]# grubby --info=ALL | grep console | wc -l
0
'如果没有,才需要配置:'(在 KVM 虚拟机里面执行,配一次就行)
[root@jiuzhao ~]# grubby --update-kernel=ALL --args="console=ttyS0,115200n8"
'grubby 本身安全,参数已存在就不会重复添加'
[root@jiuzhao ~]# grubby --info=ALL | grep console
args="... console=ttyS0,115200n8" 已经有了,跳过配置
args="... console=ttyS0,115200n8"
# 显示两条是因为 --info=ALL 显示了两个内核条目(正常 + rescue),不是配重了
[root@jiuzhao ~]# reboot
2)等待重启后,在宿主机上通过 console 连接
[root@Vir-01 ~]# virsh console node
Connected 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
Terminal window
[root@jiuzhao ~]# exit
注销
jiuzhao login: 'console 还挂着,没真正退出去'
✅️ '要再按 Ctrl+] 才回到宿主机'
Terminal window
3)启动时直接从 console 看启动过程
[root@Vir-01 ~]# virsh shutdown node
# 先关机
[root@Vir-01 ~]# virsh start --console node
'可以在终端看到完整的启动过程'
Tip

💡 virsh console 的优势:不需要等网络配好、不需要 SSH,机器只要能启动就能连进去,排障利器

开机自动扩容#

Note

⚠️ 这个配置不是给模板机用的,是给未来所有克隆机用的

Terminal window
后端盘 node.img = 15G(模板机)
├── 模板机本身:15G 刚好够用,根分区 12.5G
└── 克隆机 node1.qcow2 = 30G
└─ 开机 根分区还是 12.5G?❌ 浪费了 17.5G!
===================================================
配了 rc.local 每台克隆机开机自动撑满自己的磁盘上限
不配 给多少 G 都只能用到模板机的 12.5G
Terminal window
1)安装分区扩展工具(在KVM虚拟机中操作)
[root@jiuzhao ~]# yum -y install cloud-utils-growpart
2)查看当前分区布局
[root@jiuzhao ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 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 命令扩容
# 扩文件系统(重写账本,让文件系统认领新空间)
Tip

growpart 扩分区 → ② pvresize 扩 PV → ③ lvextend 扩 LV → ④ xfs_growfs 扩文件系统*(重写账本,让文件系统认领新空间)*

📌 为什么先 growpart? 磁盘是一整块,分区是上面画的”格子”:

Terminal window
克隆前 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 才有空间可用
Terminal window
3)编辑 rc.local 实现开机自动扩容
[root@Vir-01 ~]# ll /etc/rc.local
lrwxrwxrwx. 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
'关机'
Warning

🤡 到这里建议拍个快照 🤡

virt-sysprep 制作模板#

Note

==virt-sysprep== 它处理的是镜像文件 (node.img),==不是 XML==

  • 它直接读写磁盘 —> 清理里面的个性化信息
    • 我们的 UUID/MAC 是后来==简化XML模版==后才清理干净的
  • 让克隆出来的每台虚拟机都是”干净”的

⚠️ 在宿主机 (Vir-01) 上执行,VM 必须关机 (shut off)

开机执行会损坏磁盘数据,因为 virt-sysprep 直接读写镜像文件

Terminal window
1)确认 VM 已关机
[root@Vir-01 ~]# virsh list --all | grep node
- node shut off '必须是 shut off,不能是 running'
2)安装工具(宿主机上执行)
[root@Vir-01 ~]# yum -y install guestfs-tools
jiuzhao@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输出详细执行过程日志
Tip
  • 默认操作集:不加 --enable/--disable
    • virt-sysprep 运行一组标记为”默认”的安全清理操作(SSH 主机密钥、MAC 地址等)

✅️ 预演优先:执行关键操作前,强烈建议先用 --dry-run 确认操作范围 通过 --enable 可以精确指定==只运行==哪些操作,--disable 则在默认操作集基础上排除某些操作


📦 前端盘 && 批量创建#

前端盘 (Frontend Disk)#

Important

通俗解释 COW (Copy-On-Write)

  • 三个人共用一本教科书,谁都不在书上写字 → 一本就够了,==共享==
  • 某天小明要在第 10 页做笔记 → 把那页==复印一份==给他,他在复印件上写,其他人继续用原书
  • 只读时共享,修改时才复制 → 省空间、省内存、提升效率
Note

通俗解释

  • ==后端盘== (node.img) → 装了系统的基础”模具”,==所有虚拟机共享==
  • ==前端盘== (node1.qcow2) → 每个虚拟机自己的”增量层”,==只存差异数据==

✅️ 前端盘依赖后端盘,前端盘记录了对后端盘的”修改”

📌 COW 在磁盘上的体现

  • 读: 前端盘有数据?→ 直接用自己的
    • 前端盘没有?→ 去后端盘找
  • 写: 永远只写前端盘,后端盘不变
Terminal window
1)创建前端盘
[root@Vir-01 ~]# cd /var/lib/libvirt/images/
[root@vhost1 images]# qemu-img create -f qcow2 -b node.img -F qcow2 node1.qcow2 20G
Formatting '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 node
node1.qcow2
2)查看前端盘信息
[root@vhost1 images]# qemu-img info node1.qcow2
image: "./Virt02/image-20260530175843424.png"
file format: qcow2
virtual size: 20 GiB (21474836480 bytes) '虚拟上限 20G'
disk size: 196 KiB '实际才 196K,基于 COW 按需增长'
backing file: node.img '对应的后端盘'
backing file format: qcow2 '后端盘的格式'

📌 后端盘 vs 前端盘对比

后端盘 (Backing)前端盘 (Frontend)
角色==模板/模具==,存操作系统==增量层==,存差异数据
是否独立独立完整镜像依赖后端盘
修改==不会变==(被所有前端盘共享)只记录本机的修改

XML 模板 && 定义虚拟机#

Terminal window
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.xml
domain 'node' is already defined with 'uuid' ❌️3f4...xxx❌️
❌️ UUID 重复
[root@Vir-01 qemu]# uuidgen
a8a2a4e2-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.xml
Domain '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.com
PING 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 1002ms
rtt min/avg/max/mdev = 28.300/28.767/29.234/0.467 ms
'网络正常'
[root@localhost ~]# poweroff

批量创建与删除#

Terminal window
✅️ 保留模板机,不隐藏镜像
策略:模板机 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 '后端盘可见,方便调整'
Terminal window
'环境准备'
# 为后面批量创建做准备
1)取消 node1 定义
[root@Vir-01 ~]# virsh undefine node1
Domain 'node1' has been undefined
[root@Vir-01 ~]# ls /etc/libvirt/qemu/ | grep node1 | wc -l
0 ---> 'XML文件也被删除'
[root@Vir-01 ~]# virsh list --all | grep node1 | wc -l
0
2)手动删除前端盘
[root@Vir-01 ~]# ls /var/lib/libvirt/images/ | grep node1 | wc -l
1
[root@Vir-01 ~]# rm -rf /var/lib/libvirt/images/node1.qcow2
Tip

⚠️ 迭代修改流程(模板机 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 进行测试

创建#

Terminal window
[root@Vir-01 ~]# vim /home/create_vm.sh
Terminal window
# ===== 批量创建(模板机 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/null
done
Terminal window
[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 自动生成,不会冲突'

删除#

Terminal window
[root@Vir-01 ~]# vim /home/clean_vm.sh
Terminal window
# ===== 批量删除(模板机 node 保留不动) =====
for i in $(seq 3)
do
virsh undefine node${i} # 删定义 + XML
rm -rf /var/lib/libvirt/images/node${i}.qcow2 # 删前端盘
done
Terminal window
[root@Vir-01 ~]# sh /home/clean_vm.sh
Domain 'node1' has been undefined
Domain 'node2' has been undefined
Domain '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 port15 个0 个(libvirt 自动)MAC/UUID 冲突风险大降
硬编码 address每条设备一个全部删除libvirt 自动分配
CD-ROM / SATA用不到
tablet/mouse/keyboard3 条用不到
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 能自动生成的,就别写死——写了反而冲突==

Terminal window
1)更换我们的xml
[root@Vir-01 ~]# vim /home/template.xml
2)优化我们的创建脚本
[root@Vir-01 ~]# vim /home/create_vm.sh
Terminal window
# ===== 批量创建(模板机 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/null
done
Terminal window
[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 node3
jiuzhao login: root
密码:
[root@jiuzhao ~]# hostname -I
192.168.219.134 📌
[root@jiuzhao ~]# lsblk | grep vda3
└─vda3 252:3 0 19G 📌 0 part
[root@jiuzhao ~]# ping -W2 -c2 www.baidu.com
PING 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 -I
192.168.219.35 ✅️ IP变了
[root@jiuzhao ~]# ping -W2 -c2 www.baidu.com
PING 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 UUID
UUID: ccd0fa09-ff7c-4eae-b0f8-9c2f5801d9b5
[root@Vir-01 ~]# virsh dominfo node2 | grep UUID
UUID: fa248514-3cea-45fb-83e1-9de69a9c6687
[root@Vir-01 ~]# virsh dominfo node3 | grep UUID
UUID: 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/bash
TEMPLATE="/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 "❌️ 失败"
done
else
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 "❌️ 失败"
done
fi
# ===== /home/clean_vm.sh =====
# 用法:sh clean_vm.sh node1 node2 → 删指定 VM
# sh clean_vm.sh all → 删全部(模板机 node 除外)
#!/bin/bash
IMAGES="/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 "✅️ 完成"
done
else
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 "✅️ 完成"
done
fi

🔁 全流程串联#

📌 从零到批量部署的完整链路

Terminal window
创建虚拟网络 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 --> 重新批量
Tip

模板机 node 始终保留、node.img 不隐藏,可随时开机修正,整个流程闭环


📚 知识补充:两大工具集#

day02 实验中用到的绝大多数是 libvirt 系命令,但 libguestfs 系同样重要——==无需启动虚拟机就能操作磁盘==

📌 一句话:==libvirt 系管生命周期==,==libguestfs 系管磁盘内部==

libvirt 管理工具#

  • virshvirt-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 改配置,相当于给虚拟机开了一个”后门”

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

虚拟化管理与快速创建虚拟机
https://www.kpyun.fun/posts/services/virt/virt02/
作者
久棹
发布于
2026-04-17
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录