Modèles d'utilisation de plusieurs cartes d'interface réseau hôtes (VM avec GPU)


Certaines machines optimisées pour les accélérateurs, y compris les machines A3 Ultra, A4 et A4X, disposent de deux interfaces réseau hôtes en plus des interfaces MRDMA sur ces machines. Sur l'hôte, il s'agit d'IPU Titanium branchées sur des sockets de processeur et des nœuds NUMA (Non-Uniform Memory Access) distincts. Ces IPU sont disponibles dans la VM en tant que cartes d'interface réseau virtuelles Google (gVNIC). Elles fournissent une bande passante réseau pour les activités de stockage telles que la création de points de contrôle, le chargement de données d'entraînement, le chargement de modèles et d'autres besoins généraux en matière de réseau. La topologie NUMA de la machine, y compris celle des gVNIC, est visible par le système d'exploitation invité.

Ce document décrit les bonnes pratiques à adopter pour utiliser les deux cartes gVNIC sur ces machines.

Présentation

En général, nous vous recommandons d'utiliser les configurations suivantes, quelle que soit la façon dont vous prévoyez d'utiliser plusieurs cartes d'interface réseau hôte :

  • Paramètres réseau : chaque gVNIC doit disposer d'un réseau VPC unique. Pour une configuration de VPC, tenez compte des éléments suivants :
    • Utilisez une unité de transmission maximale (MTU) élevée pour chaque réseau VPC. 8 896 est la MTU maximale acceptée et un choix recommandé. Les performances d'entrée de certaines charges de travail peuvent être ralenties, car le système supprime les paquets de données entrants côté récepteur. Vous pouvez utiliser l'outil ethtool pour vérifier ce problème. Dans ce scénario, il peut être utile d'ajuster la MSS TCP, la MTU d'interface ou la MTU de VPC pour permettre une allocation efficace des données à partir du cache de page, ce qui permet au frame de couche 2 entrant de tenir dans deux tampons de 4 Ko.
  • Paramètres de l'application
    • Alignez l'application sur NUMA. Utilisez les cœurs de processeur, les allocations de mémoire et une interface réseau du même nœud NUMA. Si vous exécutez une instance dédiée de l'application pour utiliser un nœud NUMA ou une interface réseau spécifiques, vous pouvez utiliser des outils tels que numactl pour associer les ressources de processeur et de mémoire de l'application à un nœud NUMA spécifique.
  • Paramètres du système d'exploitation
    • Activez le déchargement de la segmentation par TCP (TSO) et le déchargement de réception volumineux (LRO).
    • Pour chaque interface gVNIC, assurez-vous que l'affinité SMP est configurée de sorte que ses demandes d'interruption (IRQ) soient traitées sur le même nœud NUMA que l'interface et que les interruptions soient réparties sur les cœurs. Si vous exécutez une image d'OS invité fournie par Google, ce processus se déroule automatiquement à l'aide du script google_set_multiqueue.
    • Évaluez les paramètres tels que RFS, RPS et XPS pour voir s'ils peuvent être utiles pour votre charge de travail.
    • Pour A4X, Nvidia recommande de désactiver la planification NUMA automatique.
    • L'agrégation du noyau Linux n'est pas compatible avec les gVNIC sur ces machines.

Modèles d'utilisation de plusieurs cartes d'interface réseau hôtes

Cette section décrit les modèles généraux d'utilisation de plusieurs cartes d'interface réseau hôtes surGoogle Cloud.

Chemins de déploiement compatibles
Format Mise en page des processus acceptée GCE (général) GKE SLURM Remarques
Modifier l'application pour utiliser une interface spécifique Traiter le shard par interface Nécessite des modifications de code dans l'application
Modifier l'application pour utiliser les deux interfaces Processus à double interface Nécessite des modifications de code dans l'application
Utiliser un espace de noms réseau dédié pour des applications spécifiques Traiter le shard par interface ✅ (conteneurs privilégiés uniquement)
Mapper le trafic d'un conteneur entier à une seule interface Tout le trafic de conteneur mappé à une interface
Appairez les VPC et laissez le système équilibrer la charge des sessions sur les interfaces. Processus à double interface ✅* ✅* ✅* Alignement NUMA difficile ou impossible Nécessite Linux Kernel 6.16 ou version ultérieure*
Répartir le trafic sur les réseaux Processus à double interface : un shard de processus par interface ✅* ✅* ✅* Peut nécessiter des modifications de code pour l'alignement NUMA si vous exécutez un processus à double interface.
Utiliser SNAT pour choisir l'interface source Processus à double interface : un shard de processus par interface ✅ (la configuration nécessite des droits d'administrateur) ✅ (la configuration nécessite des droits d'administrateur) La configuration peut être plus difficile.

* Cette option n'est généralement pas recommandée, mais peut être utile pour les charges de travail limitées sur les plates-formes x86 (A3 Ultra et A4).

Modifier l'application pour qu'elle utilise une interface spécifique

Exigences :

  • Cette méthode nécessite de modifier le code de votre application.
  • Nécessite des autorisations pour une ou plusieurs des méthodes suivantes :
    • bind() ne nécessite des autorisations spéciales que si vous utilisez un port source privilégié.
    • SO_BINDTODEVICE : nécessite l'autorisation CAP_NET_RAW.
  • Cette méthode peut vous obliger à modifier la table de routage de votre noyau pour établir des routes et éviter le routage asymétrique.

Présentation générale

Avec ce modèle, vous allez effectuer les opérations suivantes :

  1. Ajoutez la liaison d'interface réseau au code source de votre application en utilisant l'une des options suivantes :
    • Utilisez bind() pour associer un socket à une adresse IP source spécifique.
    • Utilisez l'option de socket SO_BINDTODEVICE pour associer un socket à une interface réseau spécifique.
  2. Modifiez la table de routage du noyau si nécessaire pour vous assurer qu'un itinéraire existe entre l'interface réseau source et l'adresse de destination. De plus, des routes peuvent être nécessaires pour éviter le routage asymétrique. Nous vous recommandons de configurer la liaison de règles comme décrit dans Configurer le routage pour une interface réseau supplémentaire.
  3. Vous pouvez également utiliser la commande numactl pour exécuter votre application. Dans cette approche, vous utilisez la mémoire et les processeurs qui se trouvent sur le même nœud NUMA que l'interface réseau de votre choix.

Une fois les étapes précédentes terminées, les instances de votre application s'exécutent à l'aide d'une interface réseau spécifique.

Modifier l'application pour utiliser les deux interfaces

Exigences :

  • Cette méthode nécessite de modifier le code de votre application.
  • Vous devez disposer des autorisations pour une ou plusieurs des méthodes suivantes :
    • bind() ne nécessite des autorisations spéciales que si vous utilisez un port source privilégié.
    • SO_BINDTODEVICE : nécessite l'autorisation CAP_NET_RAW.
  • Cette méthode peut vous obliger à modifier la table de routage de votre noyau pour établir des routes et éviter le routage asymétrique.

Présentation générale

Pour implémenter ce modèle, procédez comme suit :

  1. Ajoutez la liaison d'interface réseau au code source de votre application en utilisant l'une des options suivantes :
    1. Utilisez l'appel système bind() pour associer un socket à une adresse IP source spécifique.
    2. Utilisez l'option de socket SO_BINDTODEVICE pour associer un socket à une interface réseau spécifique.
  2. Si votre application agit en tant que client, vous devrez créer un socket client distinct pour chaque interface réseau source.
  3. Modifiez la table de routage du noyau si nécessaire pour vous assurer qu'un itinéraire existe entre l'interface réseau source et l'adresse de destination. De plus, vous pouvez également avoir besoin de routes pour éviter le routage asymétrique. Nous vous recommandons de configurer le routage des règles comme décrit dans Configurer le routage pour une interface réseau supplémentaire.
  4. Nous vous recommandons de partitionner l'activité réseau en threads qui s'exécutent sur le même nœud NUMA que l'interface gVNIC. Une méthode courante pour demander un nœud NUMA spécifique pour un thread consiste à appeler pthread_setaffinity_np.
    1. Étant donné que l'application utilise des ressources sur plusieurs nœuds NUMA, évitez d'utiliser numactl ou assurez-vous que votre commande numactl inclut les nœuds NUMA de toutes les interfaces réseau utilisées par votre application.

Utiliser un espace de noms réseau dédié pour des applications spécifiques

Exigences :

  • Nécessite la fonctionnalité CAP_SYS_ADMIN.
  • Non compatible avec GKE Autopilot.
  • Si vous utilisez GKE, vous devez disposer d'un conteneur privilégié.

Cette section décrit les modèles que vous pouvez utiliser pour créer un espace de noms réseau qui utilise une interface réseau secondaire. Le modèle adapté à votre charge de travail dépend de votre scénario spécifique. Les approches qui utilisent un commutateur virtuel ou IPvlan sont mieux adaptées aux cas où plusieurs applications doivent utiliser l'interface secondaire à partir de différents espaces de noms réseau.

Présentation générale : déplacer l'interface secondaire dans un espace de noms réseau dédié

Ce modèle consiste à créer un espace de noms réseau, à déplacer l'interface gVNIC secondaire dans le nouvel espace de noms, puis à exécuter l'application à partir de cet espace de noms. Ce modèle peut être plus simple à configurer et à ajuster que l'utilisation d'un commutateur virtuel. Toutefois, les applications situées en dehors du nouvel espace de noms réseau ne pourront pas accéder à la carte gVNIC secondaire.

L'exemple suivant montre une série de commandes qui peuvent être utilisées pour déplacer eth1 dans le nouvel espace de noms réseau appelé "second".

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>

Lorsque cette commande est exécutée, l'expression <command> est ensuite exécutée dans l'espace de noms réseau et utilise l'interface eth1.

Les applications exécutées dans le nouvel espace de noms réseau utilisent désormais l'interface gVNIC secondaire. Vous pouvez également utiliser la commande numactl pour exécuter votre application en utilisant la mémoire et les processeurs qui se trouvent sur le même nœud NUMA que l'interface réseau de votre choix.

Présentation générale : utiliser un commutateur virtuel et un espace de noms réseau pour une interface secondaire Ce modèle consiste à créer une configuration de commutateur virtuel pour utiliser le gVNIC secondaire à partir d'un espace de noms réseau.

Voici les grandes étapes à suivre :

  1. Créez une paire d'appareils Ethernet virtuels (veth). Ajustez l'unité de transmission maximale (MTU) sur chacun des appareils pour qu'elle corresponde à celle de la gVNIC secondaire.
  2. Exécutez la commande suivante pour vous assurer que le transfert IP est activé pour IPv4 : sysctl -w net.ipv4.ip_forward=1
  3. Déplacez une extrémité de la paire veth dans un nouvel espace de noms réseau et laissez l'autre extrémité dans l'espace de noms racine.
  4. Mappez le trafic du périphérique veth à l'interface gVNIC secondaire. Il existe plusieurs façons de procéder, mais nous vous recommandons de créer une plage d'adresses IP d'alias pour l'interface secondaire de la VM et d'attribuer une adresse IP de cette plage à l'interface enfant dans l'espace de noms.
  5. Exécutez l'application à partir du nouvel espace de noms réseau. Vous pouvez utiliser la commande numactl pour exécuter votre application en utilisant la mémoire et les processeurs qui se trouvent sur le même nœud NUMA que l'interface réseau choisie.

Selon la configuration de l'invité et de la charge de travail, vous pouvez également utiliser le pilote IPvlan avec une interface IPvlan associée au gVNIC secondaire au lieu de créer les appareils veth.

Mapper le trafic d'un conteneur entier à une seule interface

Exigences :

  • Votre application doit s'exécuter dans un conteneur qui utilise un espace de noms réseau pour la mise en réseau des conteneurs, tel que GKE, Docker ou Podman. Vous ne pouvez pas utiliser le réseau hôte.

De nombreuses technologies de conteneur, telles que GKE, Docker et Podman, utilisent un espace de noms réseau dédié pour un conteneur afin d'isoler son trafic. Cet espace de noms réseau peut ensuite être modifié, directement ou à l'aide des outils de la technologie de conteneur, pour mapper le trafic sur une autre interface réseau.

GKE exige que l'interface principale soit présente pour la communication interne de Kubernetes. Toutefois, la route par défaut du pod peut être modifiée pour utiliser l'interface secondaire, comme indiqué dans le fichier manifeste du pod GKE suivant.

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

Cette approche ne garantit pas l'alignement NUMA entre l'interface réseau par défaut et les processeurs ou la mémoire.

Appairez les VPC et laissez le système équilibrer la charge des sessions sur les interfaces.

Exigences :

  • L'appairage de VPC doit être établi entre les VPC des cartes gVNIC principales et secondaires.
  • La version 6.16 du noyau Linux est requise pour équilibrer la charge des sessions TCP sur les interfaces sources si elles sont envoyées à une seule adresse IP et à un seul port de destination.
  • La charge de travail peut toujours répondre à vos exigences de performances lorsque la pile réseau génère des transferts de mémoire entre les sockets.

Présentation générale

Dans certains cas, il est difficile de partitionner les connexions réseau au sein d'une application ou entre les instances d'une application. Dans ce scénario, pour certaines applications s'exécutant sur des VM A3U ou A4 qui ne sont pas sensibles au transfert cross-NUMA ou cross-socket, il peut être pratique de traiter les deux interfaces comme fongibles.

Pour ce faire, vous pouvez utiliser le sysctl fib_multipath_hash_policy et un itinéraire multipath :

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}

Répartir le trafic sur les réseaux

Exigences :

  • nic0 et nic1 sur la VM se trouvent dans des VPC et des sous-réseaux distincts. Ce modèle nécessite que les adresses de destination soient réparties entre les VPC nic0 et nic1.

Présentation générale

Par défaut, le noyau Linux crée des routes pour le sous-réseau de nic0 et celui de nic1, qui achemineront le trafic par destination via l'interface réseau appropriée.

Par exemple, supposons que nic0 utilise le VPC net1 avec le sous-réseau subnet-a, et que nic1 utilise le VPC net2 avec le sous-réseau subnet-b. Par défaut, les communications vers les adresses IP de pairs dans subnet-a utiliseront nic0, et les communications vers les adresses IP de pairs dans subnet-b utiliseront nic1. Par exemple, ce scénario peut se produire avec un ensemble de VM à une seule interface réseau connectées à net1 et un ensemble connecté à net2.

Utiliser SNAT pour choisir l'interface source

Exigences :

  • CAP_NET_ADMIN est requis pour configurer les règles iptables initiales, mais pas pour exécuter l'application.
  • Vous devez évaluer attentivement les règles lorsque vous les utilisez en combinaison avec d'autres règles iptables ou configurations de routage non triviales.

Remarque :

  • L'association de la carte d'interface réseau n'est correcte qu'au moment de la création de la connexion. Si un thread est déplacé vers un processeur associé à un autre nœud NUMA, la connexion subira des pénalités cross-NUMA. Par conséquent, cette solution est particulièrement utile lorsqu'il existe un mécanisme permettant de lier les threads à des ensembles de processeurs spécifiques.
  • Seules les connexions initiées par cette machine seront liées à une carte d'interface réseau spécifique. Les connexions entrantes seront associées à la carte d'interface réseau correspondant à l'adresse à laquelle elles sont destinées.

Présentation générale

Dans les scénarios où il est difficile d'utiliser des espaces de noms réseau ou d'apporter des modifications à l'application, vous pouvez utiliser NAT pour choisir une interface source. Vous pouvez utiliser des outils tels qu'iptables pour réécrire l'adresse IP source d'un flux afin qu'elle corresponde à l'adresse IP d'une interface spécifique en fonction d'une propriété de l'application émettrice, telle que cgroup, l'utilisateur ou le processeur.

L'exemple suivant utilise des règles basées sur le processeur. Le résultat final est qu'un flux provenant d'un thread exécuté sur un processeur donné est transmis par le gVNIC associé au nœud NUMA correspondant à ce processeur.

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