亚马逊AWS官方博客

如何在 AWS EC2 实例以及基于 AWS 的容器平台中使用 DPDK(一)

1 背景

数据平面开发工具包 Data Plane Development Kit(DPDK) 由一组库和用户空间驱动组成的,用来加速包处理和转发的速度并降低延时,它支持多种不同的处理器架构和操作系统。DPDK被设计运行在用户空间,这样,基于DPDK的应用程序可以直接与网卡交互收发数据包,对于某些网络性能密集型场景下的应用有着现实的意义。

具体来说,DPDK所带来的主要的优势在于:通过用户空间的驱动,旁路kernel和TCP协议栈,规避了过多的内存拷贝和系统调用;通过轮询的方式,避免传统包处理时的中断和上下文切换;通过hugepage降低TLB miss,同时利用多通道内存,避免了过多访存开销;最后,还可以设置亲和性和独占,避免了不同核心的线程切换。除此之外,DPDK还在管理上提供了更多的包处理的可控制性。

进一步,在容器环境下,无论是原生的Docker还是平台化的kubernetes,不同的网络插件也提供了对于DPDK的支持,使得在容器环境中也可以利用到DPDK所提供的优势和性能提升。

2 DPDK对AWS的支持

在AWS平台上,DPDK可以支持具有增强网络(Enhanced Networking)的实例,包括了基于intel的82599(ixgbevf)和基于AWS的Elastic Network Adapter(ena)。具体来说,基于Nitro的实例,例如C5,M5,I3和T3以及上一代基于intel的C4,M4和T2等具备增强网络的实例都可以支持DPDK。在使用DPDK时,需要选用16.04之后的版本,DPDK在该版本之后才提供了对AWS EC2的支持。

下图示意了non-DPDK和DPDK优化的应用之间的区别。

本文使用AWS EC2 C5.2xlarge实例,说明了如何在AWS EC2中安装和部署DPDK环境。

在后续的文章中,会进一步针对AWS上的容器平台如何和DPDK集成给出说明。

3 设置DPDK环境

使用amazon linux2 AMI启动一台c5.2xlarge实例,并通过ssh登录。

3.1 设置hugepages

CPU所能支持的hugepages大小可以通过查看CPU的flag确认,如果支持pse,则平台支持2MB的hugepages,如果支持pdpe1gb,则平台支持1GB的hugepages。

查看我们使用的操作系统环境c5.2xlarge。

# cat /proc/cpuinfo |grep -e pse -e pdpe1gb
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp
Bash

 

 
       
可见,c5.2xlarge可以支持2MB的hugepages和1GB的hugepages。 对于不同的hugepages,用不同的方式完成配置。一般来说,对于64-bit的应用,建议使用1GB hugepages。下面我们会分别来进行说明。 针对2M hugepage,设置如下: 在大多数较新的linux发行版,默认已经启用的2M hugepages,并已经通过dev-hugepages.mount服务进行挂载,比如本文所使用的amazon linux2。镜像为amzn2-ami-hvm-2.0.20191116.0-x86_64-gp2 (ami-07539a31f72d244e7),kernel版本4.14.154-128.181.amzn2.x86_64。 更新系统。
# yum update -y
Bash

 

查看当前系统对于hugepages分配和支持。

# cat /proc/meminfo |grep Huge
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:    0
HugePages_Free:     0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

# cat /proc/mounts |grep -i hugepage
hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=2M 0 0
Bash

 

针对2M hugepages,可以通过不同的方式来进行配置。

设置内核参数的方式。针对grub2引导的发行版,配置如下:

# echo GRUB_CMDLINE_LINUX="hugepages=2048">>/etc/default/grub
# grub2-mkconfig -o /boot/grub2/grub.cfg
Bash

 

重启后可以确认已经生效。

# cat /proc/cmdline | grep hugepages
BOOT_IMAGE=/boot/vmlinuz-4.14.152-127.182.amzn2.x86_64 root=UUID=e8f49d85-e739-436f-82ed-d474016253fe ro hugepages=2048 console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 nvme_core.io_timeout=4294967295 rd.emergency=poweroff rd.shell=0

# cat /proc/meminfo | grep -i hugepage
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:    2048
HugePages_Free:     2048
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Bash

临时修改修改内核参数的方式。

# echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# cat /proc/meminfo |grep Huge
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:    1024
HugePages_Free:     1024
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Bash

 

对于多节点的NUMA实例,需要显式的设定不同的NUMA节点。

# echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
# echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
Bash

 

当然,上述方式重启丢失需要重新设置,可以通过sysctl.conf配置来配置持久生效。

# echo "vm.nr_hugepages=512" >> /etc/sysctl.conf
# sysctl -p
# cat /proc/meminfo |grep Huge
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:     512
HugePages_Free:      512
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:    2048 kB
Bash

针对1G hugepages,只能通过配置内核启动参数的方式进行设置。后续的操作会使用1G hugepage来进行。

首先修改/etc/default/grub,增加GRUB_CMDLINE_LINUX=”default_hugepagesz=1G hugepagesz=1G hugepages=4”

# grub2-mkconfig -o /boot/grub2/grub.cfg
Bash

 

重新启动实例。可以查看设置已经生效。

# cat /proc/cmdline 
BOOT_IMAGE=/boot/vmlinuz-4.14.152-127.182.amzn2.x86_64 root=UUID=e8f49d85-e739-436f-82ed-d474016253fe ro default_hugepagesz=1G hugepagesz=1G hugepages=4 console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 nvme_core.io_timeout=4294967295 rd.emergency=poweroff rd.shell=0

# cat /proc/meminfo |grep -i hugepage
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:       4
HugePages_Free:        4
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:    1048576 kB
Bash

 

3.2 安装和配置DPDK

配置编译和使用所需的系统环境。

# yum groupinstall "development tools" -y
# yum install python3 numactl numactl-devel -y
Bash

 

DPDK可以使用meson和ninjia来进行配置,构建和安装,如果使用这种方式请首先安装meson和ninjia。

# pip3 install meson ninja
Bash

下面的说明中,我们使用makefile而并没有使用meson和ninjia进行安装。下载DPDK包。

# wget http://fast.dpdk.org/rel/dpdk-19.11.tar.xz
# tar Jxvf dpdk-19.11.tar.xz
# cd dpdk-19.11/
Bash

配置和安装。

# export RTE_SDK=/root/dpdk-19.11
# export RTE_TARGET=x86_64-native-linuxapp-gcc
# make config install T=$RTE_TARGET DESTDIR=$RTE_SDK
Bash

 

另外,DPDK提供了安装配置脚本dpdk-19.11/usertools/dpdk-setup.sh,我们也可以使用该脚本进行安装并可以进行后续的配置操作。

基于脚本配置方式如下。

# ./usertools/dpdk-setup.sh 
------------------------------------------------------------------------------
 RTE_SDK exported as /root/dpdk-19.11.script
------------------------------------------------------------------------------
----------------------------------------------------------
 Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] arm64-armada-linuxapp-gcc
…………
…………
[37] x86_64-native-linuxapp-clang
[38] x86_64-native-linuxapp-gcc
[39] x86_64-native-linuxapp-icc
[40] x86_64-native-linux-clang
[41] x86_64-native-linux-gcc
[42] x86_64-native-linux-icc
[43] x86_x32-native-linuxapp-gcc
[44] x86_x32-native-linux-gcc

----------------------------------------------------------
 Step 2: Setup linux environment
----------------------------------------------------------
[45] Insert IGB UIO module
[46] Insert VFIO module
[47] Insert KNI module
[48] Setup hugepage mappings for non-NUMA systems
[49] Setup hugepage mappings for NUMA systems
[50] Display current Ethernet/Baseband/Crypto device settings
[51] Bind Ethernet/Baseband/Crypto device to IGB UIO module
[52] Bind Ethernet/Baseband/Crypto device to VFIO module
[53] Setup VFIO permissions

----------------------------------------------------------
 Step 3: Run test application for linux environment
----------------------------------------------------------
[54] Run test application ($RTE_TARGET/app/test)
[55] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)

----------------------------------------------------------
 Step 4: Other tools
----------------------------------------------------------
[56] List hugepage info from /proc/meminfo

----------------------------------------------------------
 Step 5: Uninstall and system cleanup
----------------------------------------------------------
[57] Unbind devices from IGB UIO or VFIO driver
[58] Remove IGB UIO module
[59] Remove VFIO module
[60] Remove KNI module
[61] Remove hugepage mappings

[62] Exit Script
Bash

安装完毕之后,创建的目标目录内会包括了所有的lib库,poll-mode驱动和header文件,用以后续构建基于DPDK的应用。此外,该目录还包括一些内置编译完成可用的DPDK应用,可以用于测试。

接下来,我们需要加载相应的驱动来替代默认的ena驱动,以支持DPDK。ENA网卡可以支持UIO模式或者VFIO模式的DPDK驱动,linux的内核已经包含了标准的uio_pic_generic驱动,而刚刚我们编译也获得了DPDK提供的igb_uio驱动,uio_pic_generic对于Virtual functions的支持会有限制,通常在UIO模式下,我们会使用igb_uio。

# modprobe uio
# insmod /root/dpdk-19.11/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko wc_activate=1
Bash

 

相对来说,VFIO会是一个更加安全和健壮的选择,但是VFIO需要依赖于IOMMU,并且也不支持创建Virtual functions,同时还需要设置所需的权限来运行非特权用户的DPDK应用。当在不支持IOMMU的环境中,比如我们使用的EC2 C5.2xlarge环境,想要使用VIFO时,也会工作在类似UIO的非安全环境。另外,你可能需要使用AWS的patch https://github.com/amzn/amzn-drivers/tree/master/userspace/dpdk来重编译vfio-pci,以避免可能的性能问题或支持某些特性比如write combining。如下为直接加载vfio-pci模块并配置权限。

# modprobe vfio-pci
# chmod a+x /dev/vfio
# chmod 0666 /dev/vfio/*
Bash

 

对于大部分实例来说,并不支持IOMMU,需要确保驱动的noiommu参数被设置。

# echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
Bash

 

对于其他一些实例来说,比如 i3.metal,可以支持IOMMU,需要在内核参数指定。通过类似的方法,修改grub2启动设定。在GRUB_CMDLINE_LINUX中追加iommu=1 intel_iommu=on。然后重新生成引导参数文件。

# grub2-mkconfig > /boot/grub2/grub.cfg
Bash

 

重启即可生效。

3.3 挂载hugepagefs

之前的操作已经完成了hugepages内存的预留,要使得DPDK能够使用这些内存,还需要如下步骤的配置和操作。

对于2M hugepage来说。

# mkdir /mnt/huge
# mount -t hugetlbfs nodev /mnt/huge
Bash

 

或者通过修改/etc/fatab来持久化设置挂载点。

# echo “nodev /mnt/huge hugetlbfs defaults 0 0” >> /etc/fstab
Bash

 

对于1G hugepage,必须要指定pagesize作为挂载参数,本文的操作采用此方式。

# mkdir /mnt/huge_1gb
# echo “nodev /mnt/huge_1gb hugetlbfs pagesize=1GB 0 0” >> /etc/fstab
Bash

 

3.4 绑定ENA设备到UIO或者VFIO模块

这里以UIO模块为例。

首先,我们在测试的实例上增加一个新的ENI网口来配置DPDK。只需在控制台新增网络接口,并附加到正在使用的实例上。如下所示。

 

之后,我们可以在实例内查看到这块新加入的设备。

#  ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 0a:ff:5f:49:42:c6 brd ff:ff:ff:ff:ff:ff
    inet 172.31.9.193/20 brd 172.31.15.255 scope global dynamic eth0
       valid_lft 2820sec preferred_lft 2820sec
    inet6 fe80::8ff:5fff:fe49:42c6/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 0a:a1:03:74:08:90 brd ff:ff:ff:ff:ff:ff
    inet 172.31.3.226/20 brd 172.31.15.255 scope global dynamic eth1
       valid_lft 3095sec preferred_lft 3095sec
    inet6 fe80::8a1:3ff:fe74:890/64 scope link 
       valid_lft forever preferred_lft forever
Bash

 

我们将eth1绑定到igb_uio模块,DPDK提供的dpdk-devbind.py脚本可以用来进行绑定,解绑或者状态查看等操作(或者使用dpdk-setup.sh脚本来进行配置)。当前两个ENA设备均使用的ena驱动。

# /root/dpdk-19.11/usertools/dpdk-devbind.py --status

Network devices using kernel driver
===================================
0000:00:05.0 'Elastic Network Adapter (ENA) ec20' if=eth0 drv=ena unused=igb_uio *Active*
0000:00:06.0 'Elastic Network Adapter (ENA) ec20' if=eth1 drv=ena unused=igb_uio *Active*
Bash

 

接下来,将设备 eth1,0000:06:00.0绑定到 igb_uio 驱动。

首先关闭eth1端口

# ifconfig eth1 down
Bash

 

绑定到igb_uio驱动。

/root/dpdk-19.11/usertools/dpdk-devbind.py --bind=igb_uio 06:00.0
Bash

 

也可以使用设备名:

/root/dpdk-19.11/usertools/dpdk-devbind.py --bind=igb_uio eth1
Bash

 

确认是否正常绑定。

# /root/dpdk-19.11/usertools/dpdk-devbind.py  --status

Network devices using DPDK-compatible driver
============================================
0000:00:06.0 'Elastic Network Adapter (ENA) ec20' drv=igb_uio unused=ena

Network devices using kernel driver
===================================
0000:00:05.0 'Elastic Network Adapter (ENA) ec20' if=eth0 drv=ena unused=igb_uio *Active*
Bash

 

如果需要恢复eth1使用原有的内核ena驱动,类似的简单进行如下操作。

/root/dpdk-19.11/usertools/dpdk-devbind.py --bind=ena 06.00.0
Bash

 

4 测试DPDK环境

请注意前面设置的RTE_SDK和RTE_RARGET环境变量是否正确。编译一个内嵌的helloworld应用来进行测试。

# cd /root/dpdk-19.11examples/helloworld
# make

# ./build/helloworld 
EAL: Detected 8 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: PCI device 0000:00:05.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 1d0f:ec20 net_ena
EAL: PCI device 0000:00:06.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 1d0f:ec20 net_ena

Message from syslogd@ip-172-31-9-193 at Dec 18 07:30:15 ...
kernel:do_IRQ: 6.229 No irq handler for vector
EAL: PCI device 0000:00:07.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 1d0f:ec20 net_ena
hello from core 1
hello from core 2
hello from core 3
hello from core 4
hello from core 5
hello from core 6
hello from core 7
hello from core 0
Bash

 

5 结论

DPDK可以支持AWS ENA和82599网络设备,可以在AWS多种不同的实例上部署DPDK和运行支持DPDK的应用,从而对多种应用场景,比如电信行业虚拟化分组核心等提供更全面的支撑。

另外,DPDK也已经和容器网络有了良好集成,这体现在原生的Docker网络上,也体现在包括kubernetes在内的诸多平台使用的CNI实现中,比如contiv和Vhostuser等都可以支持DPDK。

在后续的文章中,我们会针对AWS上的Docker和容器编排平台使用DPDK来进行进一步说明。

6 参考链接

http://www.dpdk.org

https://github.com/amzn/amzn-drivers/tree/master/userspace/dpdk

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/index.html

https://github.com/DPDK/dpdk

 

本篇作者

赵伟

AWS 解决方案架构师,主要负责合作伙伴架构咨询和方案设计,同时致力于 AWS 云服务在国内的应用及推广。具有丰富的企业IT架构经验。