全面適配 DPDK 20-11,DPVS 發佈 v1-9-0 版本

經過 DPVS 團隊和社區開發者一個多季度的開發迭代,愛奇藝開源項目 DPVS 已經正式發佈了 v1.9.0 版本。DPVS v1.9.0 正式發佈於 2021/9/1,它適配了當前 DPDK 穩定版本 DPDK-20.11(LTS),支持 DPDK API/ABI 以及多種設備驅動的更新和優化。目前 DPVS v1.9.0 已在愛奇藝的多個核心數據中心部署上線,且穩定運行三個月。

關於 DPVS

DPVS 是愛奇藝網絡虛擬化團隊基於 DPDK (Data Plane Development Kit) 和 LVS (Linux Virtual Server) 開發的高性能四層網絡軟件負載均衡器,支持 FullNAT /DR /Tunnel /SNAT /NAT64 /NAT 六種負載均衡轉發方式和 IPv4 /IPv6 /TCP /UDP /ICMP /IMCPv6 等多種網絡協議,單核性能達到 2.3M PPS(每秒轉發 230 萬個包),單機性能可以達到萬兆網卡線速(約爲 15M PPS)。愛奇藝的四層負載均衡服務、SNAT 代理服務幾乎全部都是基於 DPVS 實現的。此外,DPVS 於 2017 年 10 月開源後,已吸引了來自包括網易、小米、中國移動、Shopee、字節跳動等在內的國內外衆多知名企業的核心貢獻者參與社區共建。

項目地址:

https://github.com/iqiyi/dpvs

使用文檔:

https://github.com/iqiyi/dpvs/blob/master/doc/tutorial.md

DPVS v1.9.0 內容更新列表

發佈地址:

https://github.com/iqiyi/dpvs/releases/tag/v1.9.0

DPVS 整個 1.9 大版本都將基於 DPDK 20.11 開發,v1.9.0 版本核心更新就是全面適配了 DPDK-20.11(LTS)。對 DPDK-18.11(LTS) 的支持已經移動到 DPVS-1.8-LTS 中,同時終止了對 DPDK-17.11(LTS) 的支持。

注:DPVS v1.9.0 使用的 DPDK 具體版本號是 DPDK 20.11.1

DPVS v1.9.0 是在 v1.8.10 基礎上開發的,其主要的內容更新分爲功能更新和漏洞修復兩類,分別列舉如下。

2.1  功能更新

2.2  漏洞修復

DPVS v1.9.0 重點更新介紹

3.1  更友好的編譯使用安裝方式

DPDK 20.11 用 meson/ninja 徹底取代了之前版本的 Makefile 構建方式,而 DPVSv1.9.0 雖然繼續沿用了 Makefile 構建方式,但是適配了 DPDK 20.11 的構建方式,通過 pkg-config 工具自動查找依賴 DPDK 的頭文件和庫文件,解決了 DPVS 安裝時複雜的環境依賴問題,使得 DPVS 構建更加智能。

CFLAGS += -DALLOW_EXPERIMENTAL_API $(shell pkg-config --cflags libdpdk)
LIBS += $(shell pkg-config --static --libs libdpdk)

完整的文件請參考 dpdk.mk 文件。可以看到,DPVS 鏈接階段使用了 DPDK 靜態庫。這雖然增加了 DPVS 可執行程序的大小,但避免了 DPVS 運行時在系統裏安裝 DPDK 動態鏈接庫的需求;同時,由於 DPVS 對 DPDK 打了一些補丁,用靜態鏈接的方式也避免了 DPDK 動態鏈接庫安裝時可能出現的版本衝突的麻煩。

爲了簡化 DPVS 的編譯安裝流程,DPVS v1.9.0 提供了一個輔助腳本 dpdk-build.sh,其用法如下。

$ ./scripts/dpdk-build.sh -h
usage: ./scripts/dpdk-build.sh [-d] [-w work-directory] [-p patch-directory]
OPTIONS:
   -v   specify the dpdk version, default 20.11.1
   -d   build dpdk libary with debug info
   -w   specify the work directory prefix, default {{ pwd }} 
   -p   specify the dpdk patch directory, default {{ pwd }}/patch/dpdk-stable-20.11.1

這個腳本參數支持用戶指定編譯 DPDK 使用的工作目錄前綴、DPDK patch 文件所在的目錄、DPDK 版本號(目前僅支持 20.11.1)、是否編譯爲 DEBUG 版本,其主要的工作流程如下:

利用這個輔助腳本,編譯 DPVS 僅需要如下三個簡單步驟:

S1. 編譯安裝 DPDK

$ ./scripts/dpdk-build.sh -d  -w /tmp -p ./patch/dpdk-stable-20.11.1/
...
DPDK library installed successfully into directory: //tmp/dpdk/dpdklib
You can use this library in dpvs by running the command below:
export PKG_CONFIG_PATH=//tmp/dpdk/dpdklib/lib64/pkgconfig

注:爲了說明腳本的用法,本例的命令是在 /tmp/dpdk 目錄裏編譯安裝有調試信息的 DPDK 版本。通常情況下腳本不用指定參數,使用默認值即可。

S2. 根據腳本輸出提示設置環境變量

$ export PKG_CONFIG_PATH=/tmp/dpdk/dpdklib/lib64/pkgconfig

S3. 編譯安裝 DPVS

$ make && make install

DPVS 默認安裝在當前目錄的 ./bin 子目錄下_。_

3.2  更通用的流(flow)配置管理

DPVS FullNAT 和 SNAT 的多核轉發需要配置網卡的流處理規則。下圖是一個典型的 DPVS 雙臂模式部署形式,DPVS 服務器有兩個網卡接口:網卡 - 1 負責和用戶通信,網卡 - 2 負責和 RS 通信。一般地,如果服務是 FullNAT,連接由外網用戶發起,網卡 - 1 是外網網卡,網卡 - 2 是內網網卡;如果服務是 SNAT,連接由用戶從內網發起,網卡 - 1 是內網網卡,網卡 - 2 是外網網卡。

Inbound(用戶到 RS)方向的流量通過 RSS 分發到不同的 worker 線程上,而 Outbound(RS 到用戶)的流量通過網卡流處理規則保證同一個會話的流量能匹配到正確的 worker 線程。DPVS v1.8 及其之前的版本使用 DPDK 的 rte_eth_dev_filter_ctrl 接口配置 Flow Director 類型(RTE_ETH_FILTER_FDIR)的流規則以實現 Outbound 方向的數據流和 Inbound 方向數據流的會話匹配。但是,DPDK 20.11 徹底廢棄了 rte_eth_dev_filter_ctrl 接口,改用 rte_flow 屏蔽了不同網卡、不同類型的流規則實現細節,實現了一種更通用的網卡流規則配置接口。因此,DPVS v1.9.0 適配了 rte_flow 這種新的流配置接口。

rte_flow 接口需要提供一組 flow item 組成的 pattern 和一組 action。如果數據包和流規則中的 pattern 匹配,則 action 的配置會決定數據包的下一步處理方式,比如送到某個網卡隊列、打上標籤、或者丟棄。因爲 DPVS 不僅支持物理設備接口,而且支持 Bonding、VLAN 等虛接口設備,所以我們增加了 netif_flow 模塊來管理 DPVS 不同類型的設備的 rte_flow 流規則。功能上,目前主要提供了 sa_pool 的操作接口,用於實現上面所述的兩個方向流的會話匹配。

/*
* Add sapool flow rules (for fullnat and snat).
*
* @param dev [in]
*     Target device for the flow rules, supporting bonding/physical ports.
* @param cid [in]
*     Lcore id to which to route the target flow.
* @param af [in]
*     IP address family.
* @param addr [in]
*     IP address of the sapool.
* @param port_base [in]
*     TCP/UDP base port of the sapool.
* @param port_mask [in]
*     TCP/UDP mask mask of the sapool.
* @param flows [out]
*     Containing netif flow handlers if success, undefined otherwise.
*
* @return
*     DPVS error code.
*/
int netif_sapool_flow_add(struct netif_port *dev, lcoreid_t cid,
           int af, const union inet_addr *addr,
           __be16 port_base, __be16 port_mask,
           netif_flow_handler_param_t *flows);
/*
* Delete saflow rules (for fullnat and snat).
* @param dev [in]
*     Target device for the flow rules, supporting bonding/physical ports.
* @param cid [in]
*     Lcore id to which to route the target flow.
* @param af [in]
*     IP address family.
* @param addr [in]
*     IP address of the sapool.
* @param port_base [in]
*     TCP/UDP base port of the sapool.
* @param port_mask [in]
*     TCP/UDP mask mask of the sapool.
* @param flows [in]
*     Containing netif flow handlers to delete.
*
* @return
*     DPVS error code.
*/
int netif_sapool_flow_del(struct netif_port *dev, lcoreid_t cid,
           int af, const union inet_addr *addr,
           __be16 port_base, __be16 port_mask,
           netif_flow_handler_param_t *flows);
/*
* Flush all flow rules on a port. *
* @param dev
*     Target device, supporting bonding/physical ports.
*
* @return
*     DPVS error code.
*/
int netif_flow_flush(struct netif_port *dev);

說明:Bonding 802.3ad 模式的 dedicatedqueue 也是通過 rte_flow 配置的,如果使用了這個功能,請注意不能隨意調用 rte_flow_flush 或 netif_flow_flush。

具體到 rte_flow 的配置上,sa_pool 的 flow pattern 匹配的是目標 IP 地址和目標端口信息。爲了減少網卡中流的數量,我們把目標端口地址空間,即 0 ~ 65535,按照 DPVS 配置的 worker 數量,設置了非全地址空間的掩碼。基本思路是把端口地址空間等分爲 worker 數量的份數,每個 worker 關聯其中一份端口地址子空間。所以,假如有 8 個 worker,我們僅需要配置 3-bit 的端口地址掩碼,數據包的目標端口地址和 flow item 中指定的端口地址掩碼進行 ” 與” 操作後得到的結果與 flowitem 中的端口基值比較,如果相等,則將數據包送到對應 action 設置的網卡隊列中。下面是 DPVS sa_pool 的 flow pattern 和 action 的具體配置。

需要說明的是,rte_flow 僅給我們提供了網卡流規則配置的統一接口,具體的流規則能否支持仍依賴於網卡硬件功能以及網卡的 DPDK PMD 驅動。目前,我們已經驗證 Mellanox ConnextX-5(mlx5)可以支持 DPVS 的 sa_pool flow 配置。Intel 82599 系列網卡(ixgbe 驅動)的雖然硬件支持 Flow Director,但是其 DPDK PMD 驅動卻沒有適配好 rte_flow 接口,甚至在 Debug 模式下出現因非法內存訪問導致程序崩潰的問題,所以我們給 ixgbe PMD 驅動開發了補丁 0004-ixgbe_flow-patch-ixgbe-fdir-rte_flow-for-dpvs.patch,使其也成功支持了 DPVS 的流處理規則。其它更多的網卡類型仍有待 DPVS 使用者的驗證。

3.3  更合理的 mbuf 自定義數據

爲了提高效率,DPVS 使用 DPDK 的 mbuf 用戶自定義空間存儲與數據包相關的、需要被多個模塊使用的關鍵數據。目前,DPVS 在 mbuf 中存儲的數據有兩種類型:路由信息和 IP header 指針。DPDK 18.11 中 mbuf 的用戶自定義數據空間長度是 8 個字節,在 64 位機器上最多隻能存儲一個指針數據,DPVS 需要小心區分兩種數據的存放和使用時機,保證它們不衝突。DPDK 20.11 的 mbuf 用 dynamic fields 取代了 userdata,並將長度增加到 36 個字節,且提供了一組 API 讓開發者動態註冊和申請使用。DPVS v1.9.0 爲兩種用戶數據申請了獨立的存儲空間,開發者不用再擔心數據衝突的問題了。

爲了利用 mbuf 的 dynamic fields 機制,DPVS 定義了兩個宏。

#define MBUF_USERDATA(m, type, field) \
   (*((type *)(mbuf_userdata((m), (field)))))
#define MBUF_USERDATA_CONST(m, type, field) \
   (*((type *)(mbuf_userdata_const((m)(field)))))

其中,m 表示 DPDK 的 mbuf 數據包結構,type 是 DPVS 用戶數據的類型,field 是 DPVS 定義的用戶數據類型的枚舉值。

typedef enum {
   MBUF_FIELD_PROTO = 0,
   MBUF_FIELD_ROUTE,
} mbuf_usedata_field_t;
mbuf_userdata(_const)

通過 mbuf 用戶數據註冊時返回的地址偏移量獲取存儲在 dynamic fields 裏的用戶數據。

#define MBUF_DYNFIELDS_MAX   8
static int mbuf_dynfields_offset[MBUF_DYNFIELDS_MAX];
void *mbuf_userdata(struct rte_mbuf *mbuf, mbuf_usedata_field_t field)
{
   return (void *)mbuf + mbuf_dynfields_offset[field];
}
void *mbuf_userdata_const(const struct rte_mbuf *mbuf, mbuf_usedata_field_t field)
{       
   return (void *)mbuf + mbuf_dynfields_offset[field]; 
}

最後,我們在 DPVS 初始化時調用 DPDK 接口 rte_mbuf_dynfield_register,初始化 mbuf_dynfields_offset 偏移量數組。

int mbuf_init(void)
{
   int i, offset;
   const struct rte_mbuf_dynfield rte_mbuf_userdata_fields[] = {
      [ MBUF_FIELD_PROTO ] = {
          .name = "protocol",
          .size = sizeof(mbuf_userdata_field_proto_t),
          .align = 8,
      },
      [ MBUF_FIELD_ROUTE ] = {
          .name = "route",
          .size = sizeof(mbuf_userdata_field_route_t),
          .align = 8,
      },
  };
   for (i = 0; i < NELEMS(rte_mbuf_userdata_fields); i++) {
       if (rte_mbuf_userdata_fields[i].size == 0)
           continue;
       offset = rte_mbuf_dynfield_register(&rte_mbuf_userdata_fields[i]);
       if (offset < 0) {
           RTE_LOG(ERR, MBUF, "fail to register dynfield[%d] in mbuf!\n", i);
           return EDPVS_NOROOM;
      }
       mbuf_dynfields_offset[i] = offset;
  }
   return EDPVS_OK;
}

3.4  更完善的調度算法

長連接、低併發、高負載的 gRPC 業務反饋 DPVS 在他們這種應用場景下,連接數量在 RS 上分佈不均勻。經排查分析,這個問題是由 rr/wrr/wlc 調度算法的 per-lcore 的實現方式導致的。如下圖所示,假設 DPVS 配置了 8 個轉發 worker,inbound 方向(用戶到 RS 方向)的流量是通過網卡 RSS HASH 功能,將流量分發到 w0...w7 不同 worker 上。

因爲每個 worker 上的調度算法和數據是相互獨立的,而且所有 worker 以相同的方式初始化,所以每個 worker 會以相同的順序選取 RS。比如,對於輪詢(rr)調度,所有 worker 上的第一個連接都會選擇 RS 列表中的第一臺服務器。下圖給出了 8 個 worker, 5 個 RS 的調度情況:假設 RSS HASH 算法是平衡的,則很可能前 8 個用戶連接分別哈希到 8 個不同 worker 上,而 8 個 worker 獨立調度,將 8 個用戶流量全都轉發到第一個 RS 上,而其餘 4 個 RS 沒有用戶連接,使得負載在 RS 上分佈很不均衡。

DPVS v1.9.0 解決了這個問題,思路很簡單,我們讓不同 worker 上的調度算法按照如下策略選擇不同的 RS 初始值:

 InitR(cid) = ⌊N(rs) × cid / N(worker)

其中,N(rs)、N(worker) 分別是 RS 和 worker 的數量,cid 是 worker 的編號(從 0 開始編號),InitR(cid) 爲編號爲 cid 的 worker 調度算法的 RS 初始值。下圖給出了上面的例子使用這種策略調度結果,用戶連接可以均衡的分佈到所有 RS 上了。

3.5  更高效的 keepalived UDP 健康檢

此前版本的 DPVS keepalived 不支持 UDP_CHECK,UDP 業務的健康檢查只能使用 MISC_CHECK 方式,這種方式的配置示例如下:

real_server 192.168.88.115 6000 {
  MISC_CHECK {
      misc_path "/usr/bin/lvs_udp_check 192.168.88.115 6000"
      misc_timeout 3
  }   
}

其中, lvs_udp_check 腳本通過 nmap 工具探測 UDP 端口是否開放。

ipv4_check $ip
if [ $? -ne 0 ]; then
  nmap -sU -n $ip -p $port | grep 'udp open' && exit 0 || exit 1
else
  nmap -6 -sU -n $ip -p $port | grep 'udp open' && exit 0 || exit 1
fi

基於 MISC_CHECK 的 UDP 健康檢查方式有如下缺點:

爲了支持高性能的 UDP 健康檢查,DPVS 社區開發者 weiyanhua100 移植了最新 keepalived 官方版本的 UDP_CHECK 模塊到 DPVS 的 keepalived 中。這種方式的配置示例如下:

real_server 192.168.88.115 6000 {
  UDP_CHECK {
      retry 3
      connect_timeout 5
      connect_port 6000
      payload hello
      require_reply hello ok
      min_reply_length 3
      max_reply_length 16
  }
}

其中, payload 指定健康檢查程序發送給 RS 的 UDP 請求數據,require_reply 是期望收到的 RS 的 UDP 響應數據。這樣 UDP 服務器可以自定義健康檢查接口,通過這種方式,我們既能探測到 RS 上的 UDP 服務是否真的可用,也能避免健康檢查對真實業務的干擾。如果不指定 payload 和 require_reply,則只進行 UDP 端口探測,效果和 nmap 端口探測方式類似。

UDP_CHECK 通過 keepalived 和 RS 之間的 UDP 數據交互以及 ICMP 錯誤報文確定後端 UDP 服務的可用性,這種方式的優點如下。

未來版本計劃

4.1   DPVS v1.8.12(2021 Q4)

4.2  DPVS v1.9.2(2022 Q1)

4.3  長遠版本

參與社區

目前,DPVS 是一個由數十個公司的開發者、使用者參與的開源社區,我們非常歡迎對 DPVS 感興趣的同學參與到該項目的使用、開發和社區的建設、維護中來。歡迎大家爲 DPVS 提供任何方面的貢獻,不論是文檔,還是代碼;issue 還是 bug fix;以及,也非常歡迎大家把公司添加到 DPVS 社區用戶列表中。

如果你對 DPVS 有問題,可以通過如下幾種方式聯繫到我們。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/1lqZCkun77f_mZ8osByfvA