複数のホスト NIC を使用するパターン(GPU VM)


A3 Ultra、A4、A4X などの一部のアクセラレータ最適化マシンには、これらのマシンの MRDMA インターフェースに加えて、2 つのホスト ネットワーク インターフェースがあります。ホストでは、これらは個別の CPU ソケットと不均一メモリアクセス(NUMA)ノードに接続されている Titanium IPU です。これらの IPU は、Google 仮想 NIC(gVNIC)として VM 内で使用可能であり、チェックポイント、トレーニング データの読み込み、モデルの読み込みなどのストレージ アクティビティや、その他の一般的なネットワーキングのニーズに対応するネットワーク帯域幅を提供します。gVNIC の NUMA トポロジを含むマシンの NUMA トポロジは、ゲスト オペレーティング システム(OS)に表示されます。

このドキュメントでは、これらのマシンで 2 つの gVNIC を使用する場合のベスト プラクティスについて説明します。

概要

一般に、複数のホスト NIC をどのように使用するかにかかわらず、次の構成を使用することをおすすめします。

  • ネットワーク設定: 各 gVNIC には一意の VPC ネットワークが必要です。VPC の設定では、次の点を考慮してください。
    • 各 VPC ネットワークに大きな最大伝送単位(MTU)を使用します。8896 はサポートされている最大 MTU であり、推奨される選択肢です。システムが受信側で受信データ パケットをドロップするため、一部のワークロードの入力パフォーマンスが低下する可能性があります。この問題は、ethtool ツールを使用して確認できます。このシナリオでは、TCP MSS、インターフェース MTU、VPC MTU を調整して、ページキャッシュからの効率的なデータ割り当てを可能にすると、受信レイヤ 2 フレームが 2 つの 4 KB バッファに収まるようになります。
  • アプリケーション設定
    • アプリケーションを NUMA に合わせて調整します。同じ NUMA ノードから CPU コア、メモリ割り当て、ネットワーク インターフェースを使用します。特定の NUMA ノードまたはネットワーク インターフェースを使用するためにアプリケーションの専用インスタンスを実行している場合は、numactl などのツールを使用して、アプリケーションの CPU とメモリリソースを特定の NUMA ノードに接続できます。
  • オペレーティング システムの設定
    • TCP セグメンテーション オフロード(TSO)と大規模な受信オフロード(LRO)を有効にします。
    • 各 gVNIC インターフェースで、その割り込みリクエスト(IRQ)がインターフェースと同じ NUMA ノードで処理され、割り込みがコア全体に分散されるように SMP アフィニティが設定されていることを確認します。Google 提供のゲスト OS イメージを実行している場合、このプロセスは google_set_multiqueue スクリプトを使用して自動的に行われます。
    • RFS、RPS、XPS などの設定を評価して、ワークロードに役立つかどうかを確認します。
    • A4X の場合、Nvidia は自動 NUMA スケジューリングを無効にすることを推奨しています。
    • これらのマシンでは、Linux カーネル ボンディングは gVNIC でサポートされていません。

複数のホスト NIC を使用するパターン

このセクションでは、Google Cloudで複数のホスト NIC を使用する一般的なパターンについて説明します。

サポートされているデプロイ パス
パターン サポートされているプロセス レイアウト GCE(全般) GKE SLURM
特定のインターフェースを使用するようにアプリケーションを変更する インターフェースごとのプロセス シャード アプリケーションのコード変更が必要
両方のインターフェースを使用するようにアプリケーションを変更する デュアル インターフェース プロセス アプリケーションのコード変更が必要
特定のアプリケーションに専用のネットワーク名前空間を使用する インターフェースごとのプロセス シャード ✅(特権コンテナのみ)
コンテナ全体のトラフィックを単一のインターフェースにマッピングする 1 つのインターフェースにマッピングされたすべてのコンテナ トラフィック
VPC をピアリングし、システムがインターフェース間でセッションをロードバランスできるようにする デュアル インターフェース プロセス ✅* ✅* ✅* NUMA アラインメントが困難または不可能。Linux カーネル 6.16 以降が必要*
ネットワーク間でトラフィックをシャーディングする デュアル インターフェース プロセス インターフェースごとにプロセス シャード ✅* ✅* ✅* デュアル インターフェース プロセスを実行している場合は、NUMA に合わせるためにコードの変更が必要になることがあります。
SNAT を使用して送信元インターフェースを選択する デュアル インターフェース プロセス インターフェースごとにプロセス シャード ✅(セットアップには管理者権限が必要です) ✅(セットアップには管理者権限が必要です) 正しく構成するのが難しい場合がある

* このオプションは一般的にはおすすめしませんが、x86(A3 Ultra と A4)プラットフォームの限定的なワークロードには役立つ場合があります。

特定のインターフェースを使用するようにアプリケーションを変更する

要件:

  • この方法では、アプリケーションのコードを変更する必要があります。
  • 次の 1 つ以上のメソッドの権限が必要です。
    • bind() は、特権ソースポートを使用する場合にのみ特別な権限を必要とします。
    • SO_BINDTODEVICE: CAP_NET_RAW 権限が必要です。
  • この方法では、ルートを確立して非対称ルーティングを防ぐために、カーネル ルーティング テーブルの変更が必要になることがあります。

概要

このパターンでは、次の操作を行います。

  1. 次のいずれかのオプションを使用して、アプリケーションのソースコードにネットワーク インターフェース バインディングを追加します。
    • bind() を使用して、ソケットを特定の送信元 IP アドレスにバインドする
    • SO_BINDTODEVICE ソケット オプションを使用して、ソケットを特定のネットワーク インターフェースにバインドする
  2. 必要に応じてカーネル ルーティング テーブルを変更して、送信元ネットワーク インターフェースから宛先アドレスへのルートが存在するようにします。また、非対称ルーティングを防ぐためにルートが必要になることもあります。追加のネットワーク インターフェースのルーティングを構成するの説明に従って、ポリシー ルーティングを構成することをおすすめします。
  3. numactl コマンドを使用してアプリケーションを実行することもできます。このアプローチでは、選択したネットワーク インターフェースと同じ NUMA ノードにあるメモリと CPU を使用します。

上記の手順を完了すると、アプリケーションのインスタンスは特定のネットワーク インターフェースを使用して実行されます。

両方のインターフェースを使用するようにアプリケーションを変更する

要件:

  • この方法では、アプリケーションのコードを変更する必要があります。
  • 次の 1 つ以上のメソッドに対する権限が必要です。
    • bind() は、特権ソースポートを使用する場合にのみ特別な権限を必要とします。
    • SO_BINDTODEVICE: CAP_NET_RAW 権限が必要です。
  • この方法では、ルートを確立して非対称ルーティングを防ぐために、カーネル ルーティング テーブルの変更が必要になることがあります。

概要

このパターンを実装するには、次の操作を行います。

  1. 次のいずれかのオプションを使用して、アプリケーションのソースコードにネットワーク インターフェース バインディングを追加します。
    1. bind() システムコールを使用して、ソケットを特定の送信元 IP アドレスにバインドする
    2. SO_BINDTODEVICE ソケット オプションを使用して、ソケットを特定のネットワーク インターフェースにバインドする
  2. アプリケーションがクライアントとして機能している場合は、送信元ネットワーク インターフェースごとに個別のクライアント ソケットを作成する必要があります。
  3. 必要に応じてカーネル ルーティング テーブルを変更して、送信元ネットワーク インターフェースから宛先アドレスへのルートが存在するようにします。また、非対称ルーティングを防ぐためにルートが必要になることもあります。追加のネットワーク インターフェースのルーティングを構成するの説明に従って、ポリシー ルーティングを構成することをおすすめします。
  4. ネットワーク アクティビティを、gVNIC インターフェースと同じ NUMA ノードで実行されるスレッドに分割することをおすすめします。スレッドに特定の NUMA ノードをリクエストする一般的な方法の 1 つは、pthread_setaffinity_np を呼び出すことです。
    1. アプリケーションは複数の NUMA ノードのリソースを利用するため、numactl の使用を避けるか、numactl コマンドにアプリケーションで使用されるすべてのネットワーク インターフェースの NUMA ノードが含まれるようにします。

特定のアプリに専用のネットワーク名前空間を使用する

要件:

  • CAP_SYS_ADMIN 機能が必要です。
  • GKE Autopilot と互換性がありません。
  • GKE を使用している場合は、特権コンテナが必要です。

このセクションでは、セカンダリ ネットワーク インターフェースを使用するネットワーク Namespace を作成するために使用できるパターンについて説明します。ワークロードに適したパターンは、特定のシナリオによって異なります。仮想スイッチまたは IPvlan を使用するアプローチは、複数のアプリケーションが異なるネットワーク名前空間からセカンダリ インターフェースを使用する必要がある場合に適しています。

概要: セカンダリ インターフェースを専用のネットワーク名前空間に移動する

このパターンでは、ネットワーク Namespace を作成し、セカンダリ gVNIC インターフェースを新しい Namespace に移動してから、この Namespace からアプリケーションを実行します。このパターンは、仮想スイッチを使用する場合よりも設定とチューニングが複雑にならない可能性があります。ただし、新しいネットワーク名前空間外のアプリケーションはセカンダリ gVNIC にアクセスできません。

次の例は、eth1 を second という新しいネットワーク Namespace に移動するために使用できる一連のコマンドを示しています。

ip netns add second
ip link set eth1 netns second
ip netns exec second ip addr add ${ETH1_IP}/${PREFIX} dev eth1
ip netns exec second ip link set dev eth1 up
ip netns exec second ip route add default via ${GATEWAY_IP} dev eth1
ip netns exec second <command>

このコマンドを実行すると、<command> 式がネットワーク名前空間内で実行され、eth1 インターフェースが使用されます。

新しいネットワーク Namespace 内で実行されているアプリケーションは、セカンダリ gVNIC を使用するようになりました。numactl コマンドを使用して、選択したネットワーク インターフェースと同じ NUMA ノードにあるメモリと CPU を使用してアプリケーションを実行することもできます。

概要: セカンダリ インターフェースに仮想スイッチとネットワーク Namespace を使用する このパターンでは、ネットワーク Namespace からセカンダリ gVNIC を使用するように仮想スイッチを設定します。

大まかな手順は次のとおりです。

  1. 仮想イーサネット(veth)デバイスのペアを作成します。各デバイスの最大伝送単位(MTU)を、セカンダリ gVNIC の MTU と一致するように調整します。
  2. 次のコマンドを実行して、IPv4 で IP 転送が有効になっていることを確認します。sysctl -w net.ipv4.ip_forward=1
  3. veth ペアの一方の端を新しいネットワーク Namespace に移動し、もう一方の端をルート Namespace に残します。
  4. veth デバイスからセカンダリ gVNIC インターフェースにトラフィックをマッピングします。これを行う方法はいくつかありますが、VM のセカンダリ インターフェースの IP エイリアス範囲を作成し、この範囲の IP アドレスを Namespace の子インターフェースに割り当てることをおすすめします。
  5. 新しいネットワーク名前空間からアプリケーションを実行します。numactl コマンドを使用すると、選択したネットワーク インターフェースと同じ NUMA ノードにあるメモリと CPU を使用してアプリケーションを実行できます。

ゲストとワークロードの設定によっては、veth デバイスを作成する代わりに、セカンダリ gVNIC にリンクされた IPvlan インターフェースで IPvlan ドライバを使用することもできます。

コンテナ全体のトラフィックを単一のインターフェースにマッピングする

要件:

  • アプリケーションは、コンテナ ネットワーキングにネットワーク名前空間を使用するコンテナ(GKE、Docker、Podman など)内で実行する必要があります。ホスト ネットワークは使用できません。

GKE、Docker、Podman などの多くのコンテナ テクノロジーは、コンテナのトラフィックを分離するために専用のネットワーク Namespace を使用します。このネットワーク名前空間は、直接変更することも、コンテナ テクノロジーのツールを使用してトラフィックを別のネットワーク インターフェースにマッピングすることもできます。

GKE では、Kubernetes 内部通信にプライマリ インターフェースが必要です。ただし、次の GKE Pod マニフェストに示すように、Pod のデフォルト ルートを変更してセカンダリ インターフェースを使用できます。

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

このアプローチでは、デフォルトのネットワーク インターフェースと CPU またはメモリ間の NUMA アライメントは保証されません。

VPC をピアリングし、システムがインターフェース間でセッションをロードバランスできるようにする

要件:

  • プライマリ gVNIC とセカンダリ gVNIC の VPC 間で VPC ピアリングを確立する必要があります。
  • 単一の宛先 IP とポートに送信する場合、送信元インターフェース間で TCP セッションをロードバランスするには、Linux カーネル バージョン 6.16 が必要です。
  • ネットワーキング スタックがクロスソケット メモリ転送を生成しても、ワークロードはパフォーマンス要件を満たすことができます。

概要

場合によっては、アプリケーション内またはアプリケーションのインスタンス間でネットワーク接続をシャーディングすることが困難なことがあります。このシナリオでは、クロス NUMA またはクロスソケット転送に影響を受けない A3U または A4 VM で実行されている一部のアプリケーションでは、2 つのインターフェースを代替可能として扱うと便利です。

この方法の 1 つは、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 for nic0
SECONDARY_IP=192.168.2.27  # internal IP nic1

sysctl -w net.ipv4.fib_multipath_hash_policy=1  # Enable L4 5-tuple ECMP hashing
ip route add <destination-network/subnet-mask> nexthop via ${PRIMARY_GW} nexthop
via ${SECONDARY_GW}

ネットワーク間でトラフィックをシャーディングする

要件:

  • VM の nic0nic1 は、別々の VPC とサブネットにあります。このパターンでは、宛先アドレスが nic0nic1 の VPC にシャーディングされている必要があります。

概要

デフォルトでは、Linux カーネルは nic0 のサブネットと nic1 のサブネットのルートを作成します。これにより、トラフィックは宛先ごとに適切なネットワーク インターフェースを介してルーティングされます。

たとえば、nic0 がサブネット subnet-a を含む VPC net1 を使用し、nic1 がサブネット subnet-b を含む VPC net2 を使用するとします。デフォルトでは、subnet-a のピア IP アドレスへの通信には nic0 が使用され、subnet-b のピア IP アドレスへの通信には nic1 が使用されます。たとえば、このシナリオは、net1 に接続されたピアのシングル NIC VM のセットと、net2 に接続されたセットで発生する可能性があります。

SNAT を使用して送信元インターフェースを選択する

要件:

  • CAP_NET_ADMIN は、アプリケーションの実行には必要ありませんが、初期 iptables ルールの設定には必要です。
  • ルールを他の重要な iptables ルールやルーティング構成と組み合わせて使用する場合は、ルールを慎重に評価する必要があります。

注:

  • NIC バインディングは、接続の作成時にのみ正しく設定されます。スレッドが別の NUMA ノードに関連付けられた CPU に移動すると、接続でクロス NUMA ペナルティが発生します。そのため、このソリューションは、スレッドを特定の CPU セットにバインドするメカニズムがある場合に最も効果的です。
  • このマシンから発信された接続のみが特定の NIC にバインドされます。受信接続は、宛先アドレスに一致する NIC に関連付けられます。

概要

ネットワーク Namespace の使用やアプリケーションの変更が難しいシナリオでは、NAT を使用して送信元インターフェースを選択できます。iptables などのツールを使用して、フローの送信元 IP を書き換え、cgroup、ユーザー、CPU などの送信アプリケーションのプロパティに基づいて特定のインターフェースの IP に一致させることができます。

次の例では、CPU ベースのルールを使用しています。最終的に、特定の CPU で実行されているスレッドから発信されたフローは、その CPU の対応する NUMA ノードに接続されている gVNIC によって転送されます。

# --- 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 ---

# This informs which interface to use for packets in each 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"

# This is not required for connections we originate, but replies to
# connections from peers need to know which interface to egress from.
# Add it before the fwmark rules to implicitly make sure 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"

# This informs which table to use 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.
# Otherwise, we will drop legitimate replies to the SNAT IPs.
sysctl -w net.ipv4.conf."$OUTPUT_INTERFACE_0".rp_filter=2
sysctl -w net.ipv4.conf."$OUTPUT_INTERFACE_1".rp_filter=2

# Mark packets/connections with a per-nic mark based on the source CPU.
# The `fwmark` rules will then use 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

# For established connections, restore the connection mark.
# Otherwise, we will send the packet to the wrong NIC, depending on existing
# routing rules.
iptables -t mangle -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark

# These rules NAT the source address after the packet is already destined to
# egress the correct interface. This lets replies to this flow target the correct NIC,
# and may be required to be accepted into the VPC.
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"