准备工作
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
驱动、固件版本要求
硬件支持类型
依赖安装
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
- Build dependencies may be needed, such as
pkg-config
(version 0.29.2+),automake
,libnl3
,libnl-genl-3.0
,openssl
,popt
andnumactl
. You can install the missing dependencies by using the package manager of the system, e.g.,yum install popt-devel
(CentOS).- 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等等ipvsadm
和keepalived
来自定制(爱奇艺)修改过的 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.0
和0000: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
这里仍旧使用系统原生
ip
、route
命令即可管理用户态的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 逻辑核设置亲和性时出现问题。
排查过程
所以接下来要做的是:
- 确认当前主机有多少个逻辑核
- 确认函数获取的逻辑核数是否与实际一致
- 确认函数执行到第几个逻辑核出现问题
- 再根据问题寻找详细原因
写一个测试 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.h
中 ETH_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
参考文档
在centos7虚拟机上运行DPVS发生的CPU亲和性设置问题