DPVS-1.9.0-with-DPDK-20.11.1编译安装文档


准备工作

BIOS配置

参考服务器用户手册

关闭超线程

Chipset – Processor Configuration – Hyper Threading Technology – Disabled

开启 NUMA

Chipset – Common Configuration – NUMA – Enabled

系统环境

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

[root@localhost ~]# uname -r
4.4.215-1.el7.elrepo.x86_64

关于内核:

已测试如下版本会出现编译不过的情况

  • 5.4.103-1
  • 4.9.9-1
  • 3.10.0-957
  • 只留一种网卡管理

    避免IP管理冲突,导致后续IP配置丢失。

    例如关闭 NetworkManager ,只保留 network .

    systemctl status NetworkManager
    systemctl stop NetworkManager
    systemctl disable NetworkManager

硬件参数

# 确认 硬件/网卡 型号
[root@localhost ~]# lspci |grep 'Ethernet'
01:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
01:00.1 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
06:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
06:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)


# 确认固件版本
[root@localhost ~]# ethtool -i eno5
driver: ixgbe
version: 5.1.0-k
firmware-version: 0x800003df
expansion-rom-version:
bus-info: 0000:06:00.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: yes


# 确认接线、速率情况
[root@localhost ~]# ethtool eno5
Settings for eno5:
	Supported ports: [ FIBRE ]
	Supported link modes:   10000baseT/Full
	Supported pause frame use: No
	Supports auto-negotiation: No
	Supported FEC modes: Not reported
	Advertised link modes:  10000baseT/Full
	Advertised pause frame use: No
	Advertised auto-negotiation: No
	Advertised FEC modes: Not reported
	Speed: 10000Mb/s
	Duplex: Full
	Port: FIBRE
	PHYAD: 0
	Transceiver: external
	Auto-negotiation: off
	Supports Wake-on: d
	Wake-on: d
	Current message level: 0x00000007 (7)
			       drv probe link
	Link detected: yes

https://core.dpdk.org/supported/

依赖安装

yum install -y dnf

dnf groupinstall -y "Development Tools"

yum -y install m4 gcc redhat-rpm-config xmlto asciidoc elfutils-libelf-devel binutils-devel newt-devel perl-ExtUtils-Embed hmaccalc rng-tools python3-devel libfdt-devel libbsd-devel libfdt

yum install -y xmlto gcc-c++ rpm-build patchutils asciidoc elfutils-libelf-devel zlib-devel binutils-devel newt-devel python-devel hmaccalc perl rng-tools lrzsz openssl-devel popt-devel numactl-devel libbsd-devel

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py  

python3 get-pip.py

pip3 install meson ninja
pip3 install pyelftools
  • pkg-config 源码安装

yum 安装只能到 0.27.1

Linux 系统下,可以到 pkg-config 官网 下载最新安装包
本次安装是2017年3月20日发布的0.29.2,

wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz
tar -zxvf pkg-config-0.29.2.tar.gz 
cd pkg-config-0.29.2/

./configure --with-internal-glib
make
make install

# 检查版本
pkg-config --version
0.29.2

DPDK 环境文档


编译安装

编译安装DPDK

下载 DPVS 及 DPDK 源码

根据官方推荐下载当前可支持的 dpdk stable 版本。

  • DPVS-1.9.0 – DPDK-stable-20.11.1
  • DPVS-1.8.10 – DPDK-stable-18.11.2

本次以安装 master 版本为例。

# 建议直接在目标目录下载所需源码,以免编译后路径变动
cd /usr/local

# 最新版则用 git clone,本次拉取时 master 版本为 1.9.0
git clone https://github.com/iqiyi/dpvs.git
cd dpvs

# 下载当前版本匹配的 DPDK 源码,20.11.1
# 官网下载链接:http://core.dpdk.org/download/ 【 以免链接失效 】
wget https://fast.dpdk.org/rel/dpdk-20.11.1.tar.xz
tar xf dpdk-20.11.1.tar.xz


---
# 如需安装旧版 DPVS ,可参考如下方式下载

# 指定版本可下载 release
wget https://github.com/iqiyi/dpvs/archive/refs/tags/v1.8.10.tar.gz
tar zxvf v1.8.10.tar.gz
cd dpvs-1.8.10

# 下载 DPDK-18.11.2
wget https://fast.dpdk.org/rel/dpdk-18.11.2.tar.xz
tar xf dpdk-18.11.2.tar.xz

加载 DPDK-Patch

# 把 DPVS 提供的所有 patch 复制到 DPDK 的根目录下面
cp patch/dpdk-stable-20.11.1/* dpdk-stable-20.11.1/

# 加载所有补丁
cd dpdk-stable-20.11.1/
patch -p 1 < 0001-kni-use-netlink-event-for-multicast-driver-part.patch
patch -p 1 < 0002-pdump-change-dpdk-pdump-tool-for-dpvs.patch
patch -p 1 < 0003-debug-enable-dpdk-eal-memory-debug.patch
patch -p 1 < 0004-ixgbe_flow-patch-ixgbe-fdir-rte_flow-for-dpvs.patch
patch -p 1 < 0005-bonding-allow-slaves-from-different-numa-nodes.patch
patch -p 1 < 0006-bonding-fix-bonding-mode-4-problems.patch

编译安装

  • 利用 meson 生成构建文件,输出至 dpdkbuild

    mkdir dpdklib
    mkdir dpdkbuild
    # meson -Dprefix 需要填写 dpdklib 目录的绝对路径
    meson -Denable_kmods=true -Dprefix=/usr/local/dpvs/dpdk-stable-20.11.1/dpdklib dpdkbuild
  • 输出:

    Message:
    =================
    Content Skipped
    =================
    
    libs:
    
    drivers:
    common/mvep:	missing dependency, "libmusdk"
    common/mlx5:	missing dependency, "mlx5"
    net/af_xdp:	missing dependency, "libbpf"
    net/mlx4:	missing dependency, "mlx4"
    net/mlx5:	missing internal dependency, "common_mlx5"
    net/mvneta:	missing dependency, "libmusdk"
    net/mvpp2:	missing dependency, "libmusdk"
    net/nfb:	missing dependency, "libnfb"
    net/szedata2:	missing dependency, "libsze2"
    crypto/aesni_gcm:	missing dependency, "libIPSec_MB"
    crypto/aesni_mb:	missing dependency, "libIPSec_MB"
    crypto/armv8:	missing dependency, "libAArch64crypto"
    crypto/kasumi:	missing dependency, "libIPSec_MB"
    crypto/mvsam:	missing dependency, "libmusdk"
    crypto/snow3g:	missing dependency, "libIPSec_MB"
    crypto/zuc:	missing dependency, "libIPSec_MB"
    compress/isal:	missing dependency, "libisal"
    regex/mlx5:	missing internal dependency, "common_mlx5"
    vdpa/mlx5:	missing internal dependency, "common_mlx5"
    
    
    Build targets in project: 1024
    
    Found ninja-1.10.2.git.kitware.jobserver-1 at /usr/local/bin/ninja

继续使用 ninja 编译与安装

  • 执行编译

    ninja -C dpdkbuild
  • 输出:

    ninja: Entering directory `dpdkbuild'
    [2434/2463] Generating rte_kni with a custom command
    make: Entering directory `/usr/src/kernels/4.4.215-1.el7.elrepo.x86_64'
      CC [M]  /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/kni_net.o
      CC [M]  /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/kni_misc.o
      LD [M]  /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/rte_kni.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/rte_kni.mod.o
      LD [M]  /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/rte_kni.ko
    make: Leaving directory `/usr/src/kernels/4.4.215-1.el7.elrepo.x86_64'
    [2463/2463] Linking target app/dpdk-test-bbdev
  • 安装

    cd dpdkbuild && ninja install

编译安装DPVS

声明环境变量

注意对应环境路径。

# 注意路径
export PKG_CONFIG_PATH=/usr/local/dpvs/dpdk-stable-20.11.1/dpdklib/lib64/pkgconfig:/usr/lib64/pkgconfig
export LIBDPDKPC_PATH=/usr/local/dpvs/dpdk-stable-20.11.1/dpdklib/lib64/pkgconfig/libdpdk.pc

编译安装

cd /usr/local/dpvs

make -j 20
make install -j 20

注意环境依赖

  1. Build dependencies may be needed, such as pkg-config(version 0.29.2+),automake, libnl3, libnl-genl-3.0, openssl, popt and numactl. You can install the missing dependencies by using the package manager of the system, e.g., yum install popt-devel (CentOS).
  2. Early pkg-config versions (v0.29.2 before) may cause dpvs build failure. If so, please upgrade this tool.

验证 Keepalived 支持功能

注意查看 make 过程中的提示信息,尤其是 keepalived 部分,如果出现下面的部分则表示 IPVS 支持 IPv6 .

Keepalived configuration
------------------------
Keepalived version       : 2.0.19
Compiler                 : gcc
Preprocessor flags       : -D_GNU_SOURCE -I/usr/include/libnl3
Compiler flags           : -g -g -O2 -fPIE -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -O2
Linker flags             : -pie -Wl,-z,relro -Wl,-z,now
Extra Lib                : -lm -lcrypto -lssl -L/usr/lib64 -lnl-genl-3 -lnl-3
Use IPVS Framework       : Yes
IPVS use libnl           : Yes
IPVS syncd attributes    : No
IPVS 64 bit stats        : No
HTTP_GET regex support   : No
fwmark socket support    : Yes
Use VRRP Framework       : Yes
Use VRRP VMAC            : Yes
Use VRRP authentication  : Yes
With ip rules/routes     : Yes
With track_process       : Yes
With linkbeat            : Yes
Use BFD Framework        : No
SNMP vrrp support        : No
SNMP checker support     : No
SNMP RFCv2 support       : No
SNMP RFCv3 support       : No
DBUS support             : No
SHA1 support             : No
Use JSON output          : No
libnl version            : 3
Use IPv4 devconf         : No
Use iptables             : Yes
Use libiptc              : No
Use libipset             : No
Use nftables             : No
init type                : systemd
Strict config checks     : No
Build genhash            : Yes
Build documentation      : No

验证安装结果

# 查看 bin 目录二进制文件
ls ./bin/

>>>
dpip  dpvs  ipvsadm  keepalived
  • dpvs 是主程序
  • dpip 辅助工具,用来设置IP、路由、vlan等等
  • ipvsadmkeepalived 来自定制(爱奇艺)修改过的 LVS 组件
  • 检查主程序版本

    ./bin/dpvs -v
    
    
    >>>
    dpvs version: 1.9-0, build on 2021.12.19.23:31:03

启动前配置

配置hugepage

和其他的一般程序不同,DPVS 使用的 DPDK 并不是从操作系统中索要内存,而是直接使用大页内存(hugepage),极大地提高了内存分配的效率。

hugepage 的配置比较简单,官方的配置过程中使用的是2MB的大页内存,这里的 4096 指的是分配了 4096 个 2MB 的大页内存,也就是一个 node ( 即一个物理CPU ) 对应 8GB 的内存,如果有两个 node ,则一共分配了 16GB 的内存,这里的内存可以根据机器的大小来自行调整。但是如果小于 1GB 可能会导致启动报错。

单个CPU的系统可以参考dpdk的官方文档

# for NUMA machine,根据 CPU 个数而定
echo 4096 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
# 没有第二颗物理CPU,则无需执行该项
echo 4096 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

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

# 需要开机自动挂载的话可以在
echo "nodev /mnt/huge hugetlbfs defaults 0 0" >> /etc/fstab
  • 配置完成后我们可以看到内存的使用率立马上升了
    # 配置前
    free -g
                  total        used        free      shared  buff/cache   available
    Mem:             15          15           0           0           0           0
    Swap:            15           0          15
    
    ---
    
    # 配置后
    free -g
                  total        used        free      shared  buff/cache   available
    Mem:             15           8           5           0           1           6
    Swap:            15           0          15
  • 使用 numactl 查看内存状态也可以看到确实是单颗CPU分配了8G
numactl -H

>>>
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
node 0 size: 15903 MB
node 0 free: 5813 MB
node distances:
node   0
  0:  10
  • 如果有两颗CPU,也可以看到内存各分配了56G

    单边分配 28672 个 2M 内存.

    numactl -H
    
    >>>
    available: 2 nodes (0-1)
    node 0 cpus: 0 2 4 6 8 10 12 14 16 18
    node 0 size: 64184 MB
    node 0 free: 4687 MB
    node 1 cpus: 1 3 5 7 9 11 13 15 17 19
    node 1 size: 64494 MB
    node 1 free: 5759 MB
    node distances:
    node   0   1
    0:  10  21
    1:  21  10

配置ulimit

默认情况下系统的 ulimit 限制打开的文件描述符数量如果太小会影响 DPVS 正常运行,因此我们将其调大一些:

ulimit -n 655350
echo "ulimit -n 655350" >> /etc/rc.local
chmod a+x /etc/rc.local

挂载驱动模块

首先我们需要让系统挂载我们已经编译好的dpdk驱动(PMD驱动),然后再将网卡使用的默认驱动换为我们这里编译好的PMD驱动

modprobe uio_pci_generic

cd /usr/local/dpvs/dpdk-stable-20.11.1
insmod dpdkbuild/kernel/linux/kni/rte_kni.ko carrier=on

# 查看模块是否已加载
lsmod | grep "rte_kni"

需要注意的是 carrier 参数是从 DPDK v18.11 版本开始新增的,默认值为 off

我们需要在加载 rte_kni.ko 模块的时候带上 carrier=on 参数才能够使 KNI 设备工作正常。

挂载网卡驱动

dpdk-stable-20.11.1/usertools 目录下有一些辅助我们安装使用 DPDK 的脚本,我们可以用它们来降低配置的复杂度,这里我们可以使用 dpdk-devbind.py 脚本来变更网卡的驱动。

获取网卡编号

/usr/local/dpvs/dpdk-stable-20.11.1/usertools/dpdk-devbind.py --status


>>>
Network devices using kernel driver
===================================
0000:01:00.0 'I350 Gigabit Network Connection 1521' if=eno1 drv=igb unused=uio_pci_generic
0000:01:00.1 'I350 Gigabit Network Connection 1521' if=eno2 drv=igb unused=uio_pci_generic
0000:06:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eno5 drv=ixgbe unused=uio_pci_generic *Active*
0000:06:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eno6 drv=ixgbe unused=uio_pci_generic

得出本次两张网卡 eno5 、eno6 的网卡编号 ( PCI ID ) 分别为 0000:06:00.00000:06:00.1

注意:

接下来操作可能会导致连接断开,建议使用其它管理网口 、 BMC 或者连接显示器以防连接中断。

关闭网卡,并加载 DPDK 驱动

线上可以使用 && 命令连续执行,或写成启动脚本。

ifconfig eno5 down
/usr/local/dpvs/dpdk-stable-20.11.1/usertools/dpdk-devbind.py -b uio_pci_generic 0000:06:00.0

ifconfig eno6 down
/usr/local/dpvs/dpdk-stable-20.11.1/usertools/dpdk-devbind.py -b uio_pci_generic 0000:06:00.1

操作前,均为 内核 驱动

加载后,出现如下状态,则表明已成功加载 DPDK 驱动


启动DPVS

创建默认配置

cp conf/dpvs.conf.single-nic.sample /etc/dpvs.conf

开启 DEBUG 等级日志

vim /etc/dpvs.conf

! global config
global_defs {
    log_level   DEBUG
    log_file    /var/log/dpvs.log
    ! 是否异步输出,按需开启
    ! log_async_mode    on
    ! pdump       off
}

禁用 IPV6 功能 ( 使用 ! 符号进行注释 )

! dpvs ipv6 config
!ipv6_defs {
!    disable                     on
!    forwarding                  off
!    route6 {
!        <init> method           hlist
!        recycle_time            10
!    }
!}
  • 启动成功
  • ip a 命令检查网卡,原来 eno5 、eno6 网卡变为 dpdk0.kni 、dpdk1.kni

  • 关于启动后末段输出

    • 因为 /etc/dpvs.conf 默认配置只启用了 8 个核心,所以序号 9、10、11 并未分配负载任务

    • 启动最后出现 rss_hf ( RSS网卡多队列 ) 、tx_offload 配置不可用问题


IP配置

以下内容主要以设置内网卡 dpdk0.kni 为例。

双网卡设置同理,区分只在路由。

设置DPIP

因为 DPVS 托管网络协议栈,所以IP、路由需要使用自带工具进行配置

如果只配置 DPIP ,则只能处理、响应 ICMP 基础网络包,无法实现SSH、Nginx等数据包处理。

  • IP、路由配置
# 启用 dpdk0.kni 网卡
ip link set dpdk0.kni up
# 添加内网卡IP
/usr/local/dpvs/bin/dpip addr add 192.168.7.82/24 dev dpdk0
# 添加默认路由
/usr/local/dpvs/bin/dpip route add default via 192.168.7.20 dev dpdk0


# 如需指定路由
/usr/local/dpvs/bin/dpip route add 192.168.0.0/16 via 192.168.7.20 dev dpdk0
  • 检查配置

    # 检查网卡IP
    /usr/local/dpvs/bin/dpip addr show
    
    >>>
    inet 192.168.7.82/24 scope global dpdk0
         valid_lft forever preferred_lft forever
         
         
    --- 
    
    
    # 检查路由
    /usr/local/dpvs/bin/dpip route show
    
    >>>
    inet 192.168.7.82/32 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope host metric 0 proto auto
    inet 192.168.7.0/24 via 0.0.0.0 src 192.168.7.82 dev dpdk0 mtu 1500 tos 0 scope link metric 0 proto auto
    inet 0.0.0.0/0 via 192.168.7.20 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope global metric 0 proto auto

设置本机IP

用户态数据包仍需在系统层配置对应IP、路由才可实现应用层数据流转。

此处主要配置管理IP,用于内网连接管理、内网VIP、与交换机建立OSPF

这里仍旧使用系统原生 iproute 命令即可管理

用户态的IP配置与DPVS侧的可以重复,不会影响

  • 配置IP地址

    ip addr add 192.168.7.82/24 dev dpdk0.kni
  • 配置路由

    route add default gw 192.168.7.20 dev dpdk0.kni

访问验证

服务端启动简单HTTP服务

python -m SimpleHTTPServer 80

客户端验证网络可达。

至此,已完成 DPVS 的编译、安装及运行。


后记

启动脚本

mkdir /etc/keepalived && cd /etc/keepalived
  • netstartup.sh

    #!/bin/bash
    
    StartupLog="/etc/keepalived/logs/network_startup.log"
    DPDKDevBind="/usr/local/dpvs/dpdk-stable-20.11.1/usertools/dpdk-devbind.py"
    
    # 加载网卡uio_pci_generic驱动
    modprobe uio_pci_generic
    # 加入rte_kni.ko驱动模块
    insmod /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/rte_kni.ko carrier=on
    
    # 将网卡切换为kni
    ifconfig eno5 down
    eth0_bus_info=`ethtool -i eno5 | grep "bus-info" | awk '{print $2}'`
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] eth0_bus_info: ${eth0_bus_info}" >> $StartupLog
    ${DPDKDevBind} -b uio_pci_generic ${eth0_bus_info}
    
    ifconfig eno6 down
    eth1_bus_info=`ethtool -i eno6 | grep "bus-info" | awk '{print $2}'`
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] eth1_bus_info: ${eth1_bus_info}" >> $StartupLog
    ${DPDKDevBind} -b uio_pci_generic ${eth1_bus_info}
    
    # 启动内网卡
    ip link set dpdk0.kni up
    # 配置内网IP
    /usr/local/dpvs/bin/dpip addr add 192.168.7.82/24 dev dpdk0
    /usr/local/dpvs/bin/dpip addr add 192.168.7.182/32 dev dpdk0
    
    # 配置内网路由
    /usr/local/dpvs/bin/dpip route add 192.168.0.0/16 via 192.168.7.20 dev dpdk0
    /usr/local/dpvs/bin/dpip route add 172.16.0.0/12 via 192.168.7.20 dev dpdk0
    /usr/local/dpvs/bin/dpip route add 10.0.0.0/8 via 192.168.7.20 dev dpdk0
    
    # 启动外网卡
    ip link set dpdk1.kni up
    # 配置OSPF内部IP,用于与上层交换机建立OSPF
    /usr/local/dpvs/bin/dpip addr add 10.20.30.45/24 dev dpdk1
    /usr/local/dpvs/bin/dpip route add default via 10.20.30.1 dev dpdk1
    # 配置外网地址,承接外网流量
    /usr/local/dpvs/bin/dpip addr add 100.96.122.210/32 dev dpdk1
    
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Network dpip config is done." >> $StartupLog
    
    if [ $? -eq 0 ]
    then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] adding ipv4 config to local network device" >> $StartupLog
        ip addr add 192.168.7.82/24 dev dpdk0.kni
        ip addr add 10.20.30.45/24 dev dpdk1.kni
        # 将需要通信的VIP、外网地址添加至本地LO网卡,只做用户态数据包转换,实际数据包已由DPIP指定配置
        # 如果添加至dpdk1,会有异常报文上报交换机,但不会有影响
        ip addr add 192.168.7.182/32 dev lo
        ip addr add 100.96.122.210/32 dev lo
        route add -net 192.168.0.0/16 gw 192.168.7.20 dev dpdk0.kni
        route add -net 172.16.0.0/12 gw 192.168.7.20 dev dpdk0.kni
        route add -net 10.0.0.0/8 gw 192.168.7.20 dev dpdk0.kni
        route add default gw 10.20.30.1 dev dpdk1.kni
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] done" >> $StartupLog
    fi
  • dpvs_startup.sh

    #!/bin/bash
    
    StartupLog="/etc/keepalived/logs/dpvs_startup.log"
    DPDKDevBind="/usr/local/dpvs/dpdk-stable-20.11.1/usertools/dpdk-devbind.py"
    
    # 启用NUMA巨页内存
    # 视主机NUMA与实际内存而定
    # 此处只有一颗物理CPU,启用4096个2M巨页,共消耗8GB内存
    echo 4096 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
    #echo 4096 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
    
    # 挂载巨页
    mount -t hugetlbfs nodev /mnt/huge
    
    # 加载网卡uio_pci_generic驱动
    modprobe uio_pci_generic
    # 加入rte_kni.ko驱动模块
    insmod /usr/local/dpvs/dpdk-stable-20.11.1/dpdkbuild/kernel/linux/kni/rte_kni.ko carrier=on
    
    # 将网卡切换为kni
    ifconfig eno5 down
    eth0_bus_info=`ethtool -i eno5 | grep "bus-info" | awk '{print $2}'`
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] eth0_bus_info: ${eth0_bus_info}" >> $StartupLog
    ${DPDKDevBind} -b uio_pci_generic ${eth0_bus_info}
    
    ifconfig eno6 down
    eth1_bus_info=`ethtool -i eno6 | grep "bus-info" | awk '{print $2}'`
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] eth1_bus_info: ${eth1_bus_info}" >> $StartupLog
    ${DPDKDevBind} -b uio_pci_generic ${eth1_bus_info}
    
    /usr/local/dpvs/bin/dpvs &
    
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Waiting 10s for dpvs startup..." >> $StartupLog
    
    sleep 10;
    
    # 配置DPVS的IP与路由,必须先将ip配置到dpdk网卡,否则会导致ipv6配置一直下不掉
    ## 启动内网卡
    ip link set dpdk0.kni up
    ## 配置内网IP
    /usr/local/dpvs/bin/dpip addr add 192.168.7.82/24 dev dpdk0
    /usr/local/dpvs/bin/dpip addr add 192.168.7.182/32 dev dpdk0
    ## 配置内网路由
    /usr/local/dpvs/bin/dpip route add 192.168.0.0/16 via 192.168.7.20 dev dpdk0
    /usr/local/dpvs/bin/dpip route add 172.16.0.0/12 via 192.168.7.20 dev dpdk0
    /usr/local/dpvs/bin/dpip route add 10.0.0.0/8 via 192.168.7.20 dev dpdk0
    
    ## 启动外网卡
    ip link set dpdk1.kni up
    ## 配置OSPF内部IP,用于与上层交换机建立OSPF
    /usr/local/dpvs/bin/dpip addr add 10.20.30.45/24 dev dpdk1
    /usr/local/dpvs/bin/dpip route add default via 10.20.30.1 dev dpdk1
    ## 配置外网地址,承接外网流量
    /usr/local/dpvs/bin/dpip addr add 100.96.122.210/32 dev dpdk1
    
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] DPVS ip config has done. Running User-mode IP config..." >> $StartupLog
    
    if [ $? -eq 0 ]
    then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] adding ipv4 config to local network device" >> $StartupLog
        ip addr add 192.168.7.82/24 dev dpdk0.kni
        ip addr add 10.20.30.45/24 dev dpdk1.kni
        # 将需要通信的VIP、外网地址添加至本地LO网卡,只做用户态数据包转换,实际数据包已由DPIP指定配置
        # 如果添加至dpdk1,会有异常报文上报交换机,但不会有影响
        ip addr add 192.168.7.182/32 dev lo
        ip addr add 100.96.122.210/32 dev lo
        route add -net 192.168.0.0/16 gw 192.168.7.20 dev dpdk0.kni
        route add -net 172.16.0.0/12 gw 192.168.7.20 dev dpdk0.kni
        route add -net 10.0.0.0/8 gw 192.168.7.20 dev dpdk0.kni
        route add default gw 10.20.30.1 dev dpdk1.kni
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] done" >> $StartupLog
    fi

关于功耗

得益于 CPU 抢占式亲和性,可以确保进程/线程有足够的时间分片执行,减少服务超时。

但抢占式也带来了 CPU 持续 100% 负载,带来了温度及功耗的提升十分感人,一般情况下并不建议用作家用入口。

网络通信

主机的网络通信除了 DPIP 上需要配置外,仍需系统层配置对应IP、路由才能通信。


可能出现的问题

启动 DPVS 出现线程亲和性问题

报错现场

  • 启动报错
[root@localhost ~]# /usr/local/dpvs/bin/dpvs
fail to set thread affinty: Invalid argument
set_all_thread_affinity failed

查看 dpvs 的 src/main.c 文件可以看到报错位置如下:

...
  for (cid = 0; cid < RTE_MAX_LCORE; cid++)
    CPU_SET(cid, &cpuset);

  s = pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset);
  if (s != 0) {
    errno = s;
    // 抛出报错
    perror("fail to set thread affinty");
    return -1;
  }
...

这个函数的目的是使用 CPU affinity 将当前进程绑定到所有的CPU核心上。

结合函数来看,报错的大致原因是在为每一个 CPU 逻辑核设置亲和性时出现问题。

排查过程

所以接下来要做的是:

  1. 确认当前主机有多少个逻辑核
  2. 确认函数获取的逻辑核数是否与实际一致
  3. 确认函数执行到第几个逻辑核出现问题
  4. 再根据问题寻找详细原因

写一个测试 demo ,检测当前逻辑核数量以及模拟绑核,并返回绑核结果

  • 相关编程 API 介绍

    #include <sched.h>
    #include <pthread.h>
            /* 清空cpu_set_t 亲和力的设置都是通过这个集合实现的 因此要先清空 */
            void CPU_ZERO(cpu_set_t *set);
            void CPU_ZERO_S(size_t setsize, cpu_set_t *set);
            
            /* 将第几个cpu设置到这个set中 */
            void CPU_SET(int cpu, cpu_set_t *set);
            void CPU_SET_S(int cpu, size_t setsize, cpu_set_t *set);
            
            /* 将第几个cpu从这个set中移除 */
            void CPU_CLR(int cpu, cpu_set_t *set);
            void CPU_CLR_S(int cpu, size_t setsize, cpu_set_t *set);
            
            /* 检测这个cpu是否在这个set中 */
            int CPU_ISSET(int cpu, cpu_set_t *set);
            int CPU_ISSET_S(int cpu, size_t setsize, cpu_set_t *set);
            
            /* 返回set中cpu的个数 */
            void CPU_COUNT(cpu_set_t *set);
            void CPU_COUNT_S(size_t setsize, cpu_set_t *set);
     
            /* set设置完成后调用这个函数来将进程和cpu绑定 */
            int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
            /* 获取进程的cpu亲和性 */
            int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
demo1 – 查看和设置进程的CPU亲和性
  • affinitytest.c

    #define _GNU_SOURCE
    #include <sched.h>
    #include <unistd.h> /* sysconf */
    #include <stdlib.h> /* exit */
    #include <stdio.h>
    
    int main(void)
    {
        int i, nrcpus;
        cpu_set_t mask;
        unsigned long bitmask = 0;
        CPU_ZERO(&mask); // 首先对set要初始化
        nrcpus = sysconf(_SC_NPROCESSORS_CONF);// 获取逻辑cpu个数
        printf("nrcpus value: %d\n",nrcpus);
    
         /* 获取当前线程的亲和力set */
        if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1)
        {
            perror("sched_getaffinity");
            exit(EXIT_FAILURE);
        }
    
        for (i = 0; i < nrcpus; i++)
        {
            if (CPU_ISSET(i, &mask)) // 用CPU_ISSET查看每个cpu是否绑定
            {
                bitmask |= (unsigned long)0x01 << i; //如果绑定相应位置1
                printf("processor #%d is set\n", i);
            }
        }
        printf("bitmask = %#lx\n", bitmask);
        exit(EXIT_SUCCESS);
    }

编译demo1

gcc affinitytest.c -o test1
chmod 755 test1

并执行

[root@homelab_inspur_node02 0tmp]# ./test1
nrcpus value: 12
processor #0 is set
processor #1 is set
processor #2 is set
processor #3 is set
processor #4 is set
processor #5 is set
processor #6 is set
processor #7 is set
processor #8 is set
processor #9 is set
processor #10 is set
processor #11 is set
bitmask = 0xfff

可见当前主机逻辑核共用12个,且进程和所有的 CPU 都绑定了亲和性

demo2 – 设置进程亲和性
  • chechcpuaffinity.c
    #define _GNU_SOURCE
    #include <sched.h>
    #include <unistd.h> /* sysconf */
    #include <stdlib.h> /* exit */
    #include <stdio.h>
    int main(void)
    {
        int i, nrcpus;
        cpu_set_t mask;
        unsigned long bitmask = 0;
    
        CPU_ZERO(&mask);
        CPU_SET(0, &mask); // cpu0加入到set中
        CPU_SET(2, &mask); // cpu2加入到set中
    
        /* 通过sched_setaffinity函数,根据cpu_set_t设置亲和性 */
        if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) 
        {   
            perror("sched_setaffinity");
            exit(EXIT_FAILURE);
    }
    
    // 设置完成后查看结果
    CPU_ZERO(&mask);
         /* 获取当前线程的亲和力set */
        if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1)
        {
            perror("sched_getaffinity");
            exit(EXIT_FAILURE);
        }
        nrcpus = sysconf(_SC_NPROCESSORS_CONF);
        for (i = 0; i < nrcpus; i++)
        {
            if (CPU_ISSET(i, &mask)) // 用CPU_ISSET查看每个cpu是否绑定
            {
                bitmask |= (unsigned long)0x01 << i; //如果绑定相应位置1
                printf("processor #%d is set\n", i);
            }
        }
        printf("bitmask = %#lx\n", bitmask);
        exit(EXIT_SUCCESS);
    }

编译 demo2

gcc chechcpuaffinity.c -o test2
chmod 755

执行验证

[root@homelab_inspur_node02 0tmp]# ./test2
processor #0 is set
processor #2 is set
bitmask = 0x5

bitmask 是 0x5 的原因是这个值的第一个比特位和第三个比特位置1,所以值为5

另外 taskset 命名用于获取或者设定 CPU 亲和性,taskset 命令其实就是使用 sched_getaffinity()sched_setaffinity() 接口实现的。

结合源码分析

了解了 CPU affinity 的相关知识,并且确认了本机基本信息后,我们来看一下报错函数 set_all_thread_affinity 的内容,分析一下问题产生的原因。

以下是源码和注释

static int set_all_thread_affinity(void)
{
    int s;
    lcoreid_t cid;
    pthread_t tid;
    cpu_set_t cpuset;
    unsigned long long cpumask=0;
 
    tid = pthread_self(); // 获取进程id
    CPU_ZERO(&cpuset); // 初始化set
    printf("RTE_MAX_LCORE: %d\n", RTE_MAX_LCORE); // 新加的一行,为了打印函数执行时获取的逻辑核个数
    for (cid = 0; cid < RTE_MAX_LCORE; cid++) // 宏定义RTE_MAX_LCOR获取逻辑cpu的个数
        CPU_SET(cid, &cpuset); // 将当进程设置和全部cpu的亲和性
    s = pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset); // 进行设置
    if (s != 0) {
        errno = s;
        perror("fail to set thread affinty"); // 报错
        return -1;
    }
    // 设置完成亲和性后 再读取一下
    CPU_ZERO(&cpuset);
    s = pthread_getaffinity_np(tid, sizeof(cpu_set_t), &cpuset);
    if (s != 0) {
        errno = s;
        perror("fail to get thread affinity");
        return -2;
    }
    for (cid = 0; cid < RTE_MAX_LCORE; cid++) {
        if (CPU_ISSET(cid, &cpuset))
            cpumask |= (1LL << cid);
    }
    printf("current thread affinity is set to %llX\n", cpumask);
    return 0;

定位到报错位置,发现是在设置亲和性的时候报错的,可能是 cpuset 值的问题;

而 cpuset 的值是循环调用 CPU_SET 来赋值的,所以源码中加了一行查看 RTE_MAX_LCORE 的值是多少

经过重新编译、安装后,重新运行 DPVS 时 printf 出来以后竟然是128,即 DPVS 运行时认为本机有128个逻辑核。

但是实际上根据上面的 demo 测试已知本机的逻辑 CPU 只有 12 个,并且使用 sysconf(_SC_NPROCESSORS_CONF) 函数可以获取正确的逻辑 CPU 的个数,于是尝试将 RTE_MAX_LCORE 替换为 sysconf(_SC_NPROCESSORS_CONF)

...
    tid = pthread_self();
    CPU_ZERO(&cpuset);
    // printf("RTE_MAX_LCORE: %d\n", RTE_MAX_LCORE);
    // for (cid = 0; cid < RTE_MAX_LCORE; cid++)
    for (cid = 0; cid < sysconf(_SC_NPROCESSORS_CONF); cid++)
        CPU_SET(cid, &cpuset);

    s = pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset);
    if (s != 0) {
        errno = s;
        perror("fail to set thread affinty");
        return -1;  
...

并重新编译、安装,再次运行已经正常,可跳至结果查看。

  • 如果主机小于 9 个逻辑核,请注意!!

需要注意要调整 /etc/dpvs.conf 文件里的内容,因为从 DPVS 工程里直接复制的配置文件 dpvs.conf.single-nic.sample 中 worker_defs 里一共配置了 9 个 CPU ,而像虚拟机通常较少,比如只有4个,因此只需要保留4个即可。

参考配置如下:

worker_defs {
    <init> worker cpu0 {
        type    master
        cpu_id  0
    }
    <init> worker cpu1 {
        type    slave
        cpu_id  1
        port    dpdk0 {
            rx_queue_ids     0
            tx_queue_ids     0
            ! isol_rx_cpu_ids  9
            ! isol_rxq_ring_sz 1048576
        }
    }
    <init> worker cpu2 {
        type    slave
        cpu_id  2
        port    dpdk0 {
            rx_queue_ids     1
            tx_queue_ids     1
            ! isol_rx_cpu_ids  10
            ! isol_rxq_ring_sz 1048576
        }
    }
    <init> worker cpu3 {
        type        slave
        cpu_id      3
        port        dpdk0 {
            rx_queue_ids     2
            tx_queue_ids     2
            ! isol_rx_cpu_ids  11
            ! isol_rxq_ring_sz 1048576
        }
}
}

DPDK 程序将 CPU 称为 lcore,即逻辑核。分为 master, slave 两种类型,一般 master 做管理相关的,slave cpu 是真正处理业务的核,每个 lcore 可以负责多个网卡的多个队列,但是多个lcore不能负责一个网卡的队列,DPDK 中将网卡叫做 port. rx_queue_ids 和 tx_queue_ids 分别是接收和发送队列号。

其中 isol_rx_cpu_ids 表示当前 lcore 专职负责接收数据,isol_rxq_ring_sz 专职接收数据的 ring buffer 大小。

结果

再次启动发现不会出现 set_all_thread_affinity failed 的错误( 不知道这算不算是一个 DPVS 的 BUG )

启动输出物理 CPU ( NUMA ) 1 枚,逻辑核心 ( LCore ) 数 12个,检测正确,启动正常。

DPVS 启动后出现 rss_hf 、tx_offload 加载报错

port_id 0是一个 ixgbe 驱动的网卡,型号为82599ES。

报错的意思是在调用 rte_eth_dev_configure 函数时,传入的最后一个函数参数(也就是网卡配置)中的 rx_adv_conf.rss_conf.rss_hf 参数值有问题,这个值是无效的。有效值是 0x38d34

找到 DPDK 源码中打印错误信息的位置,前后代码如下:

20.11 与 18.11 并无区别。

约 38399 行

  • dpdk-stable-20.11.1/lib/librte_ethdev/rte_ethdev.c
int
rte_eth_dev_rss_hash_update(uint16_t port_id,
                            struct rte_eth_rss_conf *rss_conf)
{
        struct rte_eth_dev *dev;
        struct rte_eth_dev_info dev_info = { .flow_type_rss_offloads = 0, };
        int ret;

        RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);

        ret = rte_eth_dev_info_get(port_id, &dev_info);
        if (ret != 0)
                return ret;

        rss_conf->rss_hf = rte_eth_rss_hf_refine(rss_conf->rss_hf);

        dev = &rte_eth_devices[port_id];
        if ((dev_info.flow_type_rss_offloads | rss_conf->rss_hf) !=
            dev_info.flow_type_rss_offloads) {
                RTE_ETHDEV_LOG(ERR,
                        "Ethdev port_id=%u invalid rss_hf: 0x%"PRIx64", valid value: 0x%"PRIx64"\n",
                        port_id, rss_conf->rss_hf,
                        dev_info.flow_type_rss_offloads);
                return -EINVAL;
        }
        RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->rss_hash_update, -ENOTSUP);
        return eth_err(port_id, (*dev->dev_ops->rss_hash_update)(dev,
                                                                 rss_conf));
}

dev_info.flow_type_rss_offloads 就是网卡支持的 flow 类型, rss_conf->rss_hf 就是我们调用 rte_eth_dev_configure 函数的最后一个参考中的 rx_adv_conf.rss_conf.rss_hf 的值,

这里的 if 判断意思是配置的 flow 类型必须是网卡支持的 flow 类型,如果配置了网卡不支持的类型,就会报错。

经过计算器算出 0x38d34 的二进制是 111000110100110100 ,再配合在 rte_ethdev.hETH_RSS_开头的宏定义,得出 111000110100110100 就是下面所有宏定义的 或 值:

约 751 行

  • dpdk-stable-20.11.1/lib/librte_ethdev/rte_ethdev.h
/*节选其中*/
#define ETH_RSS_PROTO_MASK ( \
...
ETH_RSS_IPV4 | \
ETH_RSS_NONFRAG_IPV4_TCP| \
ETH_RSS_NONFRAG_IPV4_UDP| \
ETH_RSS_IPV6 | \
ETH_RSS_NONFRAG_IPV6_TCP | \
ETH_RSS_NONFRAG_IPV6_UDP | \
ETH_RSS_IPV6_EX | \
ETH_RSS_IPV6_TCP_EX | \
ETH_RSS_IPV6_UDP_EX
...

而我们配置中的参数 ETH_RSS_PROTO_MASK 显然比上面的类型要多,也就是 ETH_RSS_PROTO_MASK 定义的一些类型,网卡不支持,故而报错。

解决方式

dpdk-stable-20.11.1/lib/librte_ethdev/rte_ethdev.h 定义配置后继续追加如下配置

/* 前序定义,用于定位 */
#define ETH_RSS_PROTO_MASK ( \
...\
)

/* 只需新增该部分 */
#define ETH_RSS_E1000_IGB (\
    ETH_RSS_IPV4 | \
    ETH_RSS_NONFRAG_IPV4_TCP| \
    ETH_RSS_NONFRAG_IPV4_UDP| \
    ETH_RSS_IPV6 | \
    ETH_RSS_NONFRAG_IPV6_TCP | \
    ETH_RSS_NONFRAG_IPV6_UDP | \
    ETH_RSS_IPV6_EX | \
    ETH_RSS_IPV6_TCP_EX | \
    ETH_RSS_IPV6_UDP_EX)
#define ETH_RSS_IXGBE ETH_RSS_E1000_IGB

重新编译 DPDK ,再重新编译 DPVS ,启动后即可消除报错。

DPVS 启动后IP配置丢失,DNS被置空

BMC 下屏幕大小受限,分了三张截图,从左至右。

根因

在CentOS系统上,目前有NetworkManager和network两种网络管理工具。 如果两种都配置会引起冲突,而且NetworkManager在网络断开的时候,会清理路由,如果一些自定义的路由,没有加入到NetworkManager的配置文件中,路由就被清理掉,网络连接后需要自定义添加上去。

解决方式

目前在CentOS上的NetworkManager版本比较低,而且比较适合有桌面环境的系统,所以服务器上保留network服务即可,将NetworkManager关闭,并且禁止开机启动。

以下方式二者选其一操作即可。

  • systemd 管理方式下关闭

    # 查看当前状态
    systemctl status NetworkManager
    systemctl stop NetworkManager
    systemctl disable NetworkManager
    
    >>> 输出
    Removed /etc/systemd/system/multi-user.target.wants/NetworkManager.service.
    Removed /etc/systemd/system/dbus-org.freedesktop.NetworkManager.service.
    Removed /etc/systemd/system/dbus-org.freedesktop.nm-dispatcher.service.
    
    # 查看是否禁用
    systemctl is-enabled NetworkManager 

    可以看到关联的几个服务一起被禁用了。如果使用桌面的话,会发现网络管理的图标不见了。

  • service 管理方式下关闭

    service NetworkManager stop
    chkconfig NetworkManger off

参考文档

源码安装 pkg-config

DPDK快速编译

在centos7虚拟机上运行DPVS发生的CPU亲和性设置问题

dpdk-18.11网卡多队列RSS设置

CentOS - network和NetworkManager冲突

网卡多队列RSS设置


文章作者: NesTeaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesTeaLin !
  目录