Patrones para usar varias NIC de host

En este documento, se describen los patrones de configuración y las prácticas recomendadas para usar las dos interfaces de red (NIC) de LAN del host para el almacenamiento y la administración en instancias de Compute Engine.

Descripción general

Algunas series de máquinas optimizadas para aceleradores, incluidas A5X, A4X Max, A4X, A4 y A3 Ultra, tienen dos interfaces de red LAN de host para el almacenamiento y la administración que se conectan a redes de nube privada virtual (VPC) normales. Estas máquinas también tienen interfaces de red MRDMA que deben conectarse a una red de VPC de acceso remoto directo a memoria sobre Ethernet convergente (RoCE).

Este documento se enfoca en configurar las interfaces de LAN del host (gVNIC o IDPF) en redes de VPC normales, no en interfaces de red de RDMA. Las interfaces de LAN del host son unidades de procesamiento de infraestructura (IPU) Titanium que se conectan a zócalos de CPU independientes y nodos de acceso a la memoria no uniforme (NUMA). Dentro de la instancia de procesamiento, estas IPU aparecen como NIC virtuales de Google (gVNIC) o interfaces de red de función de ruta de acceso de datos de infraestructura (IDPF). Estas interfaces proporcionan ancho de banda de red para las actividades de almacenamiento y administración, como la creación de puntos de control, la carga de datos de entrenamiento y la carga de modelos. La topología de NUMA de la máquina, incluida la topología de las interfaces de red, es visible para el sistema operativo (SO) invitado.

Usa los siguientes parámetros de configuración, sin importar el patrón que elijas para las NIC de varios hosts:

  • Configuración de red: Configura cada interfaz de gVNIC o IDPF para usar una red de VPC independiente. Para configurar la VPC, completa los siguientes pasos:
    • Configura una unidad de transmisión máxima (MTU) grande para cada red de VPC, como 8896 (la MTU máxima admitida).
    • Si los paquetes de datos entrantes descartados en el lado del receptor ralentizan el rendimiento de entrada de tu carga de trabajo, verifica si hay paquetes descartados con la utilidad ethtool. Para optimizar la asignación de datos de la caché de páginas, de modo que cada trama de capa 2 entrante quepa en dos búferes de 4 KB, ajusta el tamaño máximo de segmento (MSS) de TCP, la MTU de la interfaz o la MTU de la VPC.
  • Configuración de la aplicación:
    • Alinea tu aplicación con NUMA usando núcleos de CPU, asignaciones de memoria y una interfaz de red del mismo nodo de NUMA. Si ejecutas una instancia dedicada de la aplicación para un nodo de NUMA o una interfaz de red específicos, vincula los recursos de CPU y memoria de la aplicación a ese nodo de NUMA con una herramienta como numactl.
  • Configuración del sistema operativo:
    • Habilita la descarga de segmentación de TCP (TSO) y la descarga de recepción grande (LRO).
    • Para cada interfaz de gVNIC o IDPF, configura la afinidad de multiprocesamiento simétrico (SMP) para enrutar las solicitudes de interrupción (IRQ) al mismo nodo NUMA que la interfaz y distribuir las interrupciones entre los núcleos de CPU. Si usas una imagen de Linux proporcionada por Google, el agente invitado de Compute Engine ejecuta la secuencia de comandos google_set_multiqueue en el inicio para aplicar esta configuración de forma automática. Si usas una imagen personalizada, instala el entorno de invitado. Si la secuencia de comandos no se ejecuta automáticamente, ejecuta el comando sudo /usr/bin/google_set_multiqueue.
    • Evalúa la configuración de Receive Flow Steering (RFS), Receive Packet Steering (RPS) y Transmit Packet Steering (XPS) para determinar si benefician tu carga de trabajo.
    • Para las instancias A4X Max y A4X, NVIDIA recomienda que inhabilites la programación automática de NUMA.
    • No uses la vinculación del kernel de Linux con las interfaces gVNIC o IDPF en estas máquinas, ya que no se admite la vinculación del kernel.

Patrones para usar varias NIC de host

En la siguiente tabla, se describen los patrones generales para usar varias NIC de host en Cloud de Confiance by S3NS:

Patrones generales para usar varias NIC de host
Patrón Diseño de proceso admitido Ruta de Deployment: Compute Engine Ruta de Deployment: Google Kubernetes Engine Ruta de Deployment: Slurm Notas
Cambia una aplicación para usar una interfaz específica Procesa el fragmento por interfaz Sí Sí Sí Requiere cambios de código en la aplicación.
Cambia una aplicación para usar ambas interfaces Proceso de interfaz doble Sí Sí Sí Requiere cambios de código en la aplicación.
Usa un espacio de nombres de red dedicado para aplicaciones específicas Procesa el fragmento por interfaz Sí Sí (solo contenedores con privilegios) No Ninguno
Asigna todo el tráfico de un contenedor a una sola interfaz Todo el tráfico de contenedores se asigna a una interfaz Sí Sí No Ninguno
Usa el intercambio de tráfico entre VPC y permite que el sistema balancee las cargas de las sesiones entre las interfaces Proceso de interfaz doble Sí Sí Sí Es difícil o imposible alinear con NUMA. Requiere el kernel de Linux 6.16 o una versión posterior.
Fragmenta el tráfico entre redes
  • Proceso de interfaz doble
  • Procesa el fragmento por interfaz
Sí Sí Sí Es posible que se requieran cambios en el código para la alineación de NUMA si ejecutas un proceso de interfaz doble.
Usa la traducción de direcciones de red de origen (SNAT) para elegir la interfaz de origen
  • Proceso de interfaz doble
  • Procesa el fragmento por interfaz
Sí Sí (la configuración requiere privilegios de administrador) Sí (la configuración requiere privilegios de administrador) Puede ser difícil de configurar.

Notas

Cambia una aplicación para que use una interfaz específica

Requisitos:

  • Requiere cambios de código en tu aplicación.
  • Requiere permisos para uno o más de los siguientes métodos:
    • bind(): Requiere permisos especiales solo si usas un puerto de origen con privilegios.
    • SO_BINDTODEVICE: Requiere el permiso CAP_NET_RAW.
  • Es posible que debas modificar la tabla de enrutamiento del kernel para establecer rutas y evitar el enrutamiento asimétrico.

Descripción general de alto nivel

Para configurar tu aplicación de modo que use una interfaz de red específica, completa los siguientes pasos:

  1. Agrega la vinculación de una interfaz de red al código fuente de tu aplicación con una de las siguientes opciones:
    • Usa la llamada al sistema bind() para vincular un socket a una dirección IP de origen específica.
    • Usa la opción de socket SO_BINDTODEVICE para vincular un socket a una interfaz de red específica.
  2. Modifica la tabla de enrutamiento del kernel según sea necesario para asegurarte de que exista una ruta desde la interfaz de red de origen hasta la dirección de destino. Para evitar el enrutamiento asimétrico, configura la política de enrutamiento como se describe en Configura el enrutamiento para una interfaz de red adicional.
  3. Opcional: Ejecuta tu aplicación en la memoria y las CPU que comparten el mismo nodo de NUMA que la interfaz de red que elegiste con el comando numactl.

Después de completar estos pasos, tu aplicación usará la interfaz de red especificada.

Cambia una aplicación para que use ambas interfaces

Requisitos:

  • Requiere cambios de código en tu aplicación.
  • Requiere permisos para uno o más de los siguientes métodos:
    • bind(): Requiere permisos especiales solo si usas un puerto de origen con privilegios.
    • SO_BINDTODEVICE: Requiere el permiso CAP_NET_RAW.
  • Es posible que debas modificar la tabla de enrutamiento del kernel para establecer rutas y evitar el enrutamiento asimétrico.

Descripción general de alto nivel

Para configurar tu aplicación de modo que use ambas interfaces de red, completa los siguientes pasos:

  1. Agrega una vinculación para cada interfaz de red al código fuente de tu aplicación con una de las siguientes opciones:
    • Usa la llamada al sistema bind() para vincular cada socket a la dirección IP de origen de la interfaz de red correspondiente.
    • Usa la opción de socket SO_BINDTODEVICE para vincular cada socket a una interfaz de red específica.
  2. Si tu aplicación actúa como cliente, crea un socket del cliente independiente para cada interfaz de red de origen.
  3. Modifica la tabla de enrutamiento del kernel según sea necesario para asegurarte de que exista una ruta desde cada interfaz de red de origen hasta la dirección de destino. Para evitar el enrutamiento asimétrico, configura la política de enrutamiento como se describe en Configura el enrutamiento para una interfaz de red adicional.
  4. Divide la actividad de red en subprocesos que se ejecuten en el mismo nodo NUMA que la interfaz gVNIC o IDPF correspondiente, por ejemplo, llamando a pthread_setaffinity_np. Debido a que la aplicación usa recursos en varios nodos NUMA, evita usar numactl o asegúrate de que el comando numactl incluya los nodos NUMA de todas las interfaces de red que usa tu aplicación.

Usa un espacio de nombres de red dedicado para aplicaciones específicas

Requisitos:

  • Requiere la capacidad CAP_SYS_ADMIN.
  • No es compatible con Autopilot de GKE.
  • Si usas GKE, debes usar un contenedor con privilegios.

En esta sección, se describen los patrones que puedes usar para crear un espacio de nombres de red para una interfaz de red secundaria. El mejor patrón para tu carga de trabajo depende de tu situación. Los enfoques que usan un interruptor virtual o IPvlan funcionan mejor cuando varias aplicaciones necesitan compartir la interfaz secundaria desde diferentes espacios de nombres de red.

Mueve la interfaz secundaria a un espacio de nombres de red dedicado

Con este patrón, creas un espacio de nombres de red, mueves la interfaz secundaria de gVNIC o IDPF al espacio de nombres nuevo y, luego, ejecutas la aplicación desde ese espacio de nombres. Este patrón es más fácil de configurar y ajustar en comparación con un interruptor virtual, pero las aplicaciones fuera del nuevo espacio de nombres de red no pueden acceder a la interfaz de red secundaria.

Para mover la interfaz eth1 a un espacio de nombres de red llamado app-net y ejecutar un comando en ese espacio de nombres, ejecuta los siguientes comandos:

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

Reemplaza lo siguiente:

  • ETH1_IP: Es la dirección IP interna de la interfaz eth1.
  • PREFIX: Es la longitud del prefijo de la subred para la interfaz eth1.
  • GATEWAY_IP: Es la dirección IP de la puerta de enlace predeterminada para la subred eth1.
  • COMMAND: Es el comando que se ejecutará dentro del espacio de nombres de red app-net.

Cuando ejecutas estos comandos, COMMAND se ejecuta dentro del espacio de nombres de red app-net y usa la interfaz eth1.

Las aplicaciones que se ejecutan dentro del espacio de nombres de red nuevo usan la interfaz secundaria de gVNIC o IDPF. También puedes usar el comando numactl para ejecutar tu aplicación con la memoria y las CPU en el mismo nodo NUMA que la interfaz de red que elegiste.

Usa un switch virtual y un espacio de nombres de red para una interfaz secundaria

Con este patrón, configuras un interruptor virtual para acceder a la interfaz secundaria de gVNIC o IDPF desde un espacio de nombres de red:

  1. Abre una conexión SSH a la instancia de procesamiento. Para obtener más información, consulta Conéctate a VMs de Linux.
  2. Crea un par de dispositivos Ethernet virtual (veth) con el comando ip link add, por ejemplo, ip link add veth0 type veth peer name veth1.
  3. Ajusta la MTU en cada dispositivo para que coincida con la MTU de la interfaz secundaria con el comando ip link set:

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

    Reemplaza MTU por la MTU de la interfaz secundaria, por ejemplo, 8896.

  4. Habilita el reenvío de paquetes IPv4:

    sysctl -w net.ipv4.ip_forward=1
    
  5. Mueve un extremo del par veth a un espacio de nombres de red nuevo y deja el otro extremo en el espacio de nombres raíz.

  6. Asigna el tráfico del dispositivo veth a la interfaz secundaria. Para configurar esta asignación, crea un rango de alias de IP para la interfaz secundaria de la instancia de procesamiento y asigna una dirección IP de este rango a la interfaz secundaria en el espacio de nombres.

  7. Ejecuta la aplicación desde el nuevo espacio de nombres de red. Puedes usar el comando numactl para ejecutar tu aplicación con la memoria y las CPU en el mismo nodo NUMA que la interfaz de red elegida.

Como alternativa, según el SO invitado y la configuración de la carga de trabajo, puedes vincular una interfaz IPvlan a la interfaz secundaria de gVNIC o IDPF en lugar de crear dispositivos veth.

Asigna todo el tráfico de un contenedor a una sola interfaz

Requisitos:

  • La aplicación debe ejecutarse dentro de un contenedor que use un espacio de nombres de red para las herramientas de redes de contenedores, como GKE, Docker o Podman. No puedes usar la red del host.

Los entornos de ejecución de contenedores, como GKE, Docker y Podman, usan un espacio de nombres de red dedicado para cada contenedor con el objetivo de aislar su tráfico. Puedes modificar este espacio de nombres de red, ya sea directamente o con las herramientas del entorno de ejecución del contenedor, para asignar el tráfico a una interfaz de red diferente.

GKE requiere la interfaz principal para la comunicación interna de Kubernetes. Sin embargo, puedes cambiar la ruta predeterminada en el Pod para usar la interfaz secundaria, como se muestra en el siguiente manifiesto de Pod de GKE:

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

Este enfoque no garantiza la alineación de NUMA entre la interfaz de red predeterminada y las CPU o la memoria asignadas.

Usa el intercambio de tráfico entre VPC y permite que el sistema balancee las cargas de las sesiones entre las interfaces.

Requisitos:

  • Debes establecer el intercambio de tráfico entre redes de VPC entre las redes de VPC de las NIC de host principal y secundaria.
  • Debes usar la versión de kernel 6.16 o posterior de Linux para balancear las cargas de las sesiones de TCP en las interfaces de origen cuando envíes tráfico a una sola dirección IP y puerto de destino.
  • Tu carga de trabajo debe tolerar las transferencias de memoria entre sockets sin perder sus objetivos de rendimiento.

Descripción general de alto nivel

Si no puedes fragmentar fácilmente las conexiones de red dentro de una aplicación o entre instancias de aplicación, y tu carga de trabajo se ejecuta en instancias de procesamiento A4 o A3 Ultra que no son sensibles a las transferencias entre NUMA o entre sockets, puedes tratar las dos interfaces como fungibles.

Para configurar este comportamiento, habilita el parámetro sysctl fib_multipath_hash_policy y agrega una ruta de acceso de múltiples rutas:

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}

Reemplaza lo siguiente:

  • DESTINATION_NETWORK: el rango de direcciones IP internas de destino.
  • SUBNET_MASK: Es la longitud del prefijo de la subred para la red de destino.

Fragmenta el tráfico entre redes

Requisitos:

  • nic0 y nic1 en la instancia de procesamiento deben conectarse a redes de VPC y subredes independientes, y las direcciones IP de destino deben fragmentarse en las dos redes de VPC.

Descripción general de alto nivel

De forma predeterminada, el kernel de Linux crea rutas para la subred nic0 y la subred nic1 que enrutan el tráfico por dirección IP de destino a través de la interfaz de red correspondiente.

Por ejemplo, supongamos que nic0 usa la red de VPC net1 con la subred subnet-a y nic1 usa la red de VPC net2 con la subred subnet-b. De forma predeterminada, el tráfico a las direcciones IP de intercambio de tráfico en subnet-a usa nic0, y el tráfico a las direcciones IP de intercambio de tráfico en subnet-b usa nic1. Esta configuración suele aplicarse cuando un conjunto de instancias de procesamiento de NIC única de par se conecta a net1 y un segundo conjunto se conecta a net2.

Usa SNAT para elegir la interfaz de origen

Requisitos:

  • Requiere la capacidad CAP_NET_ADMIN para configurar las reglas iniciales de iptables, pero no para ejecutar la aplicación.
  • Requiere una evaluación cuidadosa cuando se combina con otras reglas de iptables o parámetros de configuración de enrutamiento no triviales.

Descripción general de alto nivel

Si no puedes usar espacios de nombres de red ni modificar tu aplicación, puedes usar SNAT para seleccionar una interfaz de origen. Con una herramienta como iptables, puedes reescribir la dirección IP de origen de un flujo para que coincida con la dirección IP de una interfaz específica en función de una propiedad de la aplicación de envío, como su cgroup, usuario o CPU.

En el siguiente ejemplo, se usan reglas basadas en CPU para que cualquier flujo que se origine en un subproceso de una CPU determinada salga a través de la interfaz de gVNIC o IDPF conectada al nodo NUMA de esa CPU:

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

¿Qué sigue?