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

Ce document décrit les modèles de configuration et les bonnes pratiques à suivre pour utiliser les deux cartes d'interface réseau (NIC) de l'hôte pour le stockage et la gestion sur les instances Compute Engine.

Présentation

Certaines séries de machines optimisées pour les accélérateurs, y compris A5X, A4X Max, A4X, A4 et A3 Ultra, disposent de deux interfaces réseau LAN hôtes pour le stockage et la gestion qui s'associent aux réseaux cloud privé virtuel (VPC) standards. Ces machines disposent également d'interfaces réseau MRDMA qui doivent être associées à un réseau VPC RoCE (accès direct à la mémoire à distance sur Ethernet convergé).

Ce document se concentre sur la configuration des interfaces LAN hôtes (gVNIC ou IDPF) sur les réseaux VPC standards, et non sur les interfaces réseau RDMA. Les interfaces LAN de l'hôte sont des unités de traitement d'infrastructure (IPU) Titanium qui se connectent à des sockets de processeur et à des nœuds NUMA (Non-Uniform Memory Access) distincts. Dans l'instance de calcul, ces IPU apparaissent sous la forme d'interfaces réseau Google Virtual NIC (gVNIC) ou Infrastructure Data Path Function (IDPF). Ces interfaces fournissent une bande passante réseau pour les activités de stockage et de gestion, telles que la création de points de contrôle, le chargement des données d'entraînement et le chargement des modèles. La topologie NUMA de la machine, y compris la topologie des interfaces réseau, est visible par le système d'exploitation (OS) invité.

Utilisez les configurations suivantes, quel que soit le modèle que vous choisissez pour plusieurs cartes d'interface réseau hôtes :

  • Paramètres réseau : configurez chaque interface gVNIC ou IDPF pour utiliser un réseau VPC distinct. Pour votre configuration de VPC, effectuez les configurations suivantes :
    • Configurez une unité de transmission maximale (MTU) élevée pour chaque réseau VPC, par exemple 8896 (la MTU maximale acceptée).
    • Si les paquets de données entrants perdus côté récepteur ralentissent les performances d'entrée de votre charge de travail, recherchez les paquets perdus à l'aide de l'utilitaire ethtool. Pour optimiser l'allocation des données à partir du cache de pages afin que chaque trame de couche 2 entrante tienne dans deux tampons de 4 Ko, ajustez la taille maximale de segment (MSS) TCP, la MTU de l'interface ou la MTU du VPC.
  • Paramètres de l'application :
    • Alignez votre application sur NUMA en utilisant des cœurs de processeur, des 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 un nœud NUMA ou une interface réseau spécifique, liez les ressources de processeur et de mémoire de l'application à ce nœud NUMA à l'aide d'un outil tel que numactl.
  • Paramètres du système d'exploitation :
    • Activez le déchargement de la segmentation TCP (TSO) et le déchargement de réception volumineux (LRO).
    • Pour chaque interface gVNIC ou IDPF, configurez l'affinité de multitraitement symétrique (SMP) pour acheminer les requêtes d'interruption (IRQ) vers le même nœud NUMA que l'interface et répartir les interruptions entre les cœurs de processeur. Si vous utilisez une image Linux fournie par Google, l'agent invité Compute Engine exécute le script google_set_multiqueue au démarrage pour appliquer automatiquement cette configuration. Si vous utilisez une image personnalisée, installez l'environnement invité. Si le script ne s'exécute pas automatiquement, exécutez la commande sudo /usr/bin/google_set_multiqueue.
    • Évaluez les paramètres Receive Flow Steering (RFS), Receive Packet Steering (RPS) et Transmit Packet Steering (XPS) pour déterminer s'ils sont bénéfiques pour votre charge de travail.
    • Pour les instances A4X Max et A4X, NVIDIA vous recommande de désactiver la planification NUMA automatique.
    • N'utilisez pas l'agrégation de liens du noyau Linux avec les interfaces gVNIC ou IDPF sur ces machines, car l'agrégation de liens du noyau n'est pas compatible.

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

Le tableau suivant décrit les modèles généraux d'utilisation de plusieurs cartes d'interface réseau hôtes surGoogle Cloud :

Modèles généraux d'utilisation de plusieurs cartes d'interface réseau hôtes
Modèle Mise en page de processus compatible Chemin de déploiement : Compute Engine Chemin de déploiement : Google Kubernetes Engine Chemin de déploiement : Slurm Remarques
Modifier une application pour utiliser une interface spécifique Segment de processus par interface Oui Oui Oui Des modifications de code sont requises dans l'application.
Modifier une application pour utiliser les deux interfaces Processus à double interface Oui Oui Oui Des modifications de code sont requises dans l'application.
Utiliser un espace de noms réseau dédié pour des applications spécifiques Segment de processus par interface Oui Oui (conteneurs privilégiés uniquement) Non Aucun
Mapper le trafic d'un conteneur entier vers une seule interface Tout le trafic des conteneurs est mappé à une seule interface Oui Oui Non Aucun
Utiliser l'appairage de VPC et laisser le système équilibrer la charge des sessions entre les interfaces Processus à double interface Oui Oui Oui Difficile ou impossible à aligner sur NUMA. Nécessite le noyau Linux 6.16 ou une version ultérieure.
Segmenter le trafic sur plusieurs réseaux
  • Processus à double interface
  • Segment de processus par interface
Oui Oui Oui Peut nécessiter des modifications de code pour l'alignement NUMA si vous exécutez un processus à double interface.
Utiliser la traduction d'adresse réseau source (SNAT) pour choisir l'interface source
  • Processus à double interface
  • Segment de processus par interface
Oui Oui (la configuration nécessite des droits d'administrateur) Oui (la configuration nécessite des droits d'administrateur) Peut être difficile à configurer.

Remarques

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

Exigences :

  • Nécessite des modifications du code de votre application.
  • Nécessite des autorisations pour une ou plusieurs des méthodes suivantes :
    • bind() : nécessite des autorisations spéciales uniquement si vous utilisez un port source privilégié.
    • SO_BINDTODEVICE : nécessite l'autorisation CAP_NET_RAW.
  • Vous devrez peut-être modifier votre table de routage du noyau pour établir des routes et empêcher le routage asymétrique.

Vue d'ensemble

Pour configurer votre application afin qu'elle utilise une interface réseau spécifique, procédez comme suit :

  1. Ajoutez une liaison pour une interface réseau au code source de votre application à l'aide de l'une des options suivantes :
    • Utilisez l'appel système bind() pour lier un socket à une adresse IP source spécifique.
    • Utilisez l'option de socket SO_BINDTODEVICE pour lier un socket à une interface réseau spécifique.
  2. Modifiez la table de routage du noyau si nécessaire pour vous assurer qu'il existe une route entre l'interface réseau source et l'adresse de destination. Pour éviter le routage asymétrique, configurez le routage des règles comme décrit dans Configurer le routage pour une interface réseau supplémentaire.
  3. Facultatif : Exécutez votre application sur la mémoire et les processeurs qui partagent le même nœud NUMA que l'interface réseau que vous avez choisie à l'aide de la commande numactl.

Une fois ces étapes terminées, votre application utilise l'interface réseau spécifiée.

Modifier une application pour utiliser les deux interfaces

Exigences :

  • Nécessite des modifications du code de votre application.
  • Nécessite des autorisations pour une ou plusieurs des méthodes suivantes :
    • bind() : nécessite des autorisations spéciales uniquement si vous utilisez un port source privilégié.
    • SO_BINDTODEVICE : nécessite l'autorisation CAP_NET_RAW.
  • Vous devrez peut-être modifier votre table de routage du noyau pour établir des routes et empêcher le routage asymétrique.

Vue d'ensemble

Pour configurer votre application afin qu'elle utilise les deux interfaces réseau, procédez comme suit :

  1. Ajoutez une liaison pour chaque interface réseau au code source de votre application à l'aide de l'une des options suivantes :
    • Utilisez l'appel système bind() pour lier chaque socket à l'adresse IP source de l'interface réseau correspondante.
    • Utilisez l'option de socket SO_BINDTODEVICE pour lier chaque socket à une interface réseau spécifique.
  2. Si votre application agit en tant que client, créez 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'il existe une route entre chaque interface réseau source et l'adresse de destination. Pour éviter le routage asymétrique, configurez le routage des règles comme décrit dans Configurer le routage pour une interface réseau supplémentaire.
  4. Partitionnez l'activité réseau en threads qui s'exécutent sur le même nœud NUMA que l'interface gVNIC ou IDPF correspondante, par exemple en appelant pthread_setaffinity_np. É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.
  • N'est pas compatible avec GKE Autopilot.
  • Si vous utilisez GKE, vous devez utiliser un conteneur privilégié.

Cette section décrit les modèles que vous pouvez utiliser pour créer un espace de noms réseau pour une interface réseau secondaire. Le meilleur modèle pour votre charge de travail dépend de votre scénario. Les approches qui utilisent un commutateur virtuel ou IPvlan fonctionnent mieux lorsque plusieurs applications doivent partager l'interface secondaire à partir de différents espaces de noms réseau.

Déplacer l'interface secondaire vers un espace de noms réseau dédié

Avec ce modèle, vous créez un espace de noms réseau, vous déplacez l'interface gVNIC ou IDPF secondaire vers le nouvel espace de noms, puis vous exécutez l'application à partir de cet espace de noms. Ce modèle est plus simple à configurer et à régler qu'un commutateur virtuel, mais les applications situées en dehors du nouvel espace de noms réseau ne peuvent pas accéder à l'interface réseau secondaire.

Pour déplacer l'interface eth1 dans un espace de noms réseau nommé app-net et exécuter une commande dans cet espace de noms, exécutez les commandes suivantes :

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

Remplacez les éléments suivants :

  • ETH1_IP : adresse IP interne de l'interface eth1.
  • PREFIX : longueur du préfixe de sous-réseau pour l'interface eth1.
  • GATEWAY_IP : adresse IP de la passerelle par défaut pour le sous-réseau eth1.
  • COMMAND : commande à exécuter dans l'espace de noms réseau app-net.

Lorsque vous exécutez ces commandes, COMMAND s'exécute dans l'espace de noms réseau app-net et utilise l'interface eth1.

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

Utiliser un commutateur virtuel et un espace de noms réseau pour une interface secondaire

Avec ce modèle, vous configurez un commutateur virtuel pour accéder à l'interface gVNIC ou IDPF secondaire à partir d'un espace de noms réseau :

  1. Ouvrez une connexion SSH à l'instance de calcul. Pour en savoir plus, consultez Se connecter à des VM Linux.
  2. Créez une paire d'appareils Virtual Ethernet (veth) à l'aide de la commande ip link add, par exemple ip link add veth0 type veth peer name veth1.
  3. Ajustez la MTU sur chaque appareil pour qu'elle corresponde à la MTU de l'interface secondaire à l'aide de la commande ip link set :

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

    Remplacez MTU par la MTU de l'interface secondaire (par exemple, 8896).

  4. Activez le transfert de paquets IPv4 :

    sysctl -w net.ipv4.ip_forward=1
    
  5. Déplacez une extrémité de la paire veth vers un nouvel espace de noms réseau et laissez l'autre extrémité dans l'espace de noms racine.

  6. Mappez le trafic de l'appareil veth vers l'interface secondaire. Pour configurer ce mappage, créez une plage d'adresses IP d'alias pour l'interface secondaire de l'instance de calcul et attribuez une adresse IP de cette plage à l'interface enfant dans l'espace de noms.

  7. 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 sur le même nœud NUMA que l'interface réseau choisie.

Selon votre OS invité et la configuration de votre charge de travail, vous pouvez également associer une interface IPvlan à l'interface gVNIC ou IDPF secondaire au lieu de créer des appareils veth.

Mapper le trafic d'un conteneur entier vers 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, comme GKE, Docker ou Podman. Vous ne pouvez pas utiliser le réseau hôte.

Les environnements d'exécution de conteneurs tels que GKE, Docker et Podman utilisent un espace de noms réseau dédié pour chaque conteneur afin d'isoler son trafic. Vous pouvez modifier cet espace de noms réseau, soit directement, soit à l'aide des outils de l'environnement d'exécution du conteneur, pour mapper le trafic vers une autre interface réseau.

GKE nécessite l'interface principale pour la communication Kubernetes interne. Toutefois, vous pouvez modifier la route par défaut dans le pod pour utiliser l'interface secondaire, comme indiqué dans le fichier manifeste de 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 attribués.

Utiliser l'appairage de VPC et laisser le système équilibrer la charge des sessions entre les interfaces

Exigences :

  • Vous devez établir un appairage de réseaux VPC entre les réseaux VPC des cartes d'interface réseau hôtes principale et secondaire.
  • Vous devez utiliser la version 6.16 ou ultérieure du noyau Linux pour équilibrer la charge des sessions TCP sur les interfaces sources lors de l'envoi de trafic vers une adresse IP et un port de destination uniques.
  • Votre charge de travail doit tolérer les transferts de mémoire entre sockets sans manquer ses objectifs de performances.

Vue d'ensemble

Si vous ne pouvez pas facilement segmenter les connexions réseau au sein d'une application ou entre des instances d'application, et que votre charge de travail s'exécute sur des instances de calcul A4 ou A3 Ultra qui ne sont pas sensibles aux transferts entre NUMA ou entre sockets, vous pouvez traiter les deux interfaces comme fongibles.

Pour configurer ce comportement, activez le paramètre sysctl fib_multipath_hash_policy et ajoutez une route multichemin :

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}

Remplacez les éléments suivants :

  • DESTINATION_NETWORK : plage d'adresses IP internes de destination.
  • SUBNET_MASK : longueur du préfixe de sous-réseau pour le réseau de destination.

Segmenter le trafic sur plusieurs réseaux

Exigences :

  • nic0 et nic1 sur l'instance de calcul doivent se connecter à des réseaux et sous-réseaux VPC distincts, et les adresses IP de destination doivent être segmentées sur les deux réseaux VPC.

Vue d'ensemble

Par défaut, le noyau Linux crée des routes pour les sous-réseaux nic0 et nic1 qui acheminent le trafic par adresse IP de destination via l'interface réseau correspondante.

Par exemple, supposons que nic0 utilise le réseau VPC net1 avec le sous-réseau subnet-a, et que nic1 utilise le réseau VPC net2 avec le sous-réseau subnet-b. Par défaut, le trafic vers les adresses IP de pairs dans subnet-a utilise nic0, et le trafic vers les adresses IP de pairs dans subnet-b utilise nic1. Cette configuration s'applique généralement lorsqu'un ensemble d'instances de calcul pairs à carte d'interface réseau unique se connecte à net1 et qu'un deuxième ensemble se connecte à net2.

Utiliser la SNAT pour choisir l'interface source

Exigences :

  • Nécessite la fonctionnalité CAP_NET_ADMIN pour configurer les règles iptables initiales, mais pas pour exécuter l'application.
  • Nécessite une évaluation minutieuse lorsqu'elle est combinée à d'autres règles iptables ou configurations de routage non triviales.

Vue d'ensemble

Si vous ne pouvez pas utiliser d'espaces de noms réseau ni modifier votre application, vous pouvez utiliser SNAT pour sélectionner une interface source. En utilisant un outil tel que iptables, vous pouvez réécrire l'adresse IP source d'un flux pour qu'elle corresponde à l'adresse IP d'une interface spécifique en fonction d'une propriété de l'application émettrice, telle que son cgroup, son utilisateur ou son processeur.

L'exemple suivant utilise des règles basées sur le processeur afin que tout flux provenant d'un thread sur un processeur donné sorte par l'interface gVNIC ou IDPF associée au nœud NUMA de 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 ---

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

Étapes suivantes