Algunas máquinas optimizadas para aceleradores, como A3 Ultra, A4 y A4X, tienen dos interfaces de red de host además de las interfaces MRDMA. En el host, se trata de IPUs de titanio que se conectan a sockets de CPU independientes y a nodos de acceso a memoria no uniforme (NUMA). Estas IPUs están disponibles en la VM como NICs virtuales de Google (gVNICs) y proporcionan ancho de banda de red para actividades de almacenamiento, como la creación de puntos de control, la carga de datos de entrenamiento, la carga de modelos y otras necesidades generales de redes. El sistema operativo invitado puede ver la topología NUMA de la máquina, incluida la de las gVNICs.
En este documento se describen las prácticas recomendadas para usar las dos gVNICs en estas máquinas.
Información general
En general, te recomendamos que utilices las siguientes configuraciones, independientemente de cómo tengas previsto usar varias NICs de host:
- Configuración de red: cada gVNIC debe tener una red de VPC única. Si configuras una VPC, ten en cuenta lo siguiente:
- Usa una unidad máxima de transmisión (MTU) grande para cada red de VPC. 8896 es el MTU máximo admitido y una opción recomendada. El rendimiento de entrada de algunas cargas de trabajo puede ralentizarse debido a que el sistema descarta paquetes de datos entrantes en el lado del receptor. Puedes usar la herramienta ethtool para comprobar si se produce este problema. En este caso, puede ser útil ajustar el MSS de TCP, la MTU de la interfaz o la MTU de la VPC para permitir una asignación de datos eficiente desde la caché de páginas, lo que permite que el marco de capa 2 entrante quepa en dos búferes de 4 KB.
- Configuración de la aplicación
- Alinea la aplicación con NUMA. Usa núcleos de CPU, asignaciones de memoria y una interfaz de red del mismo nodo NUMA. Si ejecutas una instancia dedicada de la aplicación para usar un nodo NUMA o una interfaz de red específicos, puedes usar herramientas como
numactl
para asociar los recursos de CPU y memoria de la aplicación a un nodo NUMA específico.
- Alinea la aplicación con NUMA. Usa núcleos de CPU, asignaciones de memoria y una interfaz de red del mismo nodo NUMA. Si ejecutas una instancia dedicada de la aplicación para usar un nodo NUMA o una interfaz de red específicos, puedes usar herramientas como
- Ajustes del sistema operativo
- Habilita la descarga de segmentación TCP (TSO) y la descarga de recepción grande (LRO).
- En cada interfaz gVNIC, asegúrate de que la afinidad SMP esté configurada de forma que sus solicitudes de interrupción (IRQs) se gestionen en el mismo nodo NUMA que la interfaz y que las interrupciones se distribuyan entre los núcleos. Si usas una imagen de SO invitado proporcionada por Google, este proceso se realiza automáticamente mediante la secuencia de comandos
google_set_multiqueue
. - Evalúa ajustes como RFS, RPS y XPS para ver si pueden ser útiles para tu carga de trabajo.
- En el caso de A4X, Nvidia recomienda inhabilitar la programación NUMA automática.
- No se admite la vinculación del kernel de Linux para las gVNICs en estas máquinas.
Patrones para usar varias NICs de host
En esta sección se describen los patrones generales para usar varias NICs de host enGoogle Cloud.
Rutas de implementación admitidas | |||||
---|---|---|---|---|---|
Patrón | Diseño de proceso admitido | GCE (general) | GKE | SLURM | Notas |
Cambiar la aplicación para que use una interfaz específica | Proceso de fragmento por interfaz | ✅ | ✅ | ✅ | Requiere cambios en el código de la aplicación |
Cambiar la aplicación para que use ambas interfaces | Proceso de interfaz dual | ✅ | ✅ | ✅ | Requiere cambios en el código de la aplicación |
Usar un espacio de nombres de red específico para determinadas aplicaciones | Proceso de fragmento por interfaz | ✅ | ✅ (solo contenedores con privilegios) | ⛔ | |
Asignar el tráfico de un contenedor completo a una sola interfaz | Todo el tráfico de contenedores asignado a una interfaz | ✅ | ✅ | ⛔ | |
Empareja las VPCs y deja que el sistema balancee la carga de las sesiones entre las interfaces | Proceso de interfaz dual | ✅* | ✅* | ✅* | Es difícil o imposible alinear con NUMA. Se necesita el kernel de Linux 6.16 o una versión posterior.* |
Repartir el tráfico entre redes | Proceso de doble interfaz Fragmento de proceso por interfaz | ✅* | ✅* | ✅* | Puede que sea necesario cambiar el código para alinear NUMA si se ejecuta un proceso de interfaz dual. |
Usar SNAT para elegir la interfaz de origen | Proceso de doble interfaz Fragmento de proceso por interfaz | ✅ | ✅ (la configuración requiere privilegios de administrador) | ✅ (la configuración requiere privilegios de administrador) | Puede ser más difícil de configurar correctamente |
* Esta opción no suele ser recomendable, pero puede ser útil para cargas de trabajo limitadas en plataformas x86 (A3 Ultra y A4).
Cambiar la aplicación para que use una interfaz específica
Requisitos:
- Este método requiere que modifiques el código de tu aplicación.
- Requiere permisos para uno o varios de los siguientes métodos:
bind()
solo requiere permisos especiales si se usa un puerto de origen privilegiado.SO_BINDTODEVICE
: requiere el permisoCAP_NET_RAW
.
- Este método puede requerir que modifiques la tabla de enrutamiento del kernel para establecer rutas y evitar el enrutamiento asimétrico.
Descripción general
Con este patrón, puedes hacer lo siguiente:
- Añade la vinculación de la interfaz de red al código fuente de tu aplicación mediante una de las siguientes opciones:
- Usa
bind()
para enlazar un socket a una dirección IP de origen concreta - Usa la opción de socket
SO_BINDTODEVICE
para enlazar un socket a una interfaz de red concreta.
- Usa
- Modifica la tabla de enrutamiento del kernel según sea necesario para asegurarte de que haya una ruta desde la interfaz de red de origen hasta la dirección de destino. Además, es posible que se necesiten rutas para evitar el enrutamiento asimétrico. Te recomendamos que configures el enrutamiento de políticas tal como se describe en el artículo Configurar el enrutamiento de una interfaz de red adicional.
- También puedes usar el comando
numactl
para ejecutar tu aplicación. Con este método, se usa la memoria y las CPUs que están en el mismo nodo NUMA que la interfaz de red elegida.
Después de completar los pasos anteriores, las instancias de tu aplicación se ejecutarán con una interfaz de red específica.
Cambiar la aplicación para que use ambas interfaces
Requisitos:
- Este método requiere que modifiques el código de tu aplicación.
- Necesitas permisos para uno o varios de los siguientes métodos:
bind()
solo requiere permisos especiales si se usa un puerto de origen privilegiado.SO_BINDTODEVICE
: requiere el permisoCAP_NET_RAW
.
- Este método puede requerir que modifiques la tabla de enrutamiento del kernel para establecer rutas y evitar el enrutamiento asimétrico.
Descripción general
Para implementar este patrón, sigue estos pasos:
- Añade la vinculación de la interfaz de red al código fuente de tu aplicación mediante una de las siguientes opciones:
- Usa la llamada al sistema
bind()
para vincular un socket a una dirección IP de origen concreta. - Usa la opción de socket
SO_BINDTODEVICE
para enlazar un socket a una interfaz de red concreta.
- Usa la llamada al sistema
- Si tu aplicación actúa como cliente, tendrás que crear un socket de cliente independiente para cada interfaz de red de origen.
- Modifica la tabla de enrutamiento del kernel según sea necesario para asegurarte de que haya una ruta desde la interfaz de red de origen hasta la dirección de destino. Además, es posible que también necesites rutas para evitar el enrutamiento asimétrico. Te recomendamos que configures el enrutamiento de políticas tal como se describe en el artículo Configurar el enrutamiento de una interfaz de red adicional.
- Te recomendamos que dividas la actividad de la red en hilos que se ejecuten en el mismo nodo NUMA que la interfaz gVNIC. Una forma habitual de solicitar un nodo NUMA específico para un subproceso es llamar a
pthread_setaffinity_np
.- Como la aplicación utiliza recursos en varios nodos NUMA, evita usar
numactl
o asegúrate de que tu comandonumactl
incluya los nodos NUMA de todas las interfaces de red que utilice tu aplicación.
- Como la aplicación utiliza recursos en varios nodos NUMA, evita usar
Usar un espacio de nombres de red específico para aplicaciones concretas
Requisitos:
- Requiere la función
CAP_SYS_ADMIN
. - No es compatible con Autopilot de GKE.
- Si usas GKE, debes tener un contenedor con privilegios.
En esta sección se describen los patrones que puedes usar para crear un espacio de nombres de red que utilice una interfaz de red secundaria. El patrón adecuado para tu carga de trabajo depende de tu caso concreto. Los enfoques que usan un interruptor virtual o IPvlan son más adecuados para los casos en los que varias aplicaciones necesitan usar la interfaz secundaria de diferentes espacios de nombres de red.
Descripción general: mover la interfaz secundaria a un espacio de nombres de red específico
Este patrón implica crear un espacio de nombres de red, mover la interfaz gVNIC secundaria al nuevo espacio de nombres y, a continuación, ejecutar la aplicación desde este espacio de nombres. Este patrón puede ser más sencillo de configurar y ajustar que usar un interruptor virtual. Sin embargo, las aplicaciones que no estén en el nuevo espacio de nombres de la red no podrán acceder al gVNIC secundario.
En el siguiente ejemplo se muestra una serie de comandos que se pueden usar para mover eth1 al nuevo espacio de nombres de red llamado 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>
Cuando se ejecuta este comando, la expresión <command> se ejecuta en el espacio de nombres de la red y usa la interfaz eth1.
Las aplicaciones que se ejecutan en el nuevo espacio de nombres de red ahora usan la gVNIC secundaria. También puedes usar el comando numactl
para ejecutar tu aplicación con la memoria y las CPUs que estén en el mismo nodo NUMA que la interfaz de red que hayas elegido.
Descripción general: usar un conmutador virtual y un espacio de nombres de red para una interfaz secundaria Este patrón implica crear una configuración de conmutador virtual para usar la interfaz gVNIC secundaria de un espacio de nombres de red.
Estos son los pasos generales que debe seguir:
- Crea un par de dispositivos Ethernet virtuales (veth). Ajusta la unidad máxima de transmisión (MTU) de cada uno de los dispositivos para que coincida con la MTU de la gVNIC secundaria.
- Ejecuta lo siguiente para asegurarte de que el reenvío de IP está habilitado para IPv4: sysctl -w net.ipv4.ip_forward=1
- Mueve un extremo del par veth a un nuevo espacio de nombres de red y deja el otro extremo en el espacio de nombres raíz.
- Asigna el tráfico del dispositivo veth a la interfaz gVNIC secundaria. Hay varias formas de hacerlo, pero le recomendamos que cree un intervalo de alias de IP para la interfaz secundaria de la máquina virtual y que asigne una dirección IP de este intervalo a la interfaz secundaria del espacio de nombres.
- 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 CPUs que se encuentren en el mismo nodo NUMA que la interfaz de red elegida.
En función de la configuración del invitado y de la carga de trabajo, también puedes usar el controlador IPvlan con una interfaz IPvlan vinculada al gVNIC secundario en lugar de crear los dispositivos veth.
Asignar el tráfico de un contenedor completo a una sola interfaz
Requisitos:
- Tu aplicación debe ejecutarse en un contenedor que use un espacio de nombres de red para la red de contenedores, como GKE, Docker o Podman. No puedes usar la red del host.
Muchas tecnologías de contenedores, como GKE, Docker y Podman, usan un espacio de nombres de red específico para que un contenedor aísle su tráfico. Este espacio de nombres de red se puede modificar directamente o mediante las herramientas de la tecnología de contenedores para asignar el tráfico a otra interfaz de red.
GKE requiere que la interfaz principal esté presente para la comunicación interna de Kubernetes. Sin embargo, la ruta predeterminada del pod se puede cambiar 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 NUMA entre la interfaz de red predeterminada y las CPUs o la memoria.
Empareja las VPCs y deja que el sistema equilibre la carga de las sesiones entre las interfaces.
Requisitos:
- El peering de VPC debe establecerse entre las VPCs de las gNICs virtuales primarias y secundarias.
- Se necesita la versión 6.16 del kernel de Linux para equilibrar la carga de las sesiones TCP entre las interfaces de origen si se envía a una única dirección IP y puerto de destino.
- La carga de trabajo puede seguir cumpliendo tus requisitos de rendimiento cuando la pila de redes genera transferencias de memoria entre sockets.
Resumen general
En algunos casos, es difícil fragmentar las conexiones de red en una aplicación o entre instancias de una aplicación. En este caso, en algunas aplicaciones que se ejecutan en VMs A3U o A4 que no son sensibles a la transferencia entre NUMAs o entre sockets, puede ser conveniente tratar las dos interfaces como fungibles.
Una forma de hacerlo es usar el sysctl fib_multipath_hash_policy y una ruta 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}
Distribuir el tráfico entre redes
Requisitos:
nic0
ynic1
de la VM están en VPCs y subredes independientes. Este patrón requiere que las direcciones de destino se fragmenten en las VPCs denic0
ynic1
.
Resumen general
De forma predeterminada, el kernel de Linux crea rutas para la subred de nic0
y la subred de nic1
que enrutarán el tráfico por destino a través de la interfaz de red adecuada.
Por ejemplo, supongamos que nic0
usa la VPC net1
con la subred subnet-a
y que nic1
usa la VPC net2
con la subred subnet-b
. De forma predeterminada, las comunicaciones a direcciones IP de peers en subnet-a
usarán nic0
, y las comunicaciones a direcciones IP de peers en subnet-b
usarán nic1
. Por ejemplo, este escenario puede darse con un conjunto de VMs de una sola NIC emparejadas conectadas a net1
y otro conjunto conectado a net2
.
Usar SNAT para elegir la interfaz de origen
Requisitos:
- Se necesita
CAP_NET_ADMIN
para configurar las reglas iniciales de iptables, pero no para ejecutar la aplicación. - Debes evaluar cuidadosamente las reglas cuando las utilices en combinación con otras reglas de iptables o configuraciones de enrutamiento no triviales.
Nota:
- El enlace de NIC solo es correcto en el momento en que se crea la conexión. Si un hilo se mueve a una CPU asociada a un nodo NUMA diferente, la conexión sufrirá penalizaciones entre NUMAs. Por lo tanto, esta solución es más útil cuando hay algún mecanismo para vincular los hilos a conjuntos de CPUs específicos.
- Solo las conexiones originadas por esta máquina se vincularán a una NIC específica. Las conexiones entrantes se asociarán a la NIC que coincida con la dirección a la que estén destinadas.
Descripción general
En los casos en los que es difícil usar espacios de nombres de red o hacer cambios en la aplicación, puedes usar NAT para elegir una interfaz de origen. Puedes usar herramientas como iptables para reescribir la IP de origen de un flujo de modo que coincida con la IP de una interfaz concreta en función de una propiedad de la aplicación de envío, como cgroup, usuario o CPU.
En el siguiente ejemplo se usan reglas basadas en la CPU. El resultado final es que el flujo que se origina en un subproceso que se ejecuta en cualquier CPU se transmite mediante el gVNIC que está conectado al nodo NUMA correspondiente 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 ---
# 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"