DPDK(Data Plane Development Kit),即数据平面开发工具包,是一组用于快速数据包处理的数据平面库和网络接口控制器驱动程序,DPDK 为 x86,ARM 和 PowerPC 处理器提供编程框架,可以更快地开发高速数据包网络应用程序。该平台采用 BSD 许可证发布,目前作为 Linux Foundation 下的开源项目进行管理。

在 X86 架构中,处理数据包的传统方式是 CPU 中断方式,即网卡驱动接收到数据包后通过中断通知 CPU 处理,然后由 CPU 拷贝数据并交给协议栈。在数据量大时,这种方式会产生大量 CPU 中断,导致 CPU 无法运行其他程序。而 DPDK 则采用轮询方式实现数据包处理过程:DPDK 重载了网卡驱动,在收到数据包后不中断通知 CPU,而是将数据包通过零拷贝技术存入内存,这时应用层程序就可以通过 DPDK 提供的接口,直接从内存读取数据包。这种处理方式节省了 CPU 中断时间、内存拷贝时间,并向应用层提供了简单易行且高效的数据包处理方式,使得网络应用的开发更加方便。但同时,由于需要重载网卡驱动,因此该开发包目前只能用在部分网络处理芯片的网卡中。

OVS-DPDK 是什么

OVS-DPDK 的数据平面通过 DPDK 快速通道进行处理,DPDK 的 PMD 模型使得数据收发绕过内核协议栈,达到数据包的快速收发。OVS-DPDK 有三层缓存表:Exact Match Cache(EMC)完全匹配缓存,无法通过通配符匹配;tuple space search (TSS) 元组空间搜索,使用 hash 表实现,可以通过通配符匹配; ofproto classifier table,被兼容 openflow 的控制器进行管理。

OVS-DPDK 与物理网卡的连接方式:利用 DPDK 提供的工具,使物理 NIC 直接绑定到 UIO 驱动上面,将物理网卡作为一个 port 绑定到 OVS-DPDK 上。这种情况下物理网卡将对系统不可见,也无法使用系统工具对网卡进行配置。

OVS with DPDK

OVS-DPDK 为容器提供网络支持

基于 DPDK 的应用

DPDK 官网容推荐器内运行 DPDK 应用有的两种模型,分片和汇聚,如下图所示。

use_models_for_running_dpdk_in_containers

图 2 方案可以使用 vswitch 将上层的容器和底层的物理 NIC 解耦,可以通过 OVS-DPDK 实现高性能的容器应用。OVS-DPDK 可以创建 dpdkvhostuser 模式的 DPDK port,将这个 port mount 进容器的 network namespace 可以作为容器的虚拟网络接口,容器里面的 DPDK PMD 程序可以使用经过改进的 virtio-user 连接这个 port,通过 hugepage 内存区域(shared 模式)建立起到 OVS-DPDK 和物理 NIC 的高速数据通道。经过 OVS 流表配置,可以做到东西向和南北向的数据通道加速,整个过程 by-pass 宿主机的内核。

Intel 实现了 vhost-user-net-plugin cni 插件,实现在 OVS-DPDK 上建立 dpdkvhostuser 模式的 DPDK port,这个 port mount 进容器作为容器的虚拟网络接口,供 DPDK 应用使用。其结构如下所示:

vhost user net plugin

DPDK 绕过了 Linux 内核协议栈,加速了数据的处理,用户可以在用户空间定制协议栈,满足自己的应用需求。用户业务可以借助 mTCP、f-stack 等用户态协议栈对业务应用进行改造,优化网络性能,实现高性能的网络应用。

基于内核协议栈的应用

基于内核协议栈的应用容器内部没有 virtio PMD 程序运行,所以只能连接传统的 OVS 端口,主要有两种可以参考的方案:

  • 使用 veth 对连接容器和 ovs,在这种模式下 ovs 将回落到 system datapath(如图1所示);
  • 使用 tap 将数据转发到 ovs-dpdk 的 virtio-user 接口,然后使用 dpdk 进行加速,在 tap 转发到 virtio-user 接口时会造成一次内存拷贝,会造成性能损失(如图2所示)。

两种模式都可以再通过设置 OVS 流表,将这些数据传输到基于 DPDK PMD 的 type=dpdk 的端口,这样可以在数据传输出网卡的后半程使用 DPDK 加速。这种情况下,数据在进入 OVS 端口并进行转发到 OVS DPDK PMD 绑定的 NIC 端口之后,就进入了 DPDK 高速通道。

OVS-DPDK 容器网络方案验证

搭建 DPDK 环境

1)、编译 dpdk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# install build dependencies
yum makecache
sudo yum install -y autoconf automake libtool wget vim gcc    \
    gcc-c++  kernel-devel kernel-headers kernel net-tools     \
    numactl-devel numactl-libs libpcap libpcap-devel pciutils \
    python-six python-twisted-core python-zope-interface      \
    openssl-devel openssl-libs

# download dpdk 17.11.3
wget http://fast.dpdk.org/rel/dpdk-17.11.3.tar.xz
tar -xvf dpdk-17.11.3.tar.xz

# build dpdk
export DPDK_DIR=/root/ovs-dpdk/dpdk-stable-17.11.3
export DPDK_TARGET=x86_64-native-linuxapp-gcc
export DPDK_BUILD=$DPDK_DIR/$DPDK_TARGET
export RTE_SDK=$DPDK_DIR
export RTE_TARGET=x86_64-native-linuxapp-gcc
make install T=$DPDK_TARGET DESTDIR=install RTE_SDK=`pwd`

2)、配置 dpdk 环境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 加载 igb_uio 模块
modprobe uio_pci_generic
modprobe uio
modprobe vfio-pci
insmod build/kmod/igb_uio.ko

# 在 grub.cfg 中添加参数分配1G大页
default_hugepagesz=1GB hugepagesz=1G hugepages=4

# 在 grub.cfg 中添加参数启用 iommu
iommu=pt intel_iommu=on 

查看系统的大页支持情况:

3)、绑定网卡,并测试 dpdk 环境是否 ok

查看当前的网卡驱动绑定状态:

1
 ./usertools/dpdk-devbind.py --status

绑定网卡到 igb_uio 驱动:

1
2
3
4
ip link set ens4 down
ip link set ens9 down
./usertools/dpdk-devbind.py --bind=igb_uio ens4
./usertools/dpdk-devbind.py --bind=igb_uio ens9

运行 testpmd 程序进行测试,可以看到数据包在两个接口间的转发情况,表示 dpdk 环境安装ok:

1
./x86_64-native-linuxapp-gcc/app/testpmd -- -i

编译运行 OVS-DPDK

1)、编译 OVS-DPDK (注意 ovs 和 dpdk 的版本是否匹配,参考官网 FAQ)

1
2
3
4
5
6
7
8
9
# 下载 openvswitch 2.9.2 源码
wget http://openvswitch.org/releases/openvswitch-2.9.2.tar.gz

# build and install
LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 ./boot.sh
./configure --with-dpdk=$DPDK_BUILD         \
            --prefix=/usr --sysconfdir=/etc \
            --localstatedir=/var
make && make install

2)、配置 ovs-dpdk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 启动 ovs-dpdk
/usr/share/openvswitch/scripts/ovs-ctl start

# 配置 ovs-dpdk
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem="1024,0"
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0x2

# 创建一个支持 dpdk 的网桥
ovs-vsctl add-br ovs-br0 -- set bridge ovs-br0 datapath_type=netdev

# 创建 dpdk 接口并绑定到物理网卡
ovs-vsctl add-port ovs-br0 dpdkprot01 -- set Interface dpdkprot01 type=dpdk options:dpdk-devargs=0000:00:08.0

# 创建 dpdk vhost-user 接口
ovs-vsctl add-port ovs-br0 vhost-user0 -- set Interface vhost-user0 type=dpdkvhostuser
ovs-vsctl add-port ovs-br0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser

# 创建流表
ovs-ofctl add-flow ovs-br0 in_port=1,idle_timeout=0,action=output:2
ovs-ofctl add-flow ovs-br0 in_port=2,idle_timeout=0,action=output:1

DPDK 应用的容器

实验环境:

  • 支持 dpdk 的网桥 ovs-br0
  • 4个 dpdk vhost-user 接口
  • pktgen 容器(用于生成数据包)和 testpmd 容器(流量转发)
  • 配置流表 2<–>3 1<–>4

连接情况如下:

1)、构建 pktgen 和 testpmd 镜像

pkktgen Dockerfile 如下:

1
2
3
4
FROM centos:latest
RUN yum update -y && yum install -y iproute numactl-libs numactl libpcap pciutils
COPY ./pktgen-3.5.0/ /root/pktgen-3.5.0
WORKDIR /root/pktgen-3.5.0

testpmd Dockerfile 如下:

1
2
3
4
FROM centos:latest
RUN yum update -y && yum install -y iproute numactl-libs numactl libpcap pciutils
COPY ./dpdk-stable-17.11.3/ /root/dpdk-stable-17.11.3/
WORKDIR /root/dpdk-stable-17.11.3

2)、运行并设置 ovs-dpdk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 启动 ovs-dpdk
/usr/share/openvswitch/scripts/ovs-ctl start

# 配置 ovs-dpdk
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem="1024,0"

# 创建一个支持 dpdk 的网桥
ovs-vsctl add-br ovs-br0 -- set bridge ovs-br0 datapath_type=netdev

# 创建 dpdk 接口并绑定到物理网卡
ovs-vsctl add-port ovs-br0 dpdkprot01 -- set Interface dpdkprot01 type=dpdk options:dpdk-devargs=0000:00:08.0

# 创建 dpdk vhost-user 接口
ovs-vsctl add-port ovs-br0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
ovs-vsctl add-port ovs-br0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
ovs-vsctl add-port ovs-br0 vhost-user3 -- set Interface vhost-user3 type=dpdkvhostuser
ovs-vsctl add-port ovs-br0 vhost-user4 -- set Interface vhost-user4 type=dpdkvhostuser

# 创建流表
ovs-ofctl del-flows ovs-br0  # 删除当前流表
ovs-ofctl add-flow ovs-br0 in_port=1,idle_timeout=0,action=output:4
ovs-ofctl add-flow ovs-br0 in_port=4,idle_timeout=0,action=output:1
ovs-ofctl add-flow ovs-br0 in_port=2,idle_timeout=0,action=output:3
ovs-ofctl add-flow ovs-br0 in_port=3,idle_timeout=0,action=output:2

系统状态如下:

3)、创建 pktgen 容器和 testpmd 容器,运行测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 运行 pktgen 容器
docker run -i -t --rm --privileged   \
    -v /dev/hugepages:/dev/hugepages \
    -v /var/run/openvswitch:/var/run/openvswitch pktgen-docker

# 在容器内运行 pktgen 程序,连接 vhost-user1 vhost-user2
./app/x86_64-native-linuxapp-gcc/pktgen             \
    --socket-mem 1024 --file-prefix pktgen --no-pci \
    --vdev 'net_virtio_user1,mac=00:00:00:00:00:05,path=/var/run/openvswitch/vhost-user1' \
    --vdev 'net_virtio_user2,mac=00:00:00:00:00:01,path=/var/run/openvswitch/vhost-user2' \
    -- -T -P -m "2.0,2.1"

# 运行 dpdk 容器
docker run -i -t --rm --privileged     \
    -v /dev/hugepages:/dev/hugepages   \
    -v /var/run/openvswitch:/var/run/openvswitch dpdk-docker

# 在容器中运行 testpmd 程序,连接 vhost-user3 vhost-user4
./x86_64-native-linuxapp-gcc/app/testpmd   \
    --socket-mem 1024 --file-prefix testpmd-docker --no-pci \
    --vdev 'virtio_user3,mac=00:00:00:00:00:02,path=/var/run/openvswitch/vhost-user3' \ 
    --vdev 'virtio_user4,mac=00:00:00:00:00:03,path=/var/run/openvswitch/vhost-user4' \
    -- -i --burst=64 --disable-hw-vlan --rxq=1 --txq=1 --rxd=256 --txd=256 -a

在 pktgen 应用中设置发送到数据包数量,发送频率:

1
2
3
4
set all rate 10   
set 0 count 200   # 端口0发送200个包
set 1 count 100   # 端口1发送100个包
str               # 启动发包

我们可以在 testpmd 中看到数据包的状态,使用命令 show port stats all 可以看到接口转发的包情况。结果如下图所示:

在 pktgen 中可以看到数据包发送接收情况,结果如下图:

也可以从 ovs 的接口中读到数据包的转发情况:

基于内核协议栈的应用(使用 veth pair 连接 ovs-dpdk)

实验环境:

  • 支持 dpdk 的网桥 ovs-br0
  • 使用 ovs-docker 为容器添加网络
  • 两个 centos 容器(互相 ping 测试)
  • 配置流表

1)、创建两个 centos 容器

1
docker run -i -t --rm --network=none centos

2)、使用 ovs-docker 配置容器网络

1
2
ovs-docker add-port ovs-br0 eth1 c13983ca7a73 --ipaddress=192.168.99.100/24 --gateway=192.168.99.1 --macaddress="a2:c3:0d:49:7f:f8" --mtu=1450
ovs-docker add-port ovs-br0 eth1 31ad2f628b48 --ipaddress=192.168.99.101/24 --gateway=192.168.99.1 --macaddress="a2:c3:0d:49:7f:f9" --mtu=1450

从 ovs-br0 上可以看到多了两个接口:

为这两个接口添加流表:

1
2
ovs-ofctl add-flow ovs-br0 in_port=6,idle_timeout=0,action=output:5
ovs-ofctl add-flow ovs-br0 in_port=5,idle_timeout=0,action=output:6

3)、测试容器网络

根据设置的 ip,让两个容器互相 ping ,观察结果如下,两个容器可以互相 ping 通:

4)、分析 ovs-docker 命令

ovs-docker 命令是 ovs 官方提供的为容器添加网络支持的 python 脚本,其中添加端口的流程如下:

  • 创建 nate namespace
  • 创建 veth 对
  • 将 veth 对的一端添加到 ovs 上
  • 将 veth 对的另一端加入到 net namespace 中,设置网卡名,ip,网关等信息

基于内核协议栈的应用(使用 tap 连接 ovs-dpdk 的 virtio-user 接口)

实验环境:

  • 支持 dpdk 的网桥 ovs-br0
  • 使用 ovs-docker 为容器网络
  • 两个 centos 容器(互相 ping 测试)
  • 配置流表
1
2
3
4
5
6
7
8
9
# 加载 vhost 模块
modprobe vhost
modprobe vhost_net

# container----tap----vhost-net----ovs
ovs-vsctl add-port ovs-br0 virtiouser0 -- set Interface virtiouser0 type=dpdk options:dpdk-devargs=virtio_user0,path=/dev/vhost-net,iface=virtiouser0

# container----tap----tap pmd----ovs
ovs-vsctl add-port ovs-br0 tap01 -- set Interface tap01 type=dpdk options:dpdk-devargs=net_tap01,path=/dev/vhost-net,iface=tap01

厂商方案

美团点评容器平台网络架构

数据平面: 采用万兆网卡,结合 OVS-DPDK 方案,并进一步优化单流的转发性能,将几个 CPU 核绑定给 OVS-DPDK 转发使用,只需要少量的计算资源即可提供万兆的数据转发能力。OVS-DPDK 和容器所使用的 CPU 完全隔离,因此也不影响用户的计算资源。

控制平面: 我们使用 OVS 方案。该方案是在每个宿主机上部署一个自研的软件 Controller,动态接收网络服务下发的网络规则,并将规则进一步下发至OVS流表,决定是否对某网络流放行。

2017年总结优化:

  • SRIOV 方式优化 local 性能。KNI 和 TAP 是常用的 DPDK 网络设备和内核数据交互的方案,为了提升 Local port 的性能,满足一些宿主机特殊场景。美团云将 SRIOV 和 BOND 结合,优化 OVS-DPDK bridge 的 local port 性能,现在 Local 可以跑满万兆。
  • OVS-DPDK 进程重启时间长,一直是存在的一个重要问题。美团云在今年上半年,重点解决了这个问题。这里采用的方案主要是双进程模式,以及 dpdk vhost 后端双进程模式,同时也对 restore flow 时间做了优化。60+VM,10G,2000 条流表的情况下,原生 OVS-DPDK 需要 2min+ 时间,经过改造后,可以达到 1s 以内。达到了 OVS-DPDK 进程平滑热升级,以及故障快速自恢复的功能。
  • 跨 OVS-kernel 和 OVS-DPDK 热迁移。这里主要需要解决 OVS-DPDK 对 TSO、UFO 等 feature 支持, 因为原 OVS-kernel 的 VM 是默认打开这些 feature 的。而且需要解决 VM->VM, VM-NIC, VM-LOCAL, LOCAL->NIC 等多个路径的 offload 操作。

网易

网易的 ovs-dpdk 使用方案未知,网易的容器主要运行在虚拟机中,ovs-dpdk 的使用方案可能会是 ovs 端创建 dpdkvhostuser 接口,虚拟机启动会通过 virtio 驱动加载出 dpdk 的网卡,以此提供网络加速。 网易使用 OVS+VXLAN 方案。 网络层面:基于 DPDK/SR-IOV 实现高速网络包转发,网络包处理能力接近150W; DDoS 攻击防护:基于 Intel DPDK 技术实现高性能实时抗攻击

Intel

Intel Developer Zone 上一篇文章使用 ovs-dpdk 为 Docker 提供网络,其使用的方案为 veth 对直接连接容器和 ovs,文章地址为: Using Docker* Containers with Open vSwitch* and DPDK on Ubuntu* 17.10 | Intel® Software

intel 在 2017 年 dpdk summit 上对 dpdk 提供容器网络加速,对于 tcp/ip 协议栈的应用提出了下面三种模型

1、 使用 tap 将数据转发到 ovs-dpdk 中处理数据。

2、VF 作为容器网卡,然后在通过 dpdk 连接的 VF 接口转发到 ovs 处理,整个过程会 by-pass 内核,但其会受到物理网卡的限制。

3、容器跑在虚拟机中

参考文章