要求:
-
运行 Linux 发行版的计算机。本指南使用 CentOS 9-stream,但对于其他 Linux 发行版,特别是对于 Red Hat Enterprise Linux 7,命令不应有重大变化。
-
具有 sudo 权限的用户
-
~ 主目录中有 25 GB 的可用空间
-
至少 8GB 内存
首先,我们安装我们需要的软件包:
sudo yum install qemu-kvm libvirt-daemon-qemu libvirt-daemon-kvm libvirt virt-install libguestfs-tools-c kernel-tools dpdk dpdk-tools
创建 VM
首先,从以下网站下载最新的 CentOS-Cloud-Base 映像:
user@host $ sudo wget -O /var/lib/libvirt/images/CentOS-Stream-GenericCloud-9-20240212.0.x86_64.qcow2 https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-20240212.0.x86_64.qcow2
(请注意,上面的 URL 可能会更改,请将其更新为 https://cloud.centos.org/centos/9-stream/x86_64/images中的最新 qcow2 映像。
制作image的副本,以便将来可以重用它(用这种方法,我们可以启动很多个虚拟机,但镜像只占用很小的磁盘空间):
user@host $ sudo qemu-img create -f qcow2 -b /var/lib/libvirt/images/CentOS-Stream-GenericCloud-9-20240212.0.x86_64.qcow2 -F qcow2 /var/lib/libvirt/images//vhuser-test1.qcow2
如果我们导出以下变量,则可以使用非特权用户(推荐)执行此操作的 libvirt 命令:
user@host $ export LIBVIRT_DEFAULT_URI="qemu:///system"
现在,清理命令(将密码更改为您自己的密码,卸载和安装一些工具):
user@host $ sudo virt-sysprep --root-password password:changeme --uninstall cloud-init --selinux-relabel -a /var/lib/libvirt/images/vhuser-test1.qcow2 --network --install "dpdk,dpdk-tools,pciutils"
如果出现报错:
Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist
创建shell脚本
$ cat scripts
#!/bin/bash
cd /etc/yum.repos.d/
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
,并使用如下命令:
user@host $ sudo virt-sysprep --root-password password:changeme --run ./scripts --uninstall cloud-init --selinux-relabel -a /var/lib/libvirt/images/vhuser-test1.qcow2 --network --install "dpdk,dpdk-tools,pciutils"
此命令挂载文件系统并自动应用一些基本配置,以便映像可以重新引导。
配置VM网络
我们还需要一个网络来连接我们的 VM。Libvirt 处理网络的方式与管理虚拟机的方式类似,您可以使用 XML 文件定义网络,然后通过命令行启动或停止它。
在这个例子中,我们将使用一个名为default
的网络,为了方便起见,它的定义包含在 libvirt 中。以下命令定义default
网络,启动它并检查它是否正在运行。
user@host $ virsh net-define /usr/share/libvirt/networks/default.xml
Network default defined from /usr/share/libvirt/networks/default.xml
user@host $ virsh net-start default
Network default started
user@host $virsh net-list
Name State Autostart Persistent
--------------------------------------------
default active no yes
/usr/share/libvirt/networks/default.xml
内容如下:
$ cat /usr/share/libvirt/networks/default.xml
1 <network>
2 <name>default</name>
3 <bridge name="virbr0"/>
4 <forward/>
5 <ip address="192.168.122.1" netmask="255.255.255.0">
6 <dhcp>
7 <range start="192.168.122.2" end="192.168.122.254"/>
8 </dhcp>
9 </ip>
10 </network>
该文件定义了一个内核网桥virbr0
,网桥的ip、掩码和作为dhcp server时分配的ip范围。我们也可以自定义其他的网络。
最后,我们可以使用 virt-install 来创建 VM。此命令行实用程序为一组已知操作系统创建所需的定义。这将为我们提供基本定义,然后我们可以自定义这些定义:
user@host $ virt-install --import --name vdpa-test1 --ram=5120 --vcpus=3 \
--nographics --accelerate --machine q35 \
--network network:default,model=virtio --mac 02:ca:fe:fa:ce:aa \
--debug --wait 0 --console pty \
--disk /var/lib/libvirt/images/vdpa-test1.qcow2,bus=virtio --os-variant centos-stream9
此命令的选项用于指定 vCPU 的数量、VM 的 RAM 量以及我们希望 VM 连接到的磁盘路径和网络。
osinfo-query os | grep centos
可以查询os版本。
您可能已经注意到,与 Vhost 用户动手操作相比,有一个区别,因为我们将机器类型指定为“q35”而不是默认的“pc”类型。这是启用对虚拟 IOMMU 的支持的要求。
除了根据我们指定的选项定义虚拟机外,virt-install 命令还应该为我们启动虚拟机,因此我们应该能够列出它:
user@host $ virsh list
Id Name State
------------------------------
1 vdpa-test1 running
瞧!我们的 VM 正在运行。我们需要尽快对其定义进行一些更改。所以我们现在将关闭它:
user@host $ virsh shutdown vdpa-test1
Host环境准备
DPDK 有助于以最佳方式分配和管理内存缓冲区。在 Linux 上,这需要使用 hugepage 支持,该支持必须在正在运行的内核中启用。使用大于通常 4K 大小的页面可以减少页面总数,从而减少 TLB(Translation Lookaside Buffers)查找,从而提高性能。需要这些查找才能将虚拟地址转换为物理地址。为了在引导过程中分配 hugepages,我们在引导加载程序配置中的内核参数中添加了以下内容。
user@host $ sudo grubby --args="default_hugepagesz=1G hugepagesz=1G hugepages=6" --update-kernel /boot/<your kernel image file>
这有什么作用?
default_hugepagesz=1G: make all created hugepages by default 1G big
hugepagesz=1G: for the hugepages created during startup set the size to 1G as well
hugepages=6: create 6 hugepages (of size 1G) from the start. These should be seen after booting in /proc/meminfo
现在可以重新引导主机。在它出现后,我们可以通过运行以下命令来检查我们对内核参数的更改是否有效:
user@host $ cat /proc/cmdline
Guest环境准备
virt-install 命令使用 libvirt 创建并启动了 VM。要将基于 DPDK 的 vswitch testpmd 连接到 QEMU,我们需要添加 vhost-user 接口
的定义(由 UNIX 套接字支持),并将虚拟 IOMMU
设置到 XML 的 device 部分:
user@host $ virsh edit vdpa-test1
让我们从添加所有必要的设备开始,以使 Virtio-net 设备与 IOMMU 支持一起工作。
首先,我们需要创建一个专用于 Virtio-net 设备的 pcie-root-port
,我们将用作 vDPA 设备,以便它有自己的 IOMMU group
。否则,它将与同一总线上的其他设备共享,从而阻止它在用户空间中使用:
<controller type='pci' index='8' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='8' port='0xe'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</controller>
解惑IOMMU
然后,我们可以使用 vhost-user 后端添加 Virtio-net 设备,并将其插入我们刚刚创建的 pcie-root-port
(总线 8)。我们还需要启用 IOMMU 和 ATS(地址转换服务)支持:
<iommu model='intel'>
<driver intremap='on' iotlb='on'/>
</iommu>
要使启用了 IOMMU 的 vhost-user 正常工作,还需要进行一些其他 XML 调整。
首先,就像在 vhost-user 动手操作 中一样,我们需要让 guest 使用huge pages,这是通过在 guest 定义中添加以下内容来完成的:
<memoryBacking>
<hugepages>
<page size='1048576' unit='KiB' nodeset='0'/>
</hugepages>
<locked/>
</memoryBacking>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
字段含义参考libvirt xml memoryBacking。
为了让 vhost-user 后端能够访问内存,我们需要在 guest 配置中添加一个额外的设置。这是一个重要的设置,如果没有它,我们将看不到任何正在传输的数据包:
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' cores='3' threads='1'/>
<numa>
<cell id='0' cpus='0-2' memory=’5242880’ unit='KiB' memAccess='shared'/>
</numa>
</cpu>
字段含义参考libvirt xml host-passthrough
最后,我们需要让 Qemu 管理 I/O APIC,以便中断重新映射工作:
<features>
<acpi/>
<apic/>
<ioapic driver='qemu'/>
</features>
字段含义参考libvirt xml ioapic driver=‘qemu’
现在我们需要启动我们的guest。由于我们将其配置为连接到 vhost-user UNIX 套接字,因此我们需要确保它们在guest启动时可用。这是通过启动 testpmd 来实现的,它将为我们打开套接字:
user@host $ sudo testpmd -l 0,2 --socket-mem=1024 -n 4 \
--vdev 'net_vhost0,iface=/tmp/vhost-user1,iommu-support=1' -- \
--portmask=1 -i --rxq=1 --txq=1 \
--nb-cores=1 --forward-mode=io
最后一件事,因为我们连接到 vhost-user unix 套接字,我们需要让 QEMU 以 root 身份运行这个实验。为此,请在 /etc/libvirt/qemu.conf
中设置 “user = root”
。这是我们的特殊用例所必需的,但一般不建议这样做。事实上,读者应该在阅读这篇动手文章后,通过注释掉“user = root”设置来恢复设置。
现在,我们可以使用以下命令启动 VM:virsh start
user@host $ virsh start vdpa-test1
以 root 身份登录。我们在 guest 中做的第一件事是向内核命令行添加一些参数,以在启动时保留一些巨大的页面,并在直通模式
下启用对 IOMMU 的内核支持:
root@guest $ sudo grubby --args="default_hugepagesz=1G hugepagesz=1G hugepages=2 intel_iommu=on iommu=pt" --update-kernel /boot/<your kernel image file>
为了应用新的内核参数,让我们重新启动客户机。
以上也可以通过host实现:
sudo virt-sysprep -v --run-command 'grubby --args="default_hugepagesz=1G hugepagesz=1G hugepages=6" --update-kernel=ALL' -a /var/lib/libvirt/images/vhuser-test1.qcow2
在启动时,我们现在可以将 virtio 设备绑定到 vfio-pci 驱动程序。为了能够做到这一点,我们需要首先加载所需的内核模块。
root@guest $ modprobe vfio-pci
让我们先找出 virtio-net 设备的 PCI 地址。
root@guest $ dpdk-devbind --status net
…
Network devices using kernel driver
===================================
0000:01:00.0 'Virtio network device 1041' if=eth0 drv=virtio-pci unused=virtio_pci,vfio-pci *Active*
0000:08:00.0 'Virtio network device 1041' if=eth1 drv=virtio-pci unused=virtio_pci,vfio-pci
在输出中,在未标记为active
的部分中查找 virtio-device。我们可以用dpdk-devbind来做这个实验:
root@guests $ dpdk-devbind.py -b vfio-pci 0000:08:00.0
现在,客户机已准备好运行我们基于 DPDK 的 vDPA 应用程序。但在试验 vDPA 之前,我们首先要确保在启用 IOMMU 时,我们可以将 Virtio 设备与其 Virtio PMD 驱动程序结合使用 testpmd:
root@guests $ testpmd -l 0,1 --socket-mem 1024 -n 4 -- --portmask=1 -i --auto-start
然后,我们可以在主机 testpmd 实例中启动数据包转发,并在 IO 转发循环中注入突发数据包:
HOST testpmd> start tx_first 512
为了检查一切是否正常工作,我们可以通过重复调用 show port stats all
命令来检查任一 testpmd 实例中的端口统计信息:
HOST testpmd> show port stats all
######################## NIC statistics for port 0 ########################
RX-packets: 60808544 RX-missed: 0 RX-bytes: 3891746816
RX-errors: 0
RX-nombuf: 0
TX-packets: 60824928 TX-errors: 0 TX-bytes: 3892795392
Throughput (since last show)
Rx-pps: 12027830
Tx-pps: 12027830
############################################################################
创建加速数据路径
构建 vDPA 应用程序
在确保Host和Guest都正确配置后,让乐趣开始吧!
正如在上一篇文章中提到的,Virtio-vDPA DPDK 驱动程序,它支持使用半虚拟化的 Virtio-net 设备或完整的 Virtio 卸载硬件 NIC,正在上游 DPDK 邮件列表中进行审查,因此在官方上游版本中尚不可用(计划在 v19.11 版本中提供)。
这意味着我们必须应用引入此驱动程序的系列并构建 DPDK,而不是使用 DPKD 下游包。
首先,让我们安装所有依赖项来构建 DPDK:
root@guests $ yum install git gcc numactl-devel kernel-devel
然后我们构建 DPDK 库:
root@guests $ git clone https://gitlab.com/mcoquelin/dpdk-next-virtio.git dpdk
root@guests $ cd dpdk
root@guests $ export RTE_SDK=`pwd`
root@guests $ export RTE_TARGET=x86_64-native-linuxapp-gcc
root@guests $ make -j2 install T=$RTE_TARGET DESTDIR=install
最后,我们构建了 DPDK 树中提供的 vDPA 示例应用程序。此应用程序将探测 vDPA 驱动程序,并使用 vDPA 框架将 vDPA 设备绑定到 vhost-user 套接字:
root@guests $ cd examples/vdpa
root@guests $ make
启动 vDPA 应用程序
如果还没有完成,我们需要将 Virtio-net 设备绑定到 vfio-pci 驱动程序:
root@guests $ dpdk-devbind.py -b vfio-pci 0000:08:00.0
然后,我们可以启动 vdpa 应用程序,请求它将可用的 vDPA 设备绑定到以 . /tmp/vdpa
为前缀的 Vhost 用户套接字
为了使用 Virtio-vDPA 驱动程序而不是 Virtio PMD 探测 Virtio-net 设备,我们必须在 EAL 命令行中将“vdpa=1”
设备参数传递给列入白名单的设备。
由于在此示例中只有一个 vDPA 设备,因此应用程序将在以下位置创建单个 vhost-user 套接字/tmp/vdpa0
:
root@guests $ ./build/examples/dpdk-vdpa -l 0,2 --socket-mem 1024 -w 0000:08:00.0,vdpa=1 -- --iface /tmp/vdpa
EAL: Detected 3 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: WARNING: cpu flags constant_tsc=yes nonstop_tsc=no -> using unreliable clock cycles !
EAL: PCI device 0000:08:00.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 1af4:1041 net_virtio
EAL: PCI device 0000:08:00.0 on NUMA socket 0
EAL: probe driver: 1af4:1041 net_virtio_vdpa
EAL: using IOMMU type 1 (Type 1)
iface /tmp/vdpa
VHOST_CONFIG: vhost-user server: socket created, fd: 27
VHOST_CONFIG: bind to /tmp/vdpa0
enter 'q' to quit
Note:因为qemu开启了iommu,所以IOVA mode自动选择了"VA"。
启动用户应用程序
现在,绑定到 vDPA 设备的 vhost-user 套接字已准备好使用,我们可以启动将使用accelerated Virtio datapath的应用程序。
在现实生活中,它可以传递给虚拟机,以便其 Virtio 数据路径绕过主机,但我们已经在客户机中运行,因此这意味着设置嵌套虚拟化。
其他可能的情况是将 vhost-user 套接字传递给容器,以便它可以从高速接口中受益,但我们稍后将在以后的文章中介绍这一点。
为了简单起见,我们将只启动一个 testpmd 应用程序,它将使用 Virtio-user PMD 连接到 vhost-user 套接字。Virtio-user PMD 重用了 Virtio PMD 中的数据路径,但它不是通过 PCI 管理控制路径,而是实现了 vhost-user 协议的主端。
由于我们已经运行了一个 DPDK 应用程序,因此我们必须注意为它将分配的 hugepages 的文件名添加前缀。这要归功于以下命令:
root@guests $ ./install/bin/testpmd -l 0,2 --socket-mem 1024 --file-prefix=virtio-user --no-pci --vdev=net_virtio_user0,path=/tmp/vdpa0
就是这样,我们的加速数据路径已经设置好了!为了确认一切正常,让我们再次尝试注入一些数据包,看看它是否工作正常:
HOST testpmd> start tx_first 512
HOST testpmd> show port stats all
######################## NIC statistics for port 0 ########################
RX-packets: 3565442688 RX-missed: 0 RX-bytes: 228188332032
RX-errors: 0
RX-nombuf: 0
TX-packets: 3565459040 TX-errors: 0 TX-bytes: 228189378560
Throughput (since last show)
Rx-pps: 11997065
Tx-pps: 11997065
############################################################################
上面描述的是在虚拟环境里,启动了dpdk-vdpa[vhost-user server]和testpmd[vhost-user client]实现通信。使用virtio-user PMD driver,而不需要使用qemu作为[vhost-user client]进行测试。以上测试都不包含热迁移内容。
结论
在这篇文章中,我们使用 DPDK 的 vDPA 框架创建了一个标准的加速数据路径。在直接使用 Virtio PMD 运行初始 testpmd 和设置 vDPA 后运行之间,我们引入了 vDPA 层,以在不影响性能的情况下提供额外的灵活性。
此框架的价值在于,您现在可以试验 vDPA 控制平面,而无需供应商 NIC 支持业界目前正在开发的 vDPA 数据平面(硬件中的环布局支持)。
现在,我们将从 VM 领域转移到容器领域。接下来的实践文章将基于此框架进行构建,以在本地和混合云部署中试验 vDPA 加速容器。
附录
[Centos 8 qcow2镜像下载网址] https://cloud.centos.org/centos/8/x86_64/images/
[Centos 9 stream qcow2镜像下载网址] https://cloud.centos.org/centos/9-stream/x86_64/images/
[virt-sysprep用法]虚拟化技术之kvm镜像模板制作工具virt-sysprep.
vdpa动手博客