KVM

一、KVM 概述

KVM (Kernel-based Virtual Machine) 是基于 Linux 内核的开源全虚拟化解决方案。它将 Linux 内核转化为一个 Hypervisor (虚拟机监视器)。

  • 核心原理:利用处理器硬件辅助虚拟化扩展(Intel VT-x 或 AMD-V)。

  • 组件构成:

    • kvm.ko:提供核心虚拟化基础设施。
    • kvm-intel.ko / kvm-amd.ko:处理器特定的内核模块。
  • 特性:每个虚拟机 (VM) 被视为一个标准的 Linux 进程,由标准 Linux 调度程序进行调度。每个 VM 拥有独立的虚拟硬件(CPU、内存、网卡、磁盘等)。

参考文档:

  • 官方网站:https://www.linux-kvm.org/
  • 红帽KVM介绍:https://www.redhat.com/zh/topics/virtualization/what-is-KVM
  • Readhat虚拟化指南:https://access.redhat.com/documentation/zh%EF%BF%BEcn/red_hat_enterprise_linux/7/html/virtualization_getting_started_guide/index

1. KVM 与 QEMU 的协作

KVM 并不是独立运行的,它必须与 QEMU 协同工作。

角色分工:

组件 运行空间 核心职责 备注
KVM 内核空间 (Kernel) 虚拟化 CPU 和 内存 (MMU) 性能极高,接近原生硬件
QEMU 用户空间 (User) 模拟 I/O 设备 (磁盘、网卡、显卡、键盘等) 灵活性高,支持多种硬件模拟

工作机制:

  • I/O 拦截机制:当 Guest OS(虚拟机)尝试访问硬件资源时,指令会被 KVM 内核模块拦截。如果是 CPU 或内存指令,KVM 直接处理;如果是 I/O 请求(如读写磁盘),KVM 会将其转发给用户空间的 QEMU 进程,由 QEMU 模拟真实的硬件响应。
  • 接口通信:用户空间程序(QEMU)通过 /dev/kvm 接口,使用 ioctl() 系统调用与内核中的 KVM 模块进行交互(如创建虚拟机、分配内存、启动 CPU)。

为什么要配合使用?

  • QEMU 单独运行:可以模拟整台机器,但由于所有指令都靠软件转译,速度极慢。
  • KVM 单独运行:无法模拟出一台完整的电脑,因为它不具备模拟显示器、键盘、网卡等外设的能力。
  • 两者结合:KVM 提供高性能的处理器运行环境,QEMU 提供丰富的设备模拟,从而实现高性能的全虚拟化。

2. 管理工具链

KVM 本身只提供了底层的虚拟化能力,实际生产中我们通常使用管理工具来简化操作。

Libvirt:这是管理 KVM 最核心的 API 库。它屏蔽了复杂的底层命令行,提供统一的 XML 配置文件。我们常用的 virsh 命令行工具和 virt-manager (图形界面) 都是基于 Libvirt 的。

Proxmox VE (PVE):

  • 特点:基于 Debian,集成 KVM 和 LXC(容器)。
  • 优势:自带 Web 界面,安装简单,非常适合中小企业或私有实验室环境。

oVirt:

  • 特点:Red Hat 企业级虚拟化 (RHEV) 的开源版本。
  • 优势:功能极其强大,支持复杂的存储、网络节点管理,适合大规模环境。

OpenStack:

  • 特点:目前最主流的开源云操作系统(IaaS)。
  • 优势:不仅仅是管理虚拟机,还管理整个数据中心的计算、存储和网络资源。

参考文档:http://www.linux-kvm.org/page/Management_Tools


二、KVM 安装

  • 需要在 BIOS 中开启虚拟化功能

1. 检查 KVM 环境

# 查看 KVM 模块是否加载
lsmod | grep kvm

# 检查虚拟化支持 (结果大于 0 即可)
egrep -c '(vmx|svm)' /proc/cpuinfo

# 安装 KVM 环境(以 Ubuntu 为例)
sudo apt update
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager -y

2. 安装相关组件

为了保证管理的便利性,通常安装 QEMU 核心组件、Libvirt 管理库以及常用的命令行工具。 CentOS / RHEL

# 安装核心包
yum install -y qemu-kvm libvirt virt-install virt-manager

# qemu-kvm: KVM 用户空间组件
# libvirt: 管理 API 库
# virt-install: 命令行安装虚拟机的工具
# virt-manager: 图形化管理界面(可选)

Ubuntu / Debian

# 更新并安装
apt update
apt install -y qemu-system-x86 libvirt-daemon-system libvirt-clients virtinst bridge-utils

3. 服务配置与权限管理

安装完成后,需要启动 Libvirt 守护进程,这是后续所有 virsh 命令的基础。

# 启动并设置为开机自启
systemctl enable --now libvirtd
systemctl status libvirtd

# 权限设置(非 Root 用户操作),如果希望以普通用户身份管理虚拟机,需要将该用户加入 `libvirt` 和 `kvm` 组:
usermod -aG libvirt $(whoami)
usermod -aG kvm $(whoami)
# 重新登录后生效

3. 验证安装

使用 virsh 工具查看当前环境,如果返回空列表而非报错,则说明安装成功:

virsh list --all

三、创建虚拟机

1. 前期准备

# 建立一个标准目录存放镜像
mkdir -p /data/kvm/iso # 存放ISO镜像文件
mkdir -p /data/kvm/images  # 存放虚拟机磁盘文件

2. virt-install 选项与网络

  • 使用 virt-install 创建虚拟机

2.1 选项说明

--name :

  • 指定虚拟机名称。

--memory :

  • 指定虚拟机内存大小,单位:MiB。
  • 通常需设置为1024的倍数,例如:4096 (4GB)、8192 (8GB)、16384 (16GB)、32768 (32GB) 等。

--vcpus :

  • 指定虚拟机 CPU 核心数。 默认值为 1。

--cpu :

  • 指定虚拟机 CPU 模型,例如 host-passthrough;默认值取决于版本和环境。

--os-variant :

  • 指定操作系统变体,例如 rhel7.64、rhel8.5、openeuler22.03 等。
  • 如果执行报错,可以通过命令 osinfo-query os 查看宿主机支持的列表,或者干脆用 generic。

--disk :

  • 指定虚拟机磁盘配置。
  • 格式:--disk path=/path/to/disk.img,size=50,format=qcow2,bus=virtio,cache=none
    • path:磁盘文件路径。
    • size:磁盘大小,单位:GB。
    • format:磁盘格式,qcow2 或 raw。
    • bus:磁盘总线,virtio 或 ide。
    • cache:缓存模式,none 或 writeback。

--network ...,virtualport_type=openvswitch:

  • 它告诉 libvirt:“不要用 Linux Bridge 逻辑,去调用 ovs-vsctl 把网卡插到 OVS 桥上”。

--cdrom :

  • 指定虚拟机 CD-ROM 镜像文件路径。

--graphics :

  • 指定虚拟机图形界面配置。
  • 格式:--graphics vnc,listen=0.0.0.0
    • vnc:指定使用 VNC 图形界面。
    • listen:指定 VNC 监听 IP 地址,应按实际访问和安全要求设置。

--boot :

  • 指定虚拟机启动顺序。
  • 格式:--boot hd,cdrom,menu=on
    • hd:指定从硬盘盘启动。
    • cdrom:指定从 CD-ROM 启动。
    • menu:指定显示启动菜单,默认 on。

--check :

  • 指定虚拟机检查选项。
  • 格式:--check all=off
    • all=off 会关闭所有检查,应优先排查错误,不建议作为默认配置。

--noautoconsole :

  • 创建后不自动连接虚拟机控制台。

--channel unix,target_type=virtio,name=org.qemu.guest_agent.0

  • 添加供 QEMU Guest Agent 使用的 virtio 通道。
  • 格式:--channel unix,target_type=virtio,name=org.qemu.guest_agent.0
    • target_type:此处 virtio 指客户机通道类型;unix 指宿主机侧的通道类型。
    • name:指定通道名称,org.qemu.guest_agent.0。

--description :

  • 虚拟机描述。

2.2 创建 ovs-br0 网桥

# 安装openvswitch
apt install -y openvswitch-switch # Ubuntu
yum install -y openvswitch # CentOS/Rocky

# 启动并设置为开机自启
systemctl enable --now openvswitch

# 创建网桥
ovs-vsctl add-br ovs-br0

# 将业务口网卡 ens37 加入网桥
# 注意:生产环境通常需要两块网卡,一块用于管理,一块用于业务流量,别加错,否则无法ssh连接了
# 这里假设 ens37 是业务流量口,将其加入 ovs-br0 网桥
ovs-vsctl add-port ovs-br0 ens37

# 执行 ovs-vsctl show 验证
    Bridge ovs-br0
        Port ens37
            Interface ens37
        Port ovs-br0
            Interface ovs-br0
                type: internal


# 设置网桥为混杂模式
# 在某些物理交换机环境下,可能需要手动开启物理网卡的混杂模式,否则虚拟机流量可能无法进出:
ip link set ovs-br0 promisc on

3. 通过官方 qcow2 镜像

以下以 openEuler 24.03 LTS SP3 和 OVS 网桥 br-mgmt 为例;镜像路径、网桥和网络配置按实际环境修改。

# 下载镜像
wget https://repo.openeuler.org/openEuler-24.03-LTS-SP3/virtual_machine_img/x86_64/openEuler-24.03-LTS-SP3-x86_64.qcow2.xz

# 为虚拟机准备独立镜像并解压
cp openEuler-24.03-LTS-SP3-x86_64.qcow2.xz cmdb.qcow2.xz
xz -d cmdb.qcow2.xz

# 虚拟机启动之前,扩展虚拟磁盘容量
qemu-img resize cmdb.qcow2 300G

# 在镜像所在目录创建虚拟机
sudo virt-install \
  --name cmdb \
  --vcpus 4 \
  --memory 8192 \
  --disk path="cmdb.qcow2",format=qcow2,bus=virtio \
  --network bridge=br-mgmt,virtualport_type=openvswitch,model=virtio \
  --os-variant generic \
  --import \
  --noautoconsole

# 查看状态和网卡,进入串口控制台(退出按 Ctrl + ])
virsh list --all
virsh domiflist cmdb
virsh console cmdb
  • --import:使用已有磁盘镜像启动虚拟机,跳过系统安装过程。
  • --noautoconsole:创建后不自动连接虚拟机控制台,可以使用 virsh console 手动连接。

各系统镜像默认账号和密码:

  • openEuler 24.03 LTS SP3 官方虚拟机镜像:账号 root,密码 openEuler12#$。

3.1 扩展根分区和文件系统

# 在虚拟机内确认 /dev/vda2 为 ext4,且分区后有连续空闲空间
lsblk
findmnt -no SOURCE,FSTYPE /
fdisk -l /dev/vda

# 扩展第 2 个分区(保留起始位置)
printf ', +\n' | sfdisk --no-reread -N 2 /dev/vda

# 如果提示内核仍在使用旧分区表,重启虚拟机
reboot

# 重启后确认 vda2 已变大;其他报错先排查
lsblk

# 扩展 ext4 文件系统
resize2fs /dev/vda2

# 查看根目录容量
df -h /

此示例使用 MBR 分区表;不同分区布局、LVM 或其他文件系统需使用对应方法。MBR 磁盘使用 512 字节扇区时通常最多寻址约 2 TiB。

4. 通过克隆

# 确定源虚拟机名称
virsh list --all

# 克隆前建议先停止(关机)源虚拟机,以确保磁盘数据的一致性。
virsh shutdown 源虚拟机名称
virsh domstate 源虚拟机名称  # 等待状态变为 shut off

# 执行克隆
virt-clone -o 源虚拟机名称 -n 新虚拟机名称 -f 新虚拟机磁盘文件的存储路径.qcow2

5. 通过 ISO 安装

5.1 openEuler

mkdir -p /data/kvm/images/nwxt-test

virt-install \
  --name NWXT-TEST-MD \
  --memory 32768 \
  --vcpus 8 \
  --cpu host-passthrough \
  --os-variant generic \
  --disk path=/data/kvm/images/nwxt-test/nwxt-test-md-disk1.qcow2,size=300,format=qcow2,bus=virtio,cache=none \
  --network bridge=br-mgmt,virtualport_type=openvswitch,model=virtio \
  --network bridge=br-int,virtualport_type=openvswitch,model=virtio \
  --cdrom /data/kvm/iso/openEuler-24.03-LTS-SP3-x86_64-dvd.iso \
  --graphics vnc,listen=0.0.0.0 \
  --boot cdrom,hd,menu=on \
  --noautoconsole \
  --channel unix,target_type=virtio,name=org.qemu.guest_agent.0 \
  --description "描述信息"

5.2 Red Hat 系

virt-install \
--name rocky-01 \
--vcpus 2 \
--memory 2048 \
--location /data/kvm/iso/Rocky-9.iso \
--disk path=/data/kvm/images/rocky-01.qcow2,size=20,format=qcow2,bus=virtio \
--network bridge=ovs-br0,virtualport_type=openvswitch,model=virtio \
--graphics none \
--extra-args 'console=ttyS0,115200n8 serial' \
--os-variant rockylinux9
  • --disk ...,bus=virtio: 核心优化点。使用 virtio 驱动,通常可以减少虚拟化 I/O 开销,实际性能取决于环境。
  • --network ...,model=virtio: 网卡同样使用 virtio 驱动,降低 CPU 中断损耗。此外还挂载到了 ovs-br0 网桥,实现了虚拟机与宿主机的网络通信。
  • --graphics none: 生产服务器通常不需要 VNC/显卡。
  • --extra-args ...: 配合 --graphics none 使用,将安装过程的输出重定向到当前终端(Console),这样直接在 SSH 窗口就能完成系统安装。
  • --os-variant: 告知 KVM 目标系统类型,它会自动优化 CPU 指令集和默认驱动配置。可用 osinfo-query os 查看支持列表。

5.3 Ubuntu 系

virt-install \
--name ubuntu-01 \
--vcpus 2 \
--memory 2048 \
--cdrom /data/kvm/iso/ubuntu-22.04.5-live-server-amd64.iso \
--disk path=/data/kvm/images/ubuntu-01.qcow2,size=20,format=qcow2,bus=virtio \
--network bridge=ovs-br0,virtualport_type=openvswitch,model=virtio \
--graphics vnc,listen=0.0.0.0 \
--os-variant ubuntu22.04

修改点详细说明:

1. 从 --location 改为 --cdrom

  • 原因:Ubuntu 22.04 的 Live ISO 结构与 RedHat 系不同,virt-install 无法直接从它的 ISO 中“提取”出内核来配合 extra-args 使用。
  • 后果:由于使用了 --cdrom,原有的 --extra-args(用于串口控制台输出)将无法生效。

2. 从 --graphics none 改为 --graphics vnc

  • 原因:因为 --cdrom 模式无法直接将安装界面推送到的 SSH 串口(Terminal),必须通过 VNC 才能看到 Ubuntu 的安装紫色界面。
  • 配置:listen=0.0.0.0 允许从本地电脑使用 VNC 客户端连接宿主机的 IP 来进行安装。

3. 修改 --os-variant 为 ubuntu22.04

  • 原因:这会告诉 KVM 使用适合 Ubuntu 22.04 的硬件抽象层配置。
  • 查询方式:可以通过 osinfo-query os | grep ubuntu 确认的系统支持的确切名称。

4. 磁盘路径与名称

  • 为了规范,我把名称和磁盘路径改为了 ubuntu-01,避免和之前的 Rocky 混合。

5.4 检查安装结果

  • 除了通过 VNC 查看安装过程,还可以通过下面的方式检查安装状态
# 查看虚拟机状态是否为 running
virsh list --all

# 查看网桥 ovs-br0 的状态,应该能看到一个以 vnet 开头的 Port 被自动加入到了 ovs-br0 中。
ovs-vsctl show

5.5 安装完成后的操作

# 设置开机自启(可选)
virsh autostart ubuntu-01

6. 安装后的优化

6.1 安装 qemu-guest-agent

没有它,宿主机只能像看黑盒子一样看虚拟机;有了它,宿主机就有了“上帝视角”:

  • 优雅关机/重启: 宿主机发出的关机指令会通过它通知系统正常关机,而不是直接拔电源,避免硬盘坏道。
  • 文件系统静默: 在备份时,它可以通知虚拟机“冻结”文件系统写入,帮助提高文件系统一致性;应用数据还需要应用自身配合。
  • 网络信息同步: 宿主机可以直接查看到虚拟机内部的 IP 地址(无需登录控制台)。
  • 重置密码: 宿主机可以直接修改虚拟机内的用户密码。
  • 精准统计: 获取更准确的 CPU、内存使用情况。
# 验证虚拟设备是否存在
ls /dev/virtio-ports/org.qemu.guest_agent.0

# 所有主流发行版的包名都叫这个,直接使用 apt/yum 安装即可。
yum install -y qemu-guest-agent

# 启动服务并设置开机自启
systemctl enable --now qemu-guest-agent
systemctl status qemu-guest-agent

# 从 kvm 宿主机测试,成功应返回 {"return":{}}
virsh qemu-agent-command <虚拟机名称> '{"execute":"guest-ping"}'

# 从 kvm 宿主机测试,成功应返回虚拟机的 IP 地址
virsh domifaddr <虚拟机名称> --source agent

PS:qemu-guest-agent 专门属于 KVM/QEMU 这一派系的。

四、常用操作

1. 快照管理

1.1 开机快照

# 确认虚拟机正在运行
virsh domstate 虚拟机名称

# 检查 QEMU Guest Agent 是否响应
virsh qemu-agent-command 虚拟机名称 '{"execute":"guest-ping"}'

# 拍摄在线磁盘快照(不包含内存;需要 Guest Agent 和存储支持)
virsh snapshot-create-as 虚拟机名称 快照名称 --disk-only --quiesce --atomic

1.2 关机快照

# 关机
virsh shutdown 虚拟机名称

# 等待状态为 shut off(关闭)
virsh domstate 虚拟机名称

# 拍摄快照
virsh snapshot-create-as 虚拟机名称 快照名称 --atomic

1.3 查看、恢复与删除

# 查看快照
virsh snapshot-list 虚拟机名称

# 恢复快照(会回退数据;先确认快照类型和恢复方案)
virsh snapshot-revert 虚拟机名称 快照名称

# 删除快照(外部快照需确认 libvirt 和存储支持)
virsh snapshot-delete 虚拟机名称 快照名称

2. 为虚拟机添加描述

# 创建虚拟机时添加,在使用 virt-install 创建虚拟机时,添加 --description 参数即可:
virt-install --name my-vm-01 --description "研发部-张三-用于Web接口测试" ...

# 创建后添加描述
virsh desc prod-harbor --new-desc "生产环境harbor" --live --config
# --config:修改下次启动时生效(持久化)
# --live:修改立即生效(仅限本次运行,重启丢失)
# 通常两个参数配合使用,如果机器已经关机,则只添加 --config 参数即可

3. 虚拟机改名

# 停止虚拟机(重命名必须在关机状态下进行):
virsh shutdown 老名称
virsh domstate 老名称  # 等待状态变为 shut off

# 执行重命名:
virsh domrename 老名称 新名称

4. 关闭虚拟机

  • 正常关机(建议首选):
virsh shutdown <虚拟机名称>
  • 强制断电(相当于直接断电,可能造成未保存数据丢失):
virsh destroy <虚拟机名称>

5. 删除虚拟机

# 强制关机(如果虚拟机已经关闭,则跳过)
virsh destroy 虚拟机名称

# 确认状态为 shut off(关闭)
virsh domstate 虚拟机名称

# 查看并记录磁盘和快照
virsh domblklist 虚拟机名称 --details
virsh snapshot-list 虚拟机名称

# 确认磁盘未共享且无镜像依赖后,删除定义、NVRAM 和关联存储
# 没有 NVRAM 时去掉 --nvram
virsh undefine 虚拟机名称 --nvram --remove-all-storage

# 查看命令输出,确认存储卷已删除,再检查虚拟机列表和磁盘文件
virsh list --all
ls -lh /磁盘路径/磁盘文件.qcow2

如果虚拟机定义已删除,但存储卷提示 still being allocated,先检查克隆进程和文件使用情况,等待分配任务结束,再通过 libvirt 删除剩余存储卷:

# 查询已记录的磁盘所属存储池
virsh vol-pool /磁盘路径/磁盘文件.qcow2

# 确认卷未共享、无镜像依赖且分配任务已结束后删除
virsh vol-delete 磁盘文件.qcow2 --pool 存储池名称

# 确认卷和文件已删除
virsh vol-list 存储池名称
ls -lh /磁盘路径/磁盘文件.qcow2

--remove-all-storage 不会自动删除镜像链中的 backing file;如有快照或保存状态,应先检查依赖及 libvirt 的提示。

五、网络管理

网络模式对比:

特性 网桥模式 (Bridge) NAT 模式 直接分配 (SR-IOV)
外部可见性 外部网络可直接访问 VM 外部不可见,需做端口转发 外部直接访问
性能 高 (接近物理网卡) 中 (受限于宿主机 CPU) 极高 (几乎无损)
灵活性 高 一般 低 (受物理硬件限制)
复杂度 低 极低 高 (需硬件支持)
典型用途 生产业务环境 个人实验、快速开发 高并发/低延迟业务

1. 桥接模式

在 KVM 的生产环境中,网桥模式(Bridge Mode) 是应用最广泛、最主流的选择。

桥接模式原理::

  • 首先通过 Linux 内核或 ovs 创建一个网桥(如 br0),这个网桥相当于一个“交换机”,所有连接到它的物理网卡和虚拟机网卡都在同一个二层网络中;
  • 物理网卡(如 eth0)被插到这个“交换机”的一个端口上;
  • 虚拟机启动时,KVM 会在宿主机创建一个 vnetX 虚拟网卡,并将其插到 br0 的另一个端口。

1.1 实现桥接模式

  • KVM 虚拟机网卡加入到网桥。
1.1.1 创建网桥
  • 下面以 ovs 网桥为例,创建一个名为 ovs-br0 的网桥。
# 安装 ovs 软件包
apt install openvswitch-switch -y

# 创建 ovs 网桥
ovs-vsctl add-br ovs-br0

# 查看 ovs 网桥
ovs-vsctl show
1.1.2 虚拟机网卡加入网桥

方法一:安装系统时指定网桥

virt-install  --network bridge=ovs-br0,virtualport_type=openvswitch,model=virtio   ...

方法二:通过 XML 配置

  • 编辑虚拟机 XML 配置文件,添加网络接口。
# 编辑虚拟机配置: 
virsh edit <虚拟机名称>

# 修改 <interface> 段落: 找到网络接口部分,按以下格式修改:
<interface type='bridge'>
  <source bridge='ovs-br0'/>
  <virtualport type='openvswitch'/>
  <model type='virtio'/>
</interface>

# 保存并启动,配置完成后,启动虚拟机。Libvirt 会自动执行类似于 ovs-vsctl add-port 的操作。

方法三:在 Libvirt 中定义 OVS 虚拟网络

  • 如果有很多 VM 都要连 OVS,可以先定义一个 Libvirt 网络。
# 创建一个网络 XML 文件 ovs-net.xml:
<network>
  <name>ovs-network</name>
  <forward mode='bridge'/>
  <bridge name='ovs-br0'/>
  <virtualport type='openvswitch'/>
</network>


# 激活并使用:
virsh net-define ovs-net.xml
virsh net-start ovs-network
virsh net-autostart ovs-network


# 之后在 VM 配置里只需指定 <source network='ovs-network'/> 即可。
1.1.3 验证
# 查看 ovs 网桥
# ovs-vsctl show
    Bridge ovs-br0
        Port vnet0
            Interface vnet0
        Port ens37
            Interface ens37
        Port ovs-br0
            Interface ovs-br0
                type: internal

# 宿主机查看 vnet0 网卡
# ip a
6: vnet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master ovs-system state UNKNOWN group default qlen 1000
    link/ether fe:54:00:ce:a4:42 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::fc54:ff:fece:a442/64 scope link 
       valid_lft forever preferred_lft forever

# 查看虚拟机网卡是否加入到 ovs 网桥
# virsh domiflist ubuntu-01
 Interface   Type     Source    Model    MAC
------------------------------------------------------------
 vnet0       bridge   ovs-br0   virtio   52:54:00:ce:a4:42

# 虚拟机内部查看 enp1s0 网卡,MAC 地址与宿主机 vnet0 网卡一致
# ip a
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:ce:a4:42 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.128/24 metric 100 brd 10.0.0.255 scope global dynamic enp1s0
       valid_lft 1145sec preferred_lft 1145sec
    inet6 fe80::5054:ff:fece:a442/64 scope link 
       valid_lft forever preferred_lft forever


# 检查流表,确认是否有流量统计,如果有 n_packets 在增加,说明流量已经过桥。
ovs-ofctl dump-flows ovs-br0

在虚拟化层,vnet0 是宿主机侧的一个 Tap 设备,它就像是一根虚拟网线的“一头”;而虚拟机内部的 enp1s0 是这根线的“另一头”。它们共享相同的二层身份信息。如果 MAC 地址完全一致,那么 vnet0 就是 enp1s0 在宿主机侧的呈现。

  • Tap 设备简单来说,就是一种虚拟网卡,它是连接“虚拟机内部”与“宿主机外部”的二层桥梁。

1.2 桥接模式下虚拟机间通信

  • 只要虚拟机之前接入同一个网桥,并配置了正确的 IP 地址,就可以直接通信。

2. NAT 模式

这是 KVM 安装后的默认设置(即 virbr0 网桥)。

  • 原理:宿主机充当虚拟机的路由器。宿主机会通过 dnsmasq 为虚拟机分配私有 IP,并通过内核的 IP Forwarding 和 IPTables 实现 SNAT(源地址转换)。
  • 优点:开箱即用,虚拟机可以访问外部网络。
  • 缺点:外部网络(包括局域网内其他物理机)无法直接访问虚拟机;宿主机重启或网络服务变动可能导致规则丢失。
  • 适用场景:个人开发测试、临时实验环境。

如果是实验/测试环境,可以使用 virbr0 的 NAT 模式。

如果是生产环境,建议直接禁用 virbr0,改用 OVS 桥接模式,可以执行以下操作:

# 查看所有网络
virsh net-list --all

# 停止默认网络
virsh net-destroy default

# 取消开机自启
virsh net-autostart --disable default

# (可选) 如果确定以后不用了,可以直接删除定义
virsh net-undefine default

六、虚拟机管理脚本

6.1 统计虚拟机

collect_vm.py
import subprocess
import pandas as pd
import datetime
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill, Alignment

# --- 配置区:对应你 ~/.ssh/config 里的别名 ---
HOST_ALIASES = ["kvm1", "kvm2", "kvm3"]

# 强化版 Shell 命令:增加了 virsh desc 读取逻辑,并修正了输出格式
KVM_CMD = """
virsh list --all --name | while read vm; do
    [ -z "$vm" ] && continue
    
    # 清理函数:去除换行和首尾空格
    clean() { echo "$1" | tr -d '\\n\\r' | xargs; }

    status=$(clean "$(virsh domstate "$vm" 2>/dev/null)")
    
    create_time=$(clean "$(stat -c %y /etc/libvirt/qemu/"$vm".xml 2>/dev/null | cut -d'.' -f1)")
    [ -z "$create_time" ] && create_time="Unknown"
    
    info=$(virsh dominfo "$vm" 2>/dev/null)
    vcpu=$(clean "$(echo "$info" | grep "CPU(s):" | awk '{print $2}')")
    mem=$(clean "$(echo "$info" | grep "Used memory:" | awk '{print $3/1024/1024 "G"}')")
    
    disks=$(clean "$(virsh domblklist "$vm" --details 2>/dev/null | grep 'disk' | awk '{print $4}' | paste -sd ";")")
    [ -z "$disks" ] && disks="No_Disk_Found"

    # --- 获取虚拟机备注 (使用 --config 确保即便没重启也能读到磁盘配置) ---
    note=$(clean "$(virsh desc "$vm" --config 2>/dev/null)")
    [ -z "$note" ] || [ "$note" = "没有域描述" ] && note="N/A"

    # --- IP 获取逻辑 ---
    get_ips_from_source() {
        virsh domifaddr "$vm" --source $1 2>/dev/null | grep ipv4 | awk '{print $4}' | cut -d/ -f1 | xargs | sed 's/ /,/g'
    }

    ips=$(get_ips_from_source "agent")
    if [ -z "$ips" ]; then
        ips=$(get_ips_from_source "lease")
    fi
    
    if [ -z "$ips" ]; then
        macs=$(virsh domiflist "$vm" 2>/dev/null | grep -E "vnet|virtio" | awk '{print $5}')
        temp_ips=""
        for m in $macs; do
            found_ip=$(arp -an | grep "$m" | awk '{print $2}' | tr -d '()')
            if [ ! -z "$found_ip" ]; then
                if [ -z "$temp_ips" ]; then temp_ips="$found_ip"; else temp_ips="$temp_ips,$found_ip"; fi
            fi
        done
        ips="$temp_ips"
    fi
    
    ips=$(echo "$ips" | tr -d '\\n\\r')
    [ -z "$ips" ] && ips="Unknown"
    
    # 修正点:最后确保输出的是 ${note},而不是空的 ${}
    echo "${vm}|${status}|${create_time}|${vcpu}|${mem}|${ips}|${disks}|${note}"
done
"""

def fetch_kvm_data():
    all_data = []
    for host in HOST_ALIASES:
        print(f"🔍 正在连接宿主机: {host} ...")
        ssh_cmd = ["ssh", "-o", "ConnectTimeout=5", host, KVM_CMD]
        try:
            process = subprocess.run(ssh_cmd, capture_output=True, text=True, check=True)
            output = process.stdout.strip()
            
            if not output: continue
            
            for line in output.split('\n'):
                line = line.strip()
                if not line or '|' not in line: continue
                
                parts = line.split('|')
                # 填充字段,确保有 8 个元素
                while len(parts) < 8:
                    parts.append("Unknown")
                
                all_data.append({
                    "宿主机": host,
                    "虚拟机名称": parts[0],
                    "运行状态": parts[1],
                    "创建时间": parts[2],
                    "配置(vCPU)": parts[3],
                    "配置(内存)": parts[4],
                    "IP": parts[5],
                    "磁盘路径": parts[6],
                    "备注": parts[7]
                })
        except Exception as e:
            print(f"❌ {host} 执行失败: {e}")
            
    return all_data

def save_and_style_excel(data, filename):
    df = pd.DataFrame(data)
    # 重新排序列,确保备注在最后一列
    cols = ["宿主机", "虚拟机名称", "运行状态", "创建时间", "配置(vCPU)", "配置(内存)", "IP", "磁盘路径",  "备注"]
    df = df[cols]
    df = df.sort_values(by=["宿主机", "虚拟机名称"])
    df.to_excel(filename, index=False, engine='openpyxl')
    
    wb = load_workbook(filename)
    ws = wb.active
    
    # 样式定义
    header_fill = PatternFill(start_color="44546A", end_color="44546A", fill_type="solid")
    header_font = Font(color="FFFFFF", bold=True, size=11)
    center_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)

    # 美化表头
    for cell in ws[1]:
        cell.fill = header_fill
        cell.font = header_font
        cell.alignment = center_alignment

    # 自动调整列宽
    for column in ws.columns:
        max_length = 0
        column_letter = column[0].column_letter
        for cell in column:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except: pass
        
        # 特别处理“备注”列 (K列)
        if column_letter == 'K':
            ws.column_dimensions[column_letter].width = 45
        else:
            adjusted_width = (max_length + 4)
            ws.column_dimensions[column_letter].width = adjusted_width if adjusted_width < 60 else 60

    wb.save(filename)
    print(f"✨ 最终结果已保存: {filename}")

if __name__ == "__main__":
    results = fetch_kvm_data()
    if results:
        now_str = datetime.datetime.now().strftime('%Y%m%d_%H%M')
        fname = f"KVM_INFO_{now_str}.xlsx"
        save_and_style_excel(results, fname)
    else:
        print("📭 未采集到有效数据。")

FAQ

宿主机内存够,但无法启动虚拟机

现象:启动虚拟机时报 KVM_CREATE_VM failed: Cannot allocate memory。free -h 显示 available 约 156G、buff/cache 约 159G,Swap 已用满。

本次处理:在宿主机依次执行以下命令后,虚拟机启动成功:

# 将待写入的数据同步到磁盘
sync

# 释放干净的文件缓存和可回收的目录项、inode 缓存
echo 3 > /proc/sys/vm/drop_caches

# 尝试整理内存碎片
echo 1 > /proc/sys/vm/compact_memory

# 重新启动虚拟机
virsh start 虚拟机名称

openEuler KVM 部署与磁盘扩容实战

单网卡桥接、云镜像导入、大容量磁盘扩容与常见故障处理

pve

虚拟机存储管理

虚拟机基础管理

虚拟机网络管理