Hands on vhost-user: A warm welcome to DPDK

这片文章里我们将会配置一个环境,然后在虚拟机里运行一个基于DPDK应用。我们将介绍所有用来在host系统上配置一个虚拟交换机需要的步骤,并通过这个虚拟交换机连接到虚拟机的应用。正文包括描述如何创建,安装和运行虚拟机,以及安装里面的应用。你将会学习到如何创建并设置一个简单的通过guest内的应用发送并接收网络数据包到host的虚拟交换机。基于这些设置,你将会学习到如何如何调整设置来获得最优的吞吐性能。

Setting up

对于乐意使用DPDK但不希望配置和安装相关软件的,我们提供了一个ansible playbooks在github的repo里,自动化了所有步骤,我们就基于这个配置开始吧。

Requirements:

  • 一台运行了Linux发行版的电脑。本文使用Centos 7,不过不同的Linux发行版之间命令的差别也不会特别大,特别是Red Hat Enterprise Linux 7
  • 一个有sudo权限的用户
  • home目录下有大于25GB的空闲空间
  • 至少8GB的RAM

首先我们先安装我们需要的包

1
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

首先从下面的网站下载一个最新的Centos-Cloud-Base镜像

1
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

这个下载的是一个预安装的Centos7,用来在OpenStack环境运行的。因为我们不使用OpenStack,所以我们需要清理一下这个虚拟机。不过首先我们需要做一个镜像的copy。以此保证我们之后能重复使用这个镜像。

1
sudo qemu-img create -f qcow2 -b  /var/lib/libvirt/images/CentOS-7-x86_64-GenericCloud.qcow2  /var/lib/libvirt/images/vhuser-test1.qcow2 20G

通过下面的配置我们可以允许非特权用户使用libvirt命令(推荐):

1
export LIBVIRT_DEFAULT_URI="qemu:///system"

然后使用清理命令:

1
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’网络,启动并检查网络运行状态

1
2
3
4
5
6
7
8
9
10
[root@10-0-117-158 ~]# virsh net-define /usr/share/libvirt/networks/default.xml
Network default defined from /usr/share/libvirt/networks/default.xml

[root@10-0-117-158 ~]# virsh net-start default
Network default started

[root@10-0-117-158 ~]# virsh net-list
Name State Autostart Persistent
--------------------------------------------
default active no yes

最后我们使用virt-install来创建虚拟机。这个命令行工具包含了一系列常用的操作系统配置定义。然后我们可以基于这个基本定义做一些改动:

1
2
3
4
5
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

这些参数分别制定了。vCPUs的数量,RAM的大小,磁盘的路径,以及虚拟机要连接的网络。

出了通过我们指定的这些参数定义VM之外,virt-install会把虚拟机同时创建出来,所以我们应该可以看到:

1
2
3
4
[root@10-0-117-158 ~]# virsh list
Id Name State
------------------------------
7 vhuser-test1 running

很好,虚拟机已经运行了。接下来我们先把虚拟机停下来然后做一些额外的配置

1
virsh shutdown vhuser-test1

Preparing the host

DPDK对内存缓存的分配和管理做了优化。在Linux上这个需要使用hugepage的支持,所以必须要在kernel上打开。使用的page大小通常需要大于4K,以此通过使用更少的page数量,以及更少的TLB来提升性能。在翻译虚拟地址到物理地址的时候会产生这些查询。为了在启动时分配hugepage,我们需要在bootloader配置里加上kernel参数

1
sudo grubby --args="default_hugepagesz=1G hugepagesz=1G hugepages=6 iommu=pt intel_iommu=on" --update-kernel /boot/vmlinuz-3.10.0-957.27.2.el7.x86_64

当然我们来解释一下这些参数做了什么:

default_hugepagesz=1G 默认创建出来的hugepages默认是1GB

hugepagesz=1G 启动过程中创建出来的hugepage大小也是1GB

hugepages=6 最开始启动的时候创建6个大小为1GB的hugepage,这个在重启之后可以在/proc/meminfo里看到

注意,补充说明hugepages的设置增加了两个IOMMU相关的参数 iommu=pt intel_iommu=on 这个会初始化Intel VT-d以及IOMMU Pass-Through模式,在Linux用户态处理IO的时候需要用到他们。因此我们修改了kernel参数,现在刚好可以做一下重启。

等到重启完成之后,我们可以通过命令行查看对应的参数已经生效了

1
2
[root@10-0-117-158 ~]# cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-3.10.0-957.27.2.el7.x86_64 root=/dev/mapper/zstack-root ro noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off crashkernel=auto rd.lvm.lv=zstack/root rd.lvm.lv=zstack/swap rhgb quiet LANG=en_US.UTF-8 default_hugepagesz=1G hugepagesz=1G hugepages=6 iommu=pt intel_iommu=on

Prepare the guest

virt-install命令通过libvirt创建并启动了一个虚拟机。为了将基于DPDK的vswitch TestPMD连接到QEMU,我们需要增加如下的定义到XML的device部分:

1
virsh edit vhuser-test1

<device>部分增加

1
2
3
4
5
6
7
8
9
10
11
12
<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不同的guest配置就是hugepages。因此我们需要给guest增加如下定义:

1
2
3
4
5
6
7
8
9
<memoryBacking>
<hugepages>
<page size='1048576' unit='KiB' nodeset='0'/>
</hugepages>
<locked/>
</memoryBacking>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>

这样就有内存了,然后再修改guest里的配置,这是非常重要的配置,没有的话就没办法收发数据包了:

1
2
3
4
5
6
 <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>

然后我们需要启动我们的guest。因为我们配置了让虚拟机连接到vhost-user的UNIX sockets,因此我们需要确保guest启动的时候这些sockets时可用的。这是通过启动testpmd实现的,这个操作会创建我们需要的sockets。

1
2
3
4
5
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 sockets,因此启动QEMU的时候需要用root。所以在/etc/libvirt/qemu.conf 中设置 user=root。 这是因为我们呢的特殊验证场景需要这样配置,生产环境通常不建议这样配置。实际上读者需要在本文演示结束之后把 user=root 这个配置去掉。

现在我们可以通过命令启动虚拟机了:

1
virsh start vhuser-test1.

通过root登陆之后,我们要做的第一件事就是绑定virtio设备到vfio-pci驱动。为了能够完成这个操作,我们需要加载一些内核模块

1
2
3
4
5
[root@localhost ~]# modprobe  vfio enable_unsafe_noiommu_mode=1
[ 90.462919] VFIO - User Level meta-driver version: 0.3
[root@localhost ~]# cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
Y
[root@localhost ~]# modprobe vfio-pci

然后找出virtio-net设备的PCI地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# dpdk-devbind --status net

Network devices using kernel driver
===================================
0000:00:02.0 'Virtio network device 1000' if=eth0 drv=virtio-pci unused=virtio_pci,vfio-pci *Active*
0000:00:08.0 'Virtio network device 1000' if=eth1 drv=virtio-pci unused=virtio_pci,vfio-pci
0000:00:09.0 'Virtio network device 1000' if=eth2 drv=virtio-pci unused=virtio_pci,vfio-pci

No 'Crypto' devices detected
============================

No 'Eventdev' devices detected
==============================

No 'Mempool' devices detected
=============================

No 'Compress' devices detected
==============================

在dpdk-devbind的输出中找到virtio-devices的部分,并且没有被标记Active状态的。我们可以用这些设备来进行实验。注意:地址可能是不一样的。当我们首次启动这些设备的时候将会自动绑定到virtio-pci驱动,因为我们需要和非kernel的驱动一起使用,首先就是要将这些设备和virtio-pci设备解绑,然后再绑定到vfio-pci驱动

1
2
3
4
5
[root@localhost ~]# dpdk-devbind -b vfio-pci 0000:00:08.0 0000:00:09.0
[ 360.862724] iommu: Adding device 0000:00:08.0 to group 0
[ 360.871147] vfio-pci 0000:00:08.0: Adding kernel taint for vfio-noiommu group on device
[ 360.951240] iommu: Adding device 0000:00:09.0 to group 1
[ 360.960126] vfio-pci 0000:00:09.0: Adding kernel taint for vfio-noiommu group on device

Generating traffic

我们已经安装并配置好所有东西了,接下来就是运行网络负载了。首先在host上我们需要启动testpmd实例作为虚拟交换机。然后设置它转发所有在net_vhost0收到的数据包到net_vhost1。testpmd需要在虚拟机启动之前启动,因为它会尝试连接到由QEMU创建的属于vhost-user设备初始化出来的unix sockets。

1
2
3
4
5
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

然后我们来启动之前准备好的虚拟机:

1
virsh start vhuser-test1

注意这时候我们能够在testpmd看到vhost-user收到的数据包了:

1
2
3
4
5
6
7
8
9
10
Port 1: link state change event
VHOST_CONFIG: vring base idx:0 file:0
VHOST_CONFIG: read message VHOST_USER_GET_VRING_BASE
VHOST_CONFIG: vring base idx:1 file:0
VHOST_CONFIG: read message VHOST_USER_GET_VRING_BASE

Port 0: link state change event
VHOST_CONFIG: vring base idx:0 file:0
VHOST_CONFIG: read message VHOST_USER_GET_VRING_BASE
VHOST_CONFIG: vring base idx:1 file:0

当guest启动之后我们就能够启动testpmd了。testpmd会初始化端口以及DPDK实现的virtio-net驱动。另外还有virtio特性的协商以及其他一些通用功能的协商也都在这一步发生了。

在我们启动testpmd之前,需要确认vfio内核模块已经加载并绑定了virtio-net设备到vfio-pci驱动:

1
dpdk-devbind -b vfio-pci 0000:00:08.0 0000:00:09.0

然后可以启动testpmd:

1
2
3
4
5
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处理了多少数据包了,我们可以通过输入命令 show port stats all 来看对应(RX/TX)方向的信息,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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有不同的转发模式,这个例子里面我们用的是macswap,此模式会交换目标和源头的mac地址。另外的转发模式,比如’io’则不会处理包,所以会给出更高深职很不现实的数据。另外一个转发模式就是’noisy’,可以模拟调整包的缓存/内存的查找。