ovs+dpdk numa感知特性验证
0.介绍
本测试是为了验证这篇文章中提到的DPDK的NUMA感知特性。
简单来说,在ovs+dpdk+qemu的环境中,一个虚拟机牵涉到的内存共有三部分:
- DPDK为vHost User设备分配的Device tracking memory
- OVS为网络通信分配的mbufs
- QEMU为虚拟机分配的内存
未开启DPDK的NUMA感知特性时,所有Device tracking memory都会使用同一个NUMA节点中的内存,如果这时QEMU为两台虚拟机分配的内存刚好在两个不同的NUMA节点上,那么机器间的网络通信效率就会有损耗。
开启了DPDK的NUMA感知特性后,在QEMU创建虚拟机后,DPDK会将vHost User设备的Device tracking memory挪到与虚拟机相同的NUMA节点上,并且DPDK还会通知OVS,以让OVS把与该虚拟机相关的mbufs分配在与虚拟机相同的NUMA节点上。
理论上这应该能增加两虚拟机的网络通信效率,所以本文就来用实验探究一下
但在实验之前,我对结果的预期是悲观的,我感觉(感性的)虚拟机间的通信效率更多的受制于虚拟机本身的配置,尤其是在虚拟机的规格比较差的情况下。
本文将进行对照测试,分别在“带NUMA感知的DPDK环境”与“不带NUMA感知的DPDK环境”进行测试,每次测试都在两个不同的NUMA节点上分别启动一台配置相同的虚拟机,使用netperf对两虚拟机间的网络通信效率进行测量。
虚拟机配置: 2*vcpu, 4G RAM, CentOS 7
1.测试环境配置
host的基本配置如下:
项 | 版本/配置 |
---|---|
CPU | 2 * [Intel(R) Xeon(R) CPU E5-2698 v3 @ 2.30GHz] |
OVS | ovs-vsctl (Open vSwitch) 2.7.0 |
DPDK | stable-16.11.1 |
[root@compute2 zsb]# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62
node 0 size: 130975 MB
node 0 free: 102930 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63
node 1 size: 131072 MB
node 1 free: 114196 MB
node distances:
node 0 1
0: 10 21
1: 21 10
1.1.numa库与命令行软件的安装
yum install numactl
yum install numactl-libs
yum install numactl-devel
1.2.内核启动参数配置
16个1G规格的大页,逻辑核心1-17,33-49不参与内核处理器调度
hugepagesz=1G
hugepages=16
iommu=pt
intel_iommu=on
isolcpus=1-17,33-49
1.3.hugepage挂载设置
移除原有的2M规格大页的挂载点,只挂载规格为1G的大页
umount /dev/hugepages
mkdir /mnt/huge_1GB
mount -t hugetlbfs -o pagesize=1G none /mnt/huge_1GB
1.4.dpdk的编译
打开numa感知的编译选项
CONFIG_RTE_LIBRTE_VHOST_NUMA=y
编译
export DPDK_DIR=<DPDK_DIR>
export DPDK_TARGET=x86_64-native-linuxapp-gcc
export DPDK_BUILD=$DPDK_DIR/$DPDK_TARGET
make install T=$DPDK_TARGET DESTDIR=install
1.5.ovs的编译安装
export OVS_DIR=<OVS_DIR>
cd $OVS_DIR
./configure --with-dpdk=$DPDK_BUILD CFLAGS="-Ofast -g"
make CFLAGS="-Ofast -g"
make install
1.6.ovs的启动与pmd轮询核心亲和性的配置
pmd-mask被设置为0x0c0000000c,即pmd亲和2 3 34 35四个逻辑核心。其中2与34是同一个物理封装里同一个core的两个超线程,位于NUMA 0,3与35是另外一个物理封装里的同一个core的两个超线程,位于NUMA 1。如下表
逻辑核心编号 | 物理封装编号 | core编号 | 属于的NUMA节点 |
---|---|---|---|
2 | 0 | 1 | 0 |
34 | 0 | 1 | 0 |
3 | 1 | 1 | 1 |
35 | 1 | 1 | 1 |
rm -rf /usr/local/etc/openvswitch/conf.db
mkdir -p /usr/local/etc/openvswitch
ovsdb-tool create /usr/local/etc/openvswitch/conf.db $OVS_DIR/vswitchd/vswitch.ovsschema
mkdir -p /usr/local/var/run/openvswitch
ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock \
--remote=db:Open_vSwitch,Open_vSwitch,manager_options \
--private-key=db:Open_vSwitch,SSL,private_key \
--certificate=db:Open_vSwitch,SSL,certificate \
--bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
--pidfile --detach --log-file
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0x0c0000000c
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem="1024,1024"
export DB_SOCK=/usr/local/var/run/openvswitch/db.sock
ovs-vswitchd unix:$DB_SOCK --pidfile --detach --log-file
1.7.创建bridge与port
ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev
ovs-vsctl add-port br0 port1 \
-- set interface port1 type=dpdkvhostuserclient \
-- set interface port1 options:vhost-server-path=/var/lib/openvswitch/port1
ovs-vsctl add-port br0 port2 \
-- set interface port2 type=dpdkvhostuserclient \
-- set interface port2 options:vhost-server-path=/var/lib/openvswitch/port2
1.8.启动虚拟机的配置
启动虚拟机的libvirt domain xml文件如下所示,注意两台虚拟机配置有差异的地方均有注释
注意虚拟机1配置vcpu时,将cpuset设置为6,38,在host上,6号与38号逻辑cpu属于NUMA node 0,而5,37属于NUMA node 1
<domain type=‘kvm‘>
<!-- 另外一台虚拟机:NUMA_Awareness_test_2 -->
<name>NUMA_Awareness_test_1</name>
<memoryBacking>
<hugepages/>
</memoryBacking>
<!-- 另外一台虚拟机:cpuset="5,37" -->
<vcpu placement=‘static‘ cpuset="6,38">2</vcpu>
<cpu mode=‘host-model‘>
<model fallback=‘allow‘/>
<topology sockets=‘2‘ cores=‘1‘ threads=‘1‘/>
<numa>
<cell id=‘0‘ cpus=‘0-1‘ memory=‘4194304‘ unit=‘KiB‘ memAccess=‘shared‘/>
</numa>
</cpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch=‘x86_64‘ machine=‘pc‘>hvm</type>
<boot dev=‘hd‘/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<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>restart</on_crash>
<pm>
<suspend-to-mem enabled=‘no‘/>
<suspend-to-disk enabled=‘no‘/>
</pm>
<devices>
<emulator>/usr/libexec/qemu-kvm</emulator>
<disk type=‘file‘ device=‘disk‘>
<driver name=‘qemu‘ type=‘qcow2‘/>
<!-- 另外一台虚拟机 file=‘/export/zsb/image/NUMA_Awareness_test_2.qcow2‘ -->
<source file=‘/export/zsb/image/NUMA_Awareness_test_1.qcow2‘/>
<target dev=‘hda‘ bus=‘ide‘/>
<address type=‘drive‘ controller=‘0‘ bus=‘0‘ target=‘0‘ unit=‘0‘/>
</disk>
<controller type=‘pci‘ index=‘0‘ model=‘pci-root‘/>
<controller type=‘ide‘ index=‘0‘>
<address type=‘pci‘ domain=‘0x0000‘ bus=‘0x00‘ slot=‘0x01‘ function=‘0x1‘/>
</controller>
<controller type=‘virtio-serial‘ index=‘0‘>
<address type=‘pci‘ domain=‘0x0000‘ bus=‘0x00‘ slot=‘0x06‘ function=‘0x0‘/>
</controller>
<interface type=‘vhostuser‘>
<!-- 另外一台虚拟机:path=‘/var/lib/openvswitch/port2‘ -->
<source type=‘unix‘ mode=‘server‘ path=‘/var/lib/openvswitch/port1‘/>
<model type=‘virtio‘/>
<driver name=‘vhost‘/>
</interface>
<serial type=‘pty‘>
<target port=‘0‘/>
</serial>
<console type=‘pty‘>
<target type=‘serial‘ port=‘0‘/>
</console>
<input type=‘tablet‘ bus=‘usb‘>
<address type=‘usb‘ bus=‘0‘ port=‘1‘/>
</input>
<input type=‘mouse‘ bus=‘ps2‘/>
<input type=‘keyboard‘ bus=‘ps2‘/>
<graphics type=‘vnc‘ port=‘-1‘ autoport=‘yes‘ listen=‘0.0.0.0‘>
<listen type=‘address‘ address=‘0.0.0.0‘/>
</graphics>
<sound model=‘ich6‘>
<address type=‘pci‘ domain=‘0x0000‘ bus=‘0x00‘ slot=‘0x04‘ function=‘0x0‘/>
</sound>
<video>
<model type=‘cirrus‘ vram=‘16384‘ heads=‘1‘ primary=‘yes‘/>
<address type=‘pci‘ domain=‘0x0000‘ bus=‘0x00‘ slot=‘0x02‘ function=‘0x0‘/>
</video>
<memballoon model=‘virtio‘>
<address type=‘pci‘ domain=‘0x0000‘ bus=‘0x00‘ slot=‘0x08‘ function=‘0x0‘/>
</memballoon>
</devices>
<seclabel type=‘none‘ model=‘none‘/>
<seclabel type=‘dynamic‘ model=‘dac‘ relabel=‘yes‘/>
</domain>
在虚拟机启动后,编辑虚拟机的网络设置脚本,给两个虚拟机分别配上10.0.0.1与10.0.0.2的IP地址
vim /etc/sysconfig/network-scripts/ifcfg-eth0
--------------------------------
DEVICE=eth0
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
# 对于另外一台虚拟机,设置IP地址为10.0.0.2
IPADDR=10.0.0.1
为了方便ssh到虚拟机中,我们在host上创建一个namespace,并创建一个veth-pair设备,把veth-pair的一端放在namespace中,另一端接在ovs-bridge上,从该namespace中ssh到两个虚拟机
其中namespace的名为numa_awareness_test
veth-pair设备中,接入ovs-bridge的设备名为veth-ovs,接入namespace的设备名为veth-ns
ip netns add numa_awareness_test
ip link add name veth-ovs type veth peer name veth-ns
ip link set veth-ovs up
ip link set veth-ns netns numa_awareness_test
ip netns exec numa_awareness_test ip link set dev lo up
ip netns exec numa_awareness_test ifconfig veth-ns 10.0.0.3/24 up
ovs-vsctl add-port br0 veth-ovs
为了能登录到虚拟机中,还需要关闭掉namespace中veth-ns的checksum offload功能
ip netns exec numa_awareness_test ethtool -K veth-ns tx off
之后通过在namespace中执行ssh就可登录到虚拟机上
ip netns exec numa_awareness_test ssh 10.0.0.1
ip netns exec numa_awareness_test ssh 10.0.0.2
另外为了方便监控hugepage的使用情况,可以用以下一个简单的bash脚本进行监测
#! /bin/bash
function printRed() {
printf "\e[1;31m$1\e[1;0m"
}
printRed "----------------\n"
printRed "Hugepagesz = 1GB\n"
total=$(cat /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages)
free=$(cat /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/free_hugepages)
printRed " Numa Node 0: Total: $total\n"
printRed " Free: $free\n"
total=$(cat /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages)
free=$(cat /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/free_hugepages)
printRed " Numa Node 1: Total: $total\n"
printRed " Free: $free\n"
printRed "----------------\n"
printRed "Hugepagesz = 2MB\n"
total=$(cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages)
free=$(cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages)
printRed " Numa Node 0: Total: $total\n"
printRed " Free: $free\n"
total=$(cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages)
free=$(cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/free_hugepages)
printRed " Numa Node 1: Total: $total\n"
printRed " Free: $free\n"
printRed "----------------\n"
2.测试结果
2.1.采用NUMA感知
在两台虚拟机启动成功之后,查看hugepage的使用情况,如下:
[root@compute2 xml]# ../sh/checkHugepages.sh
----------------
Hugepagesz = 1GB
Numa Node 0: Total: 8
Free: 3
Numa Node 1: Total: 8
Free: 3
----------------
Hugepagesz = 2MB
Numa Node 0: Total: 1024
Free: 1024
Numa Node 1: Total: 1024
Free: 1024
----------------
可以看到两个NUMA节点上每个虚拟机占用了4G内存,ovs分别在两个NUMA节点上占用1G内存,与预期相符
另外查看htop命令的输出,可以看到2号逻辑CPU(在htop中显示为3)与35号逻辑CPU(在htop中显示为36)被PMD轮询使用,这是符合预期的,2号逻辑CPU属于NUMA node 0,而35号逻辑CPU属于NUMA node 1。
虽然PMD轮询线程的掩码设置了4个逻辑CPU,但只有两个在工作,这是因为在环境中只有两个vHost User类型的ovs-port存在的原因。
另外,2号和34号其实是同一颗物理核心虚出来的两个超线程,3号与35号也是如此,虽然显示上3号逻辑核心与34号逻辑核心并没有工作,但其实已经在工作的2号核心与35号核心基本能榨干两个物理核心的性能。
而两个虚拟机在运行性能测试工具时,分别导致了逻辑核心5号与6号(在htop中分别显示为6号与7号)高占用,这也是符合预期的,说明两个虚拟机分别运行在两个不同的NUMA节点上。但对应的37号和38号逻辑核心没有什么占用,基本上是因为netperf工具是单线程程序的原因。
总的来说,配置如下图所示:
接下来分别在两台虚拟机上进行测试:
使用netperf工具进行测试
在10.0.0.1上运行服务端,在10.0.0.2上运行客户端,五分钟带宽测试的结果如下:
[root@localhost ~]# netperf -H 10.0.0.1 -t TCP_STREAM -l 300
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.0.0.1 (10.0.0.1) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 300.00 6063.37
将netperf的客户端与服务端对换,再次进行5分钱带宽测试,结果如下:
[root@localhost ~]# netperf -H 10.0.0.2 -t TCP_STREAM -l 300
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.0.0.2 (10.0.0.2) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 300.00 5826.15
2.2.不采用NUMA感知
测试配置上与采用NUMA感知配置不同的地方在于
- 关闭dpdk编译时的CONFIG_RTE_LIBRTE_VHOST_NUMA选项,重新编译dpdk与ovs
- 配置及启动ovs的过程中,将PMD轮询线程的掩码设置为0x1400000014,即使用2 4 34 36四颗核心,这四颗核心都属于NUMA node 0,且物理是是为两颗物理核心虚拟出来的四个超线程
- 配置及启动ovs的过程中,将dpdk-socket-mem设置为2048,0,这样ovs+dpdk会占用NUMA node 0上的2G内存自用
- 两台虚拟机的virsh domain xml配置文件不做更改,即虚拟机的配置是完全相同的
在这个场景中,
- ovs+dpdk占用NUMA node 0上的2G hugepage 内存,即ovs的mbufs与dpdk的deivce tracking memory都将分配在NUMA node 0上
- 而两个虚拟机的内存分配和之前一样,即qemu-kvm为虚拟机分配的内存依然分散在两个NUMA节点上
- PMD轮询依然使用了四个逻辑核心,实际上是两个物理核心,与上相同,不同的是这次四个轮回核心将位于同一个NUMA节点上
虚拟机启动后,hugepage的使用情况如下:
[root@compute2 xml]# ../sh/checkHugepages.sh
----------------
Hugepagesz = 1GB
Numa Node 0: Total: 8
Free: 2
Numa Node 1: Total: 8
Free: 4
----------------
Hugepagesz = 2MB
Numa Node 0: Total: 1024
Free: 1024
Numa Node 1: Total: 1024
Free: 1024
----------------
其中Node 0上有2G内存供ovs+dpdk使用,再就是每个Node都有4G内存供虚拟机使用
htop的输出如下所示(运行netperf测试中):
总的来说,配置如下图所示:
接下来依然同上进行测试:
在10.0.0.1上运行服务端,在10.0.0.2上运行客户端,五分钟带宽测试的结果如下:
[root@localhost ~]# netperf -H 10.0.0.1 -t TCP_STREAM -l 300
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.0.0.1 (10.0.0.1) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 300.00 3453.29
将netperf的客户端与服务端对换,再次进行5分钱带宽测试,结果如下:
[root@localhost ~]# netperf -H 10.0.0.2 -t TCP_STREAM -l 300
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.0.0.2 (10.0.0.2) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 300.00 5701.31
2.3.数据对比
以下为简单直白的测试数据对比
是否开启NUMA感知 | 10.0.0.2 -> 10.0.0.1 | 10.0.0.1->10.0.0.2 |
---|---|---|
是 | 6063.37 | 5826.15 |
否 | 3453.29 | 5701.31 |
测试数据比较迷,从10.0.0.2向10.0.0.1打流提升很明显,但反过来就基本没差别了,这后面是什么原因还有待再观察
基本的猜测是由于10.0.0.1所属的虚拟机,其内存与ovs+dpdk同属于一个NUMA节点,这样10.0.0.1发送流量时,流量能更有效率的到达ovs。
3.更多的思考
首先开启了NUMA感知后性能看起来是有提升的,但这个数据比较迷,目前也说不好是为什么
其次,同一host内的虚拟机通信效率可能通过NUMA感知特性进行优化
如果是不同host下的虚拟机要进行通信效率的优化,可能还需要考虑到物理网卡与NUMA的关系