使用 DPDK
本页介绍了如何在 U4C Compute Engine 实例上使用数据平面开发套件 (DPDK)。
关于通过 AF_XDP 使用 DPDK
数据平面开发套件 (DPDK) 是一种框架,适用于需要快速数据包处理、低延迟和一致性能的性能密集型应用。DPDK 会绕过 Linux 内核网络堆栈,直接在用户空间中运行。您可以使用 AF_XDP 架构在 U4C 实例上运行 DPDK。
DPDK 提供了一个 AF_XDP 轮询模式驱动程序 (PMD),这是一个虚拟设备 (vdev),可让 DPDK 应用在复制模式或零复制模式下在 AF_XDP 之上运行。如需了解详情,请参阅 DPDK 文档中的 AF_XDP 轮询模式驱动程序。与 Compute Engine 上的典型部署不同,U4C 实例上基于 AF_XDP 的 DPDK 不需要配置 VFIO、UIO 或 dpdk-devbind.py。
将 DPDK 与 ULL 解决方案搭配使用时,支持流转向。 您可以将特定流量流直接导向到指定的接收队列 (RX),从而绕过默认的接收端扩展 (RSS) 哈希处理。ULL 解决方案支持针对 ULL 单播和多播流量的 3 元组流量导向(协议、目标 IP 地址和目标端口)。
准备工作
在 U4C Compute Engine 实例上使用 DPDK 之前,您必须满足以下要求。
创建 U4C 实例
创建 U4C Bare Metal 实例(如果尚未创建)。请参阅创建 ULL Compute Engine 实例。
使用 SSH 连接到您的实例
如果您尚未连接到实例,请先使用 SSH 连接。
切换到根用户
以下过程中的命令和脚本会修改系统级设置、内核参数和网络接口。如需成功运行这些命令,您必须以根用户身份执行它们。您可以运行 sudo su 切换到根 shell,也可以根据需要添加 sudo,然后再运行命令。
在 U4C 实例上安装 DPDK
如需在 U4C 实例上安装 DPDK,请按照以下步骤操作:
配置 DPDK 安装的依赖项:
apt-get update && apt-get upgrade -yq apt-get install -yq build-essential ninja-build python3-pip \ linux-headers-$(uname -r) pkg-config libnuma-dev pip install pyelftools meson安装 DPDK。
wget https://fast.dpdk.org/rel/dpdk-VERSION.tar.xz tar xvf dpdk-VERSION.tar.xz cd dpdk-VERSION
将
VERSION替换为您要安装的 DPDK 版本,例如26.07。如有需要,请参阅 DPDK 下载页面。如需使用示例构建 DPDK,请运行以下命令:
meson setup -Dexamples=all build ninja -C build install; ldconfig
为 AF_XDP 配置网络接口
如需在 Google 虚拟 NIC (gVNIC) 上使用 AF_XDP,您必须调整默认驱动程序功能,以准备网络接口。
您可以手动执行这些步骤,也可以使用自动配置脚本。选择以下某个标签页:
手动
针对要配置的每个网络接口,执行以下步骤。
减少 RX 和 TX 队列数:gVNIC 默认使用支持的最大 RX 和 TX 队列数,但您必须将此数量减少一半,以确保有足够的 TX 队列来处理正常的内核流量。
ethtool -L NIC_NAME rx NUM_SOCKETS \ tx NUM_SOCKETS
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如eth1。NUM_SOCKETS:要配置的 AF_XDP 套接字数量。 将此值设置为不大于接口最大队列数的一半。 对于 U4C 实例,此值通常为8(默认 16 个队列的一半)。 您可以运行ethtool -l NIC_NAME来验证最大队列数。
停用硬件 GRO 和 LRO:由于 gVNIC 不支持多缓冲区 XDP,因此您必须停用大型接收分流 (LRO) 和硬件通用接收分流 (GRO):
ethtool -K NIC_NAME rx-gro-hw off ethtool -K NIC_NAME lro off
缩短 RX 缓冲区长度:默认情况下,较新的驱动程序会将 4 KB(4,096 字节)的缓冲区发布到网络接口以进行 RX,但 XDP 需要的缓冲区长度为
2048:ethtool -G NIC_NAME rx-buf-len 2048
脚本
或者,您也可以针对要配置的每个网络接口运行以下 Bash 脚本。该脚本通过减少队列数量、停用分流和调整 RX 缓冲区长度,自动为 XDP 准备给定的网络接口:
#!/bin/bash # Usage example: NUM_SOCKETS=8 prep_xdp.sh eth0 DEV=$1 NUM_SOCKETS=${NUM_SOCKETS=1} # Reduce RX/TX queue counts to the number of AF_XDP sockets ethtool -L $DEV rx $NUM_SOCKETS tx $NUM_SOCKETS # Disable LRO/HW-gro OFFLOAD=$(ethtool -k $DEV | \ grep "rx-gro-hw\|large-receive-offload" | \ grep -v fixed | cut -d ":" -f 1) ethtool -K $DEV ${OFFLOAD} off # Reduce RX buffer length to 2048 ethtool -G $DEV rx-buf-len 2048
运行 DPDK 应用
如需使用 AF_XDP PMD,请在 DPDK 应用的环境抽象层 (EAL) 实参中添加 --vdev 标志。
以下示例命令包含多个关键参数。如需详细了解设置和参数,请参阅 DPDK 文档中的 AF_XDP 轮询模式驱动程序。
DPDK_APPLICATION -a PCIE_BDF \ --vdev=net_af_xdp,iface=NIC_NAME,queue_count=NUM_SOCKETS,start_queue=START_QUEUE,xdp_prog=XDP_PROG \ -- APPLICATION_ARGS
替换以下内容:
DPDK_APPLICATION:要运行的 DPDK 应用二进制文件。PCIE_BDF:网络接口的 PCI 地址。您可以通过运行ethtool -i NIC_NAME并检查bus-info值(例如0000:00:04.0)来找到此值。NIC_NAME:网络接口的操作系统名称,例如eth1。NUM_SOCKETS:要打开的 AF_XDP 套接字数量。每个套接字都连接到单个队列对。此值必须与您在接口上配置的队列数量一致。START_QUEUE:AF_XDP 套接字的起始队列索引。XDP_PROG:要在接收到的数据包上运行的自定义 XDP 程序。如果省略,DPDK 会使用libxdp提供的默认 XDP 程序。APPLICATION_ARGS:特定于您的 DPDK 应用的实参。
使用驱动程序功能
本部分提供了有关流量转向和 RX 时间戳的使用信息。
流程导向
您可以将流导向与 XDP 结合使用,将应用数据包导向到特定的队列子集,并将剩余的队列用于内核流量。以下各部分介绍了可用于流规则编程的两种方法。
预节目流程导向(推荐)
由于 AF_XDP PMD 不支持使用 ioctl 调用进行即时流编程,并且需要修改 DPDK 应用,因此我们建议您在启动应用之前使用 ethtool 预先对流规则进行编程。
例如,如需对将 IPv4 UDP 流量引导至队列 0 的流规则进行编程,请执行以下操作:
ethtool -N NIC_NAME flow-type udp4 \ dst-ip DST_IP dst-port DST_PORT action 0 loc 0
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如eth1。DST_IP:要引导的流量的目标 IP 地址。DST_PORT:要引导的流量的目标端口。
即时制定节目流规则
AF_XDP PMD 不支持即时流编程。如需使用此方法,您必须修改 DPDK 应用以手动发送 ethtool ioctl 调用。
我们建议您避免使用此方法,除非您的应用需要处理大量临时连接。gVNIC 最多支持 20,000 条 3 元组流转向规则;如果您需要的规则较少,并且提前知道目标 IP 地址和端口,请改为预先编程您的规则。
如果您必须动态地对规则进行编程,请参阅以下 C 代码示例:
展开可查看 C 代码示例
struct flow_rule_info { uint32_t src_ip; uint32_t dst_ip; uint16_t src_port; uint16_t dst_port; uint32_t target_queue; uint32_t rule_id; } int add_flow_rule(const char *ifname, struct flow_rule_info *rule_info, bool is_5tuple) { struct ethtool_rxnfc cmd; struct ifreq ifr; int fd; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { fprintf(stderr, "Failed to open socket: %s", strerror(fd)); return -1; } memset(&cmd, 0, sizeof(cmd)); memset(&ifr, 0, sizeof(ifr)); cmd.cmd = ETHTOOL_SRXCLSRLINS; cmd.fs.flow_type = UDP_V4_FLOW; cmd.fs.h_u.udp_ip4_spec.ip4dst = rule_info->dst_ip; cmd.fs.h_u.udp_ip4_spec.pdst = htons(rule_info->dst_port); cmd.fs.m_u.udp_ip4_spec.ip4dst = 0xFFFFFFFF; cmd.fs.m_u.udp_ip4_spec.pdst = 0xFFFF; if (is_5tuple) { cmd.fs.h_u.udp_ip4_spec.ip4src = rule_info->src_ip; cmd.fs.h_u.udp_ip4_spec.psrc = htons(rule_info->src_port); cmd.fs.m_u.udp_ip4_spec.ip4src = 0xFFFFFFFF; cmd.fs.m_u.udp_ip4_spec.psrc = 0xFFFF; } cmd.fs.ring_cookie = rule_info->target_queue; cmd.fs.location = rule_info->rule_id; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); ifr.ifr_data = (void *)&cmd; int ret = ioctl(fd, SIOCETHTOOL, &ifr); if (ret) fprintf(stderr, "Failed to send ioctl: %s\n", strerror(errno)); close(fd); return ret; } int try_add_xdp_flow_rule(int port, struct flow_rule_info *rule_info) { char dev_name[RTE_ETH_NAME_MAX_LEN]; char *af_xdp_driver = "net_af_xdp"; struct rte_eth_dev_info dev_info; int err; if (!rte_eth_dev_is_valid_port(port)) return -1; err = rte_eth_dev_info_get(port, &dev_info); if (err) { fprintf(stderr, "Error getting info for port %d: %s\n", port, strerror(err)); return retval; } if (strncmp(dev_info.driver_name, af_xdp_driver, strlen(af_xdp_driver)) != 0) { fprintf(stderr, "Not an AF_XDP vdev!\n"); return -EINVAL; } err = rte_eth_dev_get_name_by_port(port, dev_name); if (retval) { fprintf(stderr, "Failed to get dev_name: %s\n", strerror(err)); return retval; } /* replace ens4 with correct ifname; must be passed in as application argument */ err = add_flow_rule("ens4", rule_info)); if (err) fprintf("Failed to add flow rule: %s\n", strerror(err)); return err; } /* From somewhere in the application: */ int application_func(...) { ... struct flow_rule_info *rule_info { .src_ip = 0x0a000001, .dst_ip = 0x0a000002, .src_port = 0x1110, .uint16_t dst_port = 0x1011, .uint32_t target_queue = 0, .uint32_t rule_id = 1, }; try_add_xdp_flow_rule(xdp_port_id, &rule_info, /*is_5tuple=*/false); ... }
RSS 节目
使用流转向时,RSS 有助于提供更好的流量隔离。
与流量转向一样,您可以使用 ethtool 配置 RSS。建议您先配置 RSS,然后再运行应用。
以下示例展示了如何配置 RSS 以便与 AF_XDP 搭配使用:
# Example: Kernel queues 0-3, XDP queues 4-7 NUM_SOCKETS=4 bash prep_xdp.sh eth0 # Program flow rules ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_0 action 4 loc 0 ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_1 action 5 loc 1 ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_2 action 6 loc 2 ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_3 action 7 loc 3 # If there are more ports that the application polls on, flow rules can be added in a round-robin fashion in a script. # Program RSS ethtool -X eth0 start 0 equal 4 # Run the application ./path/to/application -a 0000:00:03.0 --vdev net_af_xdp,iface=eth0,start_queue=4,queue_count=4 -- APPLICATION_ARGS
接收时间戳
虽然上游 DPDK 代码库中不支持 AF_XDP PMD 的 RX 时间戳,但您可以通过执行以下操作来使用 RX 时间戳:
将必需的补丁应用到 DPDK 源代码树,然后重新编译 DPDK 源代码和您的应用。 这些补丁在接收到的
mbufs中添加了对rte_eth_read_clock和 RX 时间戳的支持。使用将时间戳加载到元数据中的 XDP 程序。
启动 DPDK 应用时,请提供以下额外的 AF_XDP
vdev参数:xdp_meta_rx_ts_offset:从 XDP 元数据开头算起,64 位 RX 时间戳值所在的字节偏移量。xdp_meta_valid_hint_offset:(可选)涵盖 1 字节标志字段的字节偏移量,用于指示时间戳是否有效。xdp_meta_rx_ts_valid_mask:(可选)用于提取有效标志位的位掩码。如果
ctx是元数据的开头,则(ctx->data_meta + xdp_meta_valid_hint_offset) & xdp_meta_rx_ts_valid_mask的值用于描述时间戳是否有效。
后续步骤
- 如需将实例时钟与宿主服务器的物理 NIC 时钟同步,请参阅配置准确时间。