使用 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 Poll Mode Driver」。與在 Compute Engine 上部署的典型應用程式不同,在 U4C 執行個體上透過 AF_XDP 使用 DPDK 時,不需要設定 VFIO、UIO 或 dpdk-devbind.py。
搭配 ULL 解決方案使用 DPDK 時,可支援流量導向。您可以導向特定流量,直接將流量傳送至指定接收佇列 (RX),藉此略過預設的接收端縮放 (RSS) 雜湊。ULL 解決方案支援 3 元組流量轉向 (通訊協定、目的地 IP 位址和目的地通訊埠),適用於 ULL 單點傳播和多點傳播流量。
事前準備
在 U4C Compute Engine 執行個體上使用 DPDK 之前,請務必符合下列條件。
建立 U4C 執行個體
如果尚未建立 U4C 裸機執行個體,請先建立。請參閱「建立 ULL Compute Engine 執行個體」。
使用 SSH 連線至執行個體
如果尚未連線,請使用 SSH 連線至執行個體。
切換為超級使用者
下列程序中的指令和指令碼會修改系統層級設定、核心參數和網路介面。如要順利執行這些指令,您必須以超級使用者身分執行。您可以執行 sudo su 切換至根層級殼層,或視需要先新增 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:網路介面的 OS 名稱,例如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 Poll Mode Driver」。
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:網路介面的 OS 名稱,例如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:網路介面的 OS 名稱,例如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
RX 時間戳記
雖然上游 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 時鐘同步,請參閱「設定準確時間」。