使用多個主機 NIC 的模式

本文說明設定模式和最佳做法,協助您在 Compute Engine 執行個體上,使用兩個主機區域網路介面 (NIC) 進行儲存和管理。

總覽

部分加速器最佳化機器系列 (包括 A5X、A4X Max、A4X、A4 和 A3 Ultra) 具有兩個主機 LAN 網路介面,用於儲存和管理,可連結至一般虛擬私有雲 (VPC) 網路。這些機器也有 MRDMA 網路介面,必須連結至基於融合乙太網路的遠端直接記憶體存取 (RoCE) 虛擬私有雲網路。

本文著重於在一般虛擬私有雲網路 (而非 RDMA 網路介面) 設定主機區域網路介面 (gVNIC 或 IDPF)。主機 LAN 介面是 Titanium 基礎架構處理單元 (IPU),可連線至獨立的 CPU 插槽和非統一記憶體存取 (NUMA) 節點。在運算執行個體中,這些 IPU 會顯示為 Google 虛擬 NIC (gVNIC) 或基礎架構資料路徑函式 (IDPF) 網路介面。這些介面提供網路頻寬,用於儲存和管理活動,例如檢查點、載入訓練資料和載入模型。訪客作業系統 (OS) 可查看機器的 NUMA 拓撲,包括網路介面的拓撲。

無論您為多個主機 NIC 選擇哪種模式,請使用下列設定:

  • 網路設定: 設定每個 gVNIC 或 IDPF 介面,使用獨立的虛擬私有雲網路。請完成下列虛擬私有雲設定:
    • 為每個虛擬私有雲網路設定較大的最大傳輸單位 (MTU),例如 8896 (支援的最大 MTU)。
    • 如果接收端捨棄傳入的資料封包,導致工作負載的輸入效能降低,請使用 ethtool 公用程式檢查是否有捨棄的封包。如要最佳化頁面快取的資料分配,讓每個傳入的第 2 層框架都能容納在兩個 4 KB 緩衝區中,請調整 TCP 最大區段大小 (MSS)、介面 MTU 或虛擬私有雲 MTU。
  • 應用程式設定:
    • 使用 CPU 核心、記憶體配置和來自相同 NUMA 節點的網路介面,對應用程式進行 NUMA 對齊。如果您為特定 NUMA 節點或網路介面執行應用程式的專屬執行個體,請使用 numactl 等工具,將應用程式的 CPU 和記憶體資源繫結至該 NUMA 節點。
  • 作業系統設定:
    • 啟用 TCP 區隔卸載 (TSO) 和大型接收卸載 (LRO)。
    • 針對每個 gVNIC 或 IDPF 介面,設定對稱多處理 (SMP) 親和性,將中斷要求 (IRQ) 轉送至與介面相同的 NUMA 節點,並將中斷分散至各個 CPU 核心。如果您使用 Google 提供的 Linux 映像檔,Compute Engine 訪客代理程式會在啟動時執行 google_set_multiqueue 指令碼,自動套用這項設定。如果使用自訂映像檔,請安裝客體環境。如果指令碼未自動執行,請執行 sudo /usr/bin/google_set_multiqueue 指令。
    • 評估接收流程導向 (RFS)、接收封包導向 (RPS) 和傳輸封包導向 (XPS) 設定,判斷是否對工作負載有益。
    • 如果是 A4X Max 和 A4X 執行個體,NVIDIA 建議停用自動 NUMA 排程。
    • 請勿在這些機器上使用 Linux 核心繫結與 gVNIC 或 IDPF 介面,因為系統不支援核心繫結。

使用多個主機 NIC 的模式

下表概述在Cloud de Confiance by S3NS上使用多個主機 NIC 的一般模式:

使用多個主機 NIC 的一般模式
模式 支援的程序版面配置 部署路徑:Compute Engine 部署路徑:Google Kubernetes Engine 部署路徑:Slurm 附註
變更應用程式以使用特定介面 每個介面的處理 shard 是 是 是 需要變更應用程式的程式碼。
變更應用程式以使用這兩種介面 雙介面程序 是 是 是 需要變更應用程式的程式碼。
為特定應用程式使用專屬網路命名空間 每個介面的處理 shard 是 是 (僅限特殊權限容器) 否 無
將整個容器的流量對應至單一介面 所有容器流量都對應至一個介面 是 是 否 無
使用虛擬私有雲對等互連,讓系統在介面之間平衡工作階段負載 雙介面程序 是 是 是 難以或無法進行 NUMA 調整。需要 Linux 核心 6.16 以上版本。
將流量分散至多個網路
  • 雙介面程序
  • 每個介面的處理 shard
是 是 是 如果您執行雙介面程序,可能需要變更程式碼,以配合 NUMA。
使用來源網路位址轉譯 (SNAT) 選擇來源介面
  • 雙介面程序
  • 每個介面的處理 shard
是 是 (設定需要管理員權限) 是 (設定需要管理員權限) 設定可能相當困難。

附註

  • 「使用虛擬私有雲對接,並讓系統在介面之間平衡工作階段負載」和「在網路之間分割流量」模式:一般不建議使用這些選項,但可能適用於 x86 (A4 和 A3 Ultra) 平台上的有限工作負載。

將應用程式變更為使用特定介面

必要操作:

  • 需要變更應用程式的程式碼。
  • 需要下列一或多個方法的權限:
    • bind():只有在使用特殊權限來源通訊埠時,才需要特殊權限。
    • SO_BINDTODEVICE:需要 CAP_NET_RAW 權限。
  • 可能需要修改核心路由表,才能建立路由並防止非對稱路由。

高階總覽

如要設定應用程式使用特定網路介面,請完成下列步驟:

  1. 使用下列任一選項,將網路介面的繫結新增至應用程式的原始碼:
    • 使用 bind() 系統呼叫,將通訊端繫結至特定來源 IP 位址。
    • 使用 SO_BINDTODEVICE 通訊端選項,將通訊端繫結至特定網路介面。
  2. 視需要修改核心路由表,確保從來源網路介面到目的地地址有路由。如要避免非對稱轉送,請按照「設定其他網路介面的轉送」一文所述,設定政策轉送。
  3. 選用:使用 numactl 指令,在與所選網路介面共用相同 NUMA 節點的記憶體和 CPU 上執行應用程式。

完成上述步驟後,應用程式就會使用指定的網路介面。

變更應用程式以使用兩個介面

必要操作:

  • 需要變更應用程式的程式碼。
  • 需要下列一或多個方法的權限:
    • bind():只有在使用特殊權限來源通訊埠時,才需要特殊權限。
    • SO_BINDTODEVICE:需要 CAP_NET_RAW 權限。
  • 可能需要修改核心路由表,才能建立路由並防止非對稱路由。

高階總覽

如要將應用程式設為使用這兩個網路介面,請完成下列步驟:

  1. 使用下列任一選項,在應用程式的原始碼中為每個網路介面新增繫結:
    • 使用 bind() 系統呼叫,將每個通訊端繫結至對應網路介面的來源 IP 位址。
    • 使用 SO_BINDTODEVICE 通訊端選項,將每個通訊端繫結至特定網路介面。
  2. 如果應用程式做為用戶端,請為每個來源網路介面建立個別的用戶端通訊端。
  3. 視需要修改核心路由表,確保每個來源網路介面都有通往目的地位置的路徑。如要避免非對稱轉送,請按照「設定其他網路介面的轉送」一文所述,設定政策轉送。
  4. 將網路活動分區為執行緒,並在與對應 gVNIC 或 IDPF 介面相同的 NUMA 節點上執行,例如呼叫 pthread_setaffinity_np。由於應用程式會使用多個 NUMA 節點的資源,請避免使用 numactl,或確保 numactl 指令包含應用程式使用的所有網路介面 NUMA 節點。

為特定應用程式使用專屬網路命名空間

必要操作:

  • 需要 CAP_SYS_ADMIN 功能。
  • 與 GKE Autopilot 不相容。
  • 如果您使用的是 GKE,則必須使用具特殊權限的容器。

本節說明可用來為次要網路介面建立網路命名空間的模式。最適合工作負載的模式取決於您的情境。如果多個應用程式需要從不同的網路命名空間共用次要介面,最適合使用虛擬交換器或 IPvlan 的方法。

將次要介面移至專屬網路命名空間

採用這個模式時,您會建立網路命名空間,將次要 gVNIC 或 IDPF 介面移至新的命名空間,然後從該命名空間執行應用程式。與虛擬交換器相比,這種模式更容易設定和調整,但新網路命名空間以外的應用程式無法存取次要網路介面。

如要將 eth1 介面移至名為 app-net 的網路命名空間,並在該命名空間中執行指令,請執行下列指令:

ip netns add app-net
ip link set eth1 netns app-net
ip netns exec app-net ip addr add ETH1_IP/PREFIX dev eth1
ip netns exec app-net ip link set dev eth1 up
ip netns exec app-net ip route add default via GATEWAY_IP dev eth1
ip netns exec app-net COMMAND

更改下列內容:

  • ETH1_IP:eth1 介面的內部 IP 位址。
  • PREFIX:eth1 介面的子網路前置碼長度。
  • GATEWAY_IP:eth1 子網路的預設閘道 IP 位址。
  • COMMAND:在 app-net 網路命名空間中執行的指令。

執行這些指令時,COMMAND 會在 app-net 網路命名空間內執行,並使用 eth1 介面。

在新網路命名空間中執行的應用程式,會使用次要 gVNIC 或 IDPF 介面。您也可以使用 numactl 指令,透過與所選網路介面位於相同 NUMA 節點的記憶體和 CPU 執行應用程式。

使用虛擬交換器和網路命名空間做為次要介面

使用這個模式時,您需要設定虛擬交換器,以便從網路命名空間存取次要 gVNIC 或 IDPF 介面:

  1. 開啟與運算執行個體的 SSH 連線。詳情請參閱「連線至 Linux VM」。
  2. 使用 ip link add 指令建立虛擬乙太網路 (veth) 裝置配對,例如 ip link add veth0 type veth peer name veth1。
  3. 使用 ip link set 指令調整每部裝置的 MTU,使其與次要介面的 MTU 相符:

    ip link set dev veth0 mtu MTU
    ip link set dev veth1 mtu MTU
    

    將 MTU 替換為次要介面的 MTU,例如 8896。

  4. 啟用 IPv4 封包轉送:

    sysctl -w net.ipv4.ip_forward=1
    
  5. 將 veth 配對的一端移至新的網路命名空間,另一端則留在根命名空間。

  6. 將 veth 裝置的流量對應至次要介面。如要設定這項對應,請為運算執行個體的次要介面建立別名 IP 範圍,並將這個範圍內的 IP 位址指派給命名空間中的子介面。

  7. 從新的網路命名空間執行應用程式。您可以使用 numactl 指令,透過與所選網路介面位於相同 NUMA 節點的記憶體和 CPU 執行應用程式。

或者,視客體 OS 和工作負載設定而定,您可以將 IPvlan 介面連結至次要 gVNIC 或 IDPF 介面,而不建立 veth 裝置。

將整個容器的流量對應至單一介面

必要操作:

  • 應用程式必須在容器內執行,且該容器使用網路命名空間進行容器網路,例如 GKE、Docker 或 Podman。無法使用主機網路。

GKE、Docker 和 Podman 等容器執行階段會為每個容器使用專屬的網路命名空間,藉此隔離流量。您可以直接或使用容器執行階段的工具修改這個網路命名空間,將流量對應至不同的網路介面。

GKE 需要主要介面,才能進行內部 Kubernetes 通訊。不過,您可以變更 Pod 中的預設路徑,改用次要介面,如以下 GKE Pod 資訊清單所示:

metadata:
  # ...
  annotations:
    networking.gke.io/default-interface: 'eth1'
    networking.gke.io/interfaces: |
      [
        {"interfaceName":"eth0","network":"default"},
        {"interfaceName":"eth1","network":"secondary-network"}
      ]

這種方法無法保證預設網路介面與指派的 CPU 或記憶體之間的 NUMA 對齊。

使用虛擬私有雲對接,讓系統在介面之間平衡工作階段負載

必要操作:

  • 您必須在主要和次要主機 NIC 的虛擬私有雲網路之間,建立虛擬私有雲網路對接。
  • 將流量傳送至單一目的地 IP 位址和通訊埠時,您必須使用 Linux 核心版本 6.16 以上版本,才能在來源介面之間平衡 TCP 工作階段的負載。
  • 工作負載必須能容忍跨通訊端記憶體傳輸,且不會錯過成效目標。

高階總覽

如果您無法在應用程式內或跨應用程式執行個體輕鬆 shard 網路連線,且工作負載在 A4 或 A3 Ultra 運算執行個體上執行,且對跨 NUMA 或跨通訊端傳輸不敏感,則可將這兩個介面視為可替換。

如要設定這項行為,請啟用 fib_multipath_hash_policy sysctl 參數,並新增多路徑路由:

PRIMARY_GW=192.168.1.1  # Gateway of nic0
SECONDARY_GW=192.168.2.1  # Gateway of nic1
PRIMARY_IP=192.168.1.15  # Internal IP address for nic0
SECONDARY_IP=192.168.2.27  # Internal IP address for nic1

# Enable L4 5-tuple equal-cost multipath (ECMP) hashing
sysctl -w net.ipv4.fib_multipath_hash_policy=1
ip route add DESTINATION_NETWORK/SUBNET_MASK \
    nexthop via ${PRIMARY_GW} \
    nexthop via ${SECONDARY_GW}

更改下列內容:

  • DESTINATION_NETWORK:目的地內部 IP 位址範圍。
  • SUBNET_MASK:目的地網路的子網路前置碼長度。

在網路之間分配流量

必要操作:

  • 運算執行個體上的 nic0 和 nic1 必須連線至不同的虛擬私有雲網路和子網路,且目的地 IP 位址必須在兩個虛擬私有雲網路之間進行 shard。

高階總覽

根據預設,Linux 核心會為 nic0 子網路和 nic1 子網路建立路由,依據目的地 IP 位址,透過對應的網路介面轉送流量。

舉例來說,假設 nic0 使用虛擬私有雲網路 net1 和子網路 subnet-a,而 nic1 使用虛擬私有雲網路 net2 和子網路 subnet-b。根據預設,傳送至 subnet-a 中對等互連 IP 位址的流量會使用 nic0,而傳送至 subnet-b 中對等互連 IP 位址的流量則會使用 nic1。如果一組對等互連單一 NIC 運算執行個體連線至 net1,而第二組連線至 net2,通常會套用這項設定。

使用 SNAT 選擇來源介面

必要操作:

  • 需要 CAP_NET_ADMIN 功能才能設定初始 iptables 規則,但不需要執行應用程式。
  • 與其他非一般 iptables 規則或轉送設定搭配使用時,必須謹慎評估。

高階總覽

如果您無法使用網路命名空間或修改應用程式,則可使用 SNAT 選取來源介面。使用 iptables 等工具,您可以根據傳送應用程式的屬性 (例如 cgroup、使用者或 CPU),重寫流量的來源 IP 位址,以符合特定介面的 IP 位址。

下列範例使用 CPU 規則,因此來自特定 CPU 執行緒的任何流量,都會透過連結至該 CPU NUMA 節點的 gVNIC 或 IDPF 介面輸出:

# --- Begin Configuration ---
OUTPUT_INTERFACE_0="enp0s19"        # CHANGEME: NIC0
OUTPUT_INTERFACE_1="enp192s20"      # CHANGEME: NIC1

CPUS_0=($(seq 0 55; seq 112 167))   # CHANGEME: CPU IDs for NIC0
GATEWAY_0="10.0.0.1"                # CHANGEME: Gateway for NIC0
SNAT_IP_0="10.0.0.2"                # CHANGEME: SNAT IP for NIC0
CONNMARK_0="0x1"
RT_TABLE_0="100"

CPUS_1=($(seq 56 111; seq 168 223)) # CHANGEME: CPU IDs for NIC1
GATEWAY_1="10.0.1.1"                # CHANGEME: Gateway for NIC1
SNAT_IP_1="10.0.1.2"                # CHANGEME: SNAT IP for NIC1
CONNMARK_1="0x2"
RT_TABLE_1="101"
# --- End Configuration ---

# Specify which interface to use for packets in each routing table.
ip route add default via "$GATEWAY_0" dev "$OUTPUT_INTERFACE_0" table "$RT_TABLE_0"
ip route add default via "$GATEWAY_1" dev "$OUTPUT_INTERFACE_1" table "$RT_TABLE_1"

# Route replies to connections from peers through the matching egress interface.
# Add these rules before the fwmark rules so that fwmark takes precedence.
ip rule add from "$SNAT_IP_0" table "$RT_TABLE_0"
ip rule add from "$SNAT_IP_1" table "$RT_TABLE_1"

# Select the routing table based on the packet mark set in OUTPUT.
ip rule add fwmark "$CONNMARK_0" table "$RT_TABLE_0"
ip rule add fwmark "$CONNMARK_1" table "$RT_TABLE_1"

# Relax reverse path filtering so that the kernel doesn't drop legitimate
# replies to the SNAT IP addresses.
sysctl -w net.ipv4.conf."$OUTPUT_INTERFACE_0".rp_filter=2
sysctl -w net.ipv4.conf."$OUTPUT_INTERFACE_1".rp_filter=2

# Mark packets and connections with a per-NIC mark based on the source CPU.
# The fwmark rules then select the corresponding routing table for this traffic.
for cpu_id in "${CPUS_0[@]}"; do
    iptables -t mangle -A OUTPUT -m state --state NEW -m cpu --cpu "$cpu_id" -j CONNMARK --set-mark "$CONNMARK_0"
    iptables -t mangle -A OUTPUT -m state --state NEW -m cpu --cpu "$cpu_id" -j MARK --set-mark "$CONNMARK_0"
done
for cpu_id in "${CPUS_1[@]}"; do
    iptables -t mangle -A OUTPUT -m state --state NEW -m cpu --cpu "$cpu_id" -j CONNMARK --set-mark "$CONNMARK_1"
    iptables -t mangle -A OUTPUT -m state --state NEW -m cpu --cpu "$cpu_id" -j MARK --set-mark "$CONNMARK_1"
done

# Restore the connection mark for established connections so that packets use
# the same NIC regardless of other routing rules.
iptables -t mangle -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark

# Rewrite the source address after the packet is routed to the target interface
# so that replies return to the same NIC and pass VPC network validation.
iptables -t nat -A POSTROUTING -m mark --mark "$CONNMARK_0" -j SNAT --to-source "$SNAT_IP_0"
iptables -t nat -A POSTROUTING -m mark --mark "$CONNMARK_1" -j SNAT --to-source "$SNAT_IP_1"

後續步驟