在这篇文章中,我们将建立一个环境并在虚拟机中运行一个基于DPDK的应用程序。我们将详细介绍设置在主机系统中连接到虚拟机中的应用程序所需的所有步骤,包括如何创建、安装和运行虚拟机以及在其中安装应用程序的说明。您将学习如何创建一个简单的设置,在这个设置中,您可以通过虚拟机中的应用程序将数据包发送到主机系统中的虚拟交换机,然后再返回。基于这个设置,您将学习如何调整设置以实现最佳性能。
设置
对于有兴趣尝试DPDK但不想配置和安装所需设置的读者,我们在一个Github存储库中有Ansible playbook,可以用来自动化一切。让我们从基本设置开始。
要求:
1、运行Linux发行版的计算机。本指南使用CentOS 7,但对于其他Linux发行版,特别是Red Hat Enterprise Linux 7,命令应该不会有太大变化。
2、具有sudo权限的用户
3、家目录中至少有25GB的可用空间
4、至少8GB的RAM
首先,我们安装需要的软件包:
sudo yum install qemu-kvm libvirt-daemon-qemu libvirt-daemon-kvm libvirt virt-install libguestfs-tools-c kernel-tools dpdk dpdk-tools
Creating a VM
user@host $ sudo wget -O /var/lib/libvirt/images/CentOS-7-x86_64-GenericCloud.qcow2 http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2
首先,从以下网站下载最新的CentOS-Cloud-Base镜像:
(请注意上面的URL可能会更改,请更新为https://wiki.centos.org/Download上的最新qcow2镜像。)
这将下载一个预先安装好的CentOS 7版本,准备在OpenStack环境中运行。由于我们没有运行OpenStack,因此我们必须清理该镜像。为此,首先我们将复制该镜像,以便将来可以重用它:
user@host $ sudo qemu-img create -f qcow2 -b /var/lib/libvirt/ima`g`es/CentOS-7-x86_64-GenericCloud.qcow2 /var/lib/libvirt/images//vhuser-test1.qcow2 20G
要执行这些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”
这个命令会挂载文件系统并自动应用一些基本的配置,以便使镜像准备好重新启动。
我们需要一个网络来连接我们的虚拟机。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
最后,我们可以使用virt-install创建虚拟机。这个命令行实用程序创建了一组知名操作系统所需的定义。这将为我们提供基本的定义,然后我们可以进行自定义:
user@host $ virt-install --import --name vhuser-test1 --ram=4096 --vcpus=3 \
--nographics --accelerate \
--network network:default,model=virtio --mac 02:ca:fe:fa:ce:aa \
--debug --wait 0 --console pty \
--disk /var/lib/libvirt/images/vhuser-test1.qcow2,bus=virtio --os-variant centos7.0
除了根据我们指定的选项定义虚拟机之外,virt-install命令还应该已经为我们启动了虚拟机,所以我们应该能够列出它:
user@host $ virsh list
Id Name State
------------------------------
1 vhuser-test1 running
好了!我们的虚拟机正在运行。我们需要很快对其进行一些更改。因此,现在我们将关闭它:
user@host $ virsh shutdown vhuser-test1
Preparing the host
DPDK通过优化分配和管理内存缓冲区来提高性能。在Linux上,这需要启用运行中的内核中的大页面支持。使用大于通常的4K的页面可以通过使用更少的页面和更少的TLB(Translation Lookaside Buffers)查找来提高性能。这些查找用于将虚拟地址转换为物理地址。要在启动期间分配大页面,我们需要将以下内容添加到引导加载程序配置中的内核参数中。
user@host $ sudo grubby --args=“default_hugepagesz=1G hugepagesz=1G hugepages=6 iommu=pt intel_iommu=on” --update-kernel /boot/<your kernel image file>
让我们了解每个参数的作用:
default_hugepagesz=1G:默认情况下,使所有创建的大页面的大小为1G。
hugepagesz=1G:为启动期间创建的大页面设置大小为1G。
hugepages=6:从一开始创建6个大页面(大小为1G)。在引导后,应该在/proc/meminfo中看到它们。
请注意,除了大页面设置之外,我们还添加了两个与IOMMU相关的内核参数,iommu=pt和intel_iommu=on。这将初始化Intel VT-d和我们将需要用于在Linux用户空间处理IO的IOMMU透传模式。由于我们更改了内核参数,现在是重新启动主机的好时机。
在主机重新启动后,我们可以通过运行user@host $ cat /proc/cmdline来检查我们对内核参数的更改是否有效。
Prepare the guest
virt-install命令创建并启动了一个使用libvirt的虚拟机。要将基于DPDK的vswitch testpmd连接到QEMU,我们需要将vhost-user接口的定义(由UNIX套接字支持)添加到XML的设备部分中:
user@host $ virsh edit vhuser-test1
将以下内容添加到 部分中:
<interface type='vhostuser'>
<mac address='56:48:4f:53:54:01'/>
<source type='unix' path='/tmp/vhost-user1' mode='client'/>
<model type='virtio'/>
<driver name='vhost' rx_queue_size='256' />
</interface>
<interface type='vhostuser'>
<mac address='56:48:4f:53:54:02'/>
<source type='unix' path='/tmp/vhost-user2' mode='client'/>
<model type='virtio'/>
<driver name='vhost' rx_queue_size='256' />
</interface>
与用于 vhost-net 的客户端配置相比,客户端配置的另一个区别是使用了大页。为此,我们将以下内容添加到客户端定义中:
<memoryBacking>
<hugepages>
<page size='1048576' unit='KiB' nodeset='0'/>
</hugepages>
<locked/>
</memoryBacking>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
为了能够访问内存,我们需要在客户机配置中添加一个额外的设置。这是一个重要的设置,如果没有它,我们将看不到任何数据包被传输:
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' cores='3' threads='1'/>
<numa>
<cell id='0' cpus='0-2' memory='3145728' unit='KiB' memAccess='shared'/>
</numa>
</cpu>
现在我们需要启动客户机。因为我们配置它连接到了vhost-user UNIX套接字,所以在客户机启动时,我们需要确保这些套接字可用。这可以通过启动testpmd来实现,它将为我们打开套接字:
user@host $ sudo testpmd -l 0,2,3,4,5 --socket-mem=1024 -n 4 \
--vdev 'net_vhost0,iface=/tmp/vhost-user1' \
--vdev 'net_vhost1,iface=/tmp/vhost-user2' -- \
--portmask=f -i --rxq=1 --txq=1 \
--nb-cores=4 --forward-mode=io
最后一件事,因为我们连接到vhost-user UNIX套接字,所以我们需要让QEMU以root身份运行此实验。为此,请在/etc/libvirt/qemu.conf中设置user = root。这对于我们的特殊用例是必需的,但通常不建议这样做。实际上,读者在按照本实践文章操作后应该取消user = root设置,将其注释掉。
现在我们可以使用user@host $ virsh start vhuser-test1启动虚拟机。
以root用户身份登录。在虚拟机中,我们首先要做的事情是将virtio设备绑定到vfio-pci驱动程序。为了能够做到这一点,我们需要首先加载所需的内核模块。
root@guest $ modprobe vfio enable_unsafe_noiommu_mode=1
root@guest $ cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
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=enp1s0 drv=virtio-pci unused= *Active*
0000:0a:00.0 'Virtio network device 1041' if=enp1s1 drv=virtio-pci unused=
0000:0b:00.0 'Virtio network device 1041' if=enp1s2 drv=virtio-pci unused=
在dpdk-devbind的输出中,查找未标记为活动的部分中的virtio设备。我们可以使用这些设备进行实验。请注意:读者系统上的地址可能不同。当我们首次启动这些设备时,它们将自动绑定到virtio-pci驱动程序。因为我们要使用vfio-pci内核模块而不是内核驱动程序,所以我们首先将它们从virtio-pci解绑,然后绑定到vfio-pci驱动程序。
root@guests $ dpdk-devbind.py -b vfio-pci 0000:0a:00.0 0000:0b:00.0
现在,客户机已准备好运行基于DPDK的应用程序。要使此绑定永久生效,我们还可以使用driverctl实用程序:
root@guest $ driverctl -v set-override 0000:00:10.0 vfio-pci
对于第二个地址为0000:00:11.0的virtio设备执行相同的操作。然后,列出所有覆盖项以检查是否生效:
user@guest $ sudo driverctl list-overrides
0000:00:10.0 vfio-pci
0000:00:11.0 vfio-pci
Generating traffic
我们已经安装和配置了所有内容,现在可以开始在接口上运行网络流量了。首先,在主机上启动作为虚拟交换机的testpmd实例。我们将只是让它将接收到的net_vhost0接口上的所有数据包转发到net_vhost1接口上。在启动虚拟机之前,必须先启动它,因为它会在初始化期间尝试连接到属于vhost-user设备的Unix套接字,而这些套接字是由QEMU创建的。
root@host $ testpmd -l 0,2,3,4,5 --socket-mem=1024 -n 4 \
--vdev 'net_vhost0,iface=/tmp/vhost-user1' \
--vdev 'net_vhost1,iface=/tmp/vhost-user2' -- \
--portmask=f -i --rxq=1 --txq=1 \
--nb-cores=4 --forward-mode=io
现在我们可以启动之前准备好的虚拟机:
user@host $ virsh start vhuser-test1
请注意,您可以在 testpmd 窗口中看到输出,显示了它接收到的 vhost-user 消息。
一旦虚拟机启动后,我们可以启动 testpmd 实例。这个实例将初始化端口和 DPDK 实现的 virtio-net 驱动程序。在这其中,virtio 特性协商会发生,一组公共特性会被确定下来。
在启动 testpmd 之前,确保 vfio 内核模块已加载,并将 virtio-net 设备绑定到 vfio-pci 驱动程序:
root@guest $ dpdk-devbind.py -b vfio-pci 0000:00:10.0 0000:00:11.0
启动 testpmd:
root@guest $ testpmd -l 0,1,2 --socket-mem 1024 -n 4 \
--proc-type auto --file-prefix pg -- \
--portmask=3 --forward-mode=macswap --port-topology=chained \
--disable-rss -i --rxq=1 --txq=1 \
--rxd=256 --txd=256 --nb-cores=2 --auto-start
现在,我们可以检查我们的 testpmd 实例处理了多少数据包。在 testpmd 提示符上,输入命令 ‘show port stats all’,查看每个方向(RX/TX)转发的数据包数量。
示例:
testpmd> show port stats all
######################## NIC statistics for port 0 ########################
RX-packets: 75525952 RX-missed: 0 RX-bytes: 4833660928
RX-errors: 0
RX-nombuf: 0
TX-packets: 75525984 TX-errors: 0 TX-bytes: 4833662976
Throughput (since last show)
Rx-pps: 4684120
Tx-pps: 4684120
#########################################################################
######################## NIC statistics for port 1 ########################
RX-packets: 75525984 RX-missed: 0 RX-bytes: 4833662976
RX-errors: 0
RX-nombuf: 0
TX-packets: 75526016 TX-errors: 0 TX-bytes: 4833665024
Throughput (since last show)
Rx-pps: 4681229
Tx-pps: 4681229
#########################################################################
testpmd 中有不同的转发模式。在这个示例中,我们使用了 --forward-mode=macswap,它会交换目标和源 MAC 地址。其他的转发模式,如 ‘io’,完全不处理数据包,因此会得到更高的数据包转发速率,但也更不真实。还有另一种转发模式是 ‘noisy’,它可以进行微调以模拟数据包缓冲和内存查找。
额外内容:优化配置以实现最大吞吐量和低延迟。
到目前为止,我们大多数时候都使用了默认设置。这有助于使教程简单易懂。但对于那些希望调整所有组件以获得最佳性能的读者,我们将解释如何实现这一目标。
优化主机设置
首先,我们从优化主机系统开始。
为了获得最佳性能,我们需要在主机系统中进行一些设置。请注意,您不一定需要执行所有这些手动步骤。使用 tuned,您可以选择一组可用的 tuned 配置文件。应用 tuned 的 cpu-partitioning 配置文件将处理我们将在此手动执行的所有步骤。
在我们开始详细解释调整内容之前,这是您如何使用 tuned 的 cpu-partition 配置文件,而不必担心所有细节:
user@host $ sudo dnf install tuned-profiles-cpu-partitioning
然后编辑 /etc/tuned/cpu-partitioning-variables.conf 文件,并将 isol_cpus 和 no_balance_cores 都设置为 2-7。
现在,我们可以使用 tuned-adm 命令应用 tuned 配置文件:
user@host $ sudo tuned-adm profile cpu-partitioning
为了应用这些更改,需要重新启动系统,因为我们添加了内核参数。
对于那些想要了解 cpu-partitioning 配置文件更多细节的读者,我们可以手动执行这些步骤。如果您对此不感兴趣,可以直接跳到下一节。
假设我们的系统有八个核心,并且我们想要将其中六个隔离在同一个 NUMA 节点上。我们使用两个核心来运行虚拟机的虚拟 CPU,剩下的四个核心用于运行应用程序的数据路径。
最基本的更改甚至可以在 Linux 之外的系统 BIOS 设置中完成。在那里,我们必须禁用 turbo-boost 和超线程。如果由于某种原因无法访问 BIOS,请使用以下命令禁用超线程:
cat /sys/devices/system/cpu/cpu*[0-9]/topology/thread_siblings_list \
| sort | uniq \
| awk -F, '{system("echo 0 > /sys/devices/system/cpu/cpu"$2"/online")}'
之后,我们将以下内容添加到内核命令行中:
intel_pstate=disable isolcpus=2-7 rcu_nocbs=2-7 nohz_full=2-7
这些参数的含义是:
intel_pstate=disable:避免切换电源状态
通过运行以下命令来执行这些操作:
user@host $ grubby --args “intel_pstate=disable mce=ignore_ce isolcpus=2-7 rcu_nocbs=2-7 nohz_full=2-7” --update-kernel /boot/<your kernel image file>
不可屏蔽中断会降低性能,因为它们会占用宝贵的周期,本应由核心处理数据包,所以我们通过以下方式禁用它们:
user@host $ echo 0 > /proc/sys/kernel/nmi_watchdog
为了达到类似的效果,我们从回写CPU掩码中排除我们想要隔离的核心:
user@host $ echo ffffff03 > /sys/bus/workqueue/devices/writeback/cpumask
Optimizing guest settings
与我们在主机上所做的类似,我们还要更改客户机的内核参数。再次使用 grubby,我们将以下参数添加到配置中:
default_hugepagesz=1G hugepagesz=1G hugepages=1 intel_iommu=on iommu=pt isolcpus=1,2 rcu_nocbs=1,2 nohz_full=1,2
第一个参数的含义是,我们要与主机配置中已知的前三个参数相同。其他参数如下:
intel_iommu=on:启用IOMMU。
iommu=pt:将IOMMU设置为通过模式。关于这意味着什么,稍后会详细介绍。
isolcpus=1,2:要求内核隔离这些核心。
rcu_nocbs=1,2:不要在CPU上执行RCU回调,将其卸载到其他线程以避免RCU回调作为软中断。
nohz_full=1,2:避免调度时钟滴答声。
因为我们希望与主机上的核心相同,处理数据包的客户机核心,我们执行相同的步骤并禁用NMIs,将这些核心从块设备写回刷新线程和IRQ中排除在外,如下所示:
user@guest $ echo 0 > /proc/sys/kernel/nmi_watchdog
user@guest $ echo 1 > /sys/bus/workqueue/devices/writeback/cpumask
user@guest $ clear_mask=0x6 #Isolate CPU1 and CPU2 from IRQs
for i in /proc/irq/*/smp_affinity
do
echo "obase=16;$(( 0x$(cat $i) & ~$clear_mask ))" | bc > $i
done
在主机上将虚拟CPU绑定到物理核心将确保vcpu不会被调度到不同的核心。
<cputune>
<vcpupin vcpu='0' cpuset='1'/>
<vcpupin vcpu='1' cpuset='6'/>
<vcpupin vcpu='2' cpuset='7'/>
<emulatorpin cpuset='0'/>
</cputune>
Analyzing the performance
在经过所有性能调整步骤后,让我们再次运行testpmd实例,以查看每秒处理的数据包数量发生了何种变化。
testpmd> show port stats all
######################## NIC statistics for port 0 ########################
RX-packets: 24828768 RX-missed: 0 RX-bytes: 1589041152
RX-errors: 0
RX-nombuf: 0
TX-packets: 24828800 TX-errors: 0 TX-bytes: 1589043200
Throughput (since last show)
Rx-pps: 5207755
Tx-pps: 5207755
########################################################################
######################## NIC statistics for port 1 ########################
RX-packets: 24852096 RX-missed: 0 RX-bytes: 1590534144
RX-errors: 0
RX-nombuf: 0
TX-packets: 24852128 TX-errors: 0 TX-bytes: 1590536192
Throughput (since last show)
Rx-pps: 5207927
Tx-pps: 5207927
########################################################################
与未经调优的设置相比,每秒处理的数据包数量大致增加了12%。这是一个非常简单的设置,主机和虚拟机上没有运行其他工作负载。在更复杂的场景中,性能改善可能会更加显著。
在之前构建了一个简单的设置后,本节我们集中讨论了调整各个组件性能的问题。关键在于取消配置和禁用所有会使核心(物理或虚拟)分心的因素,让它们执行其预期任务:处理数据包。
我们以看似复杂的一系列命令手动执行了这些操作,以便了解背后的原理。但事实上,所有这些都可以通过安装和使用“tuned”和“tuned-profiles-cpu-partition”软件包以及简单的一行配置文件更改来实现。而且,将虚拟CPU固定到主机核心是取得最大影响的关键。
提供了Ansible脚本
设置和运行这个环境是理解、调试和测试这个架构的第一和基本步骤。为了尽可能地快速和简便,红帽的virtio-net团队已经开发了一组Ansible脚本供所有人使用。
只需按照README中的说明操作,Ansible会处理其余的工作。
结论
我们已经设置和配置了一个主机系统,以运行基于DPDK的应用程序,并创建了一个通过vhost-user接口连接到它的虚拟机。在虚拟机内部,我们运行了基于DPDK的testpmd,并使用它来在主机中的testpmd vswitch实例和虚拟机中的实例之间循环生成、发送和接收数据包。我们查看的设置非常简单。对于感兴趣的读者,下一步可能是部署和使用OVS-DPDK,这是基于DPDK构建的OpenVSwitch。它是在生产场景中使用的更高级的虚拟交换机。