Alcune macchine ottimizzate per l'acceleratore, tra cui A3 Ultra, A4 e A4X, hanno due interfacce di rete host oltre alle interfacce MRDMA su queste macchine. Sull'host, si tratta di IPU Titanium che sono collegate a socket CPU separati e a nodi NUMA (accesso alla memoria non uniforme). Queste IPU sono disponibili all'interno della VM come Google Virtual NIC (gVNIC) e forniscono larghezza di banda di rete per attività di archiviazione come checkpoint, caricamento di dati di addestramento, caricamento di modelli e altre esigenze di rete generali. La topologia NUMA della macchina, inclusa quella delle gVNIC, è visibile al sistema operativo guest.
Questo documento descrive le best practice per l'utilizzo delle due gNIC virtuali su queste macchine.
Panoramica
In generale, ti consigliamo di utilizzare le seguenti configurazioni, indipendentemente da come prevedi di utilizzare più NIC host:
- Impostazioni di rete:ogni gVNIC deve avere una rete VPC
unica. Per una configurazione VPC, considera
quanto segue:
- Utilizza un'unità massima di trasmissione (MTU) di grandi dimensioni per ogni rete VPC. 8896 è l'MTU massima supportata e una scelta consigliata. Le prestazioni di ingresso per alcuni workload potrebbero essere rallentate a causa del sistema che elimina i pacchetti di dati in entrata sul lato ricevitore. Puoi utilizzare lo strumento ethtool per verificare la presenza di questo problema. In questo scenario, può essere utile regolare l'MSS TCP, l'MTU dell'interfaccia o l'MTU VPC per consentire un'allocazione efficiente dei dati dalla cache della pagina, il che consente al frame di livello 2 in entrata di rientrare in due buffer da 4 KB.
- Impostazioni dell'applicazione
- Allinea l'applicazione in base a NUMA. Utilizza core CPU, allocazioni di memoria e un'interfaccia di rete dello stesso nodo NUMA. Se esegui un'istanza dedicata dell'applicazione per utilizzare un nodo NUMA o un'interfaccia di rete specifici, puoi utilizzare strumenti come
numactl
per collegare le risorse di CPU e memoria dell'applicazione a un nodo NUMA specifico.
- Allinea l'applicazione in base a NUMA. Utilizza core CPU, allocazioni di memoria e un'interfaccia di rete dello stesso nodo NUMA. Se esegui un'istanza dedicata dell'applicazione per utilizzare un nodo NUMA o un'interfaccia di rete specifici, puoi utilizzare strumenti come
- Impostazioni del sistema operativo
- Attiva il trasferimento della segmentazione TCP (TSO) e il trasferimento della ricezione di grandi dimensioni (LRO).
- Per ogni interfaccia gVNIC, assicurati che l'affinità SMP sia configurata in modo che le richieste di interruzione (IRQ) vengano gestite sullo stesso nodo NUMA dell'interfaccia e che le interruzioni siano distribuite tra i core. Se utilizzi un'immagine del sistema operativo guest fornita da Google, questa procedura viene eseguita automaticamente utilizzando lo script
google_set_multiqueue
. - Valuta impostazioni come RFS, RPS e XPS per vedere se possono essere utili per il tuo workload.
- Per A4X, Nvidia consiglia di disattivare la pianificazione NUMA automatica.
- Il bonding del kernel Linux non è supportato per le gVNIC su queste macchine.
Pattern per l'utilizzo di più NIC host
Questa sezione descrive i pattern generali per l'utilizzo di più NIC host su Google Cloud.
Percorsi di deployment supportati | |||||
---|---|---|---|---|---|
Pattern | Layout di processo supportato | GCE (generale) | GKE | SLURM | Note |
Modificare l'applicazione per utilizzare un'interfaccia specifica | Elabora shard per interfaccia | ✅ | ✅ | ✅ | Richiede modifiche al codice dell'applicazione |
Modificare l'applicazione per utilizzare entrambe le interfacce | Processo a doppia interfaccia | ✅ | ✅ | ✅ | Richiede modifiche al codice dell'applicazione |
Utilizzare uno spazio dei nomi di rete dedicato per applicazioni specifiche | Elabora shard per interfaccia | ✅ | ✅ (solo container con privilegi) | ⛔ | |
Mappare l'intero traffico di un container a una singola interfaccia | Tutto il traffico dei container mappato a un'unica interfaccia | ✅ | ✅ | ⛔ | |
Esegui il peering dei VPC e lascia che il sistema bilanci il carico delle sessioni tra le interfacce | Processo a doppia interfaccia | ✅* | ✅* | ✅* | Difficile o impossibile da allineare a NUMA. Richiede il kernel Linux 6.16 o versioni successive* |
Distribuire il traffico tra le reti | Processo a doppia interfaccia Shard di processo per interfaccia | ✅* | ✅* | ✅* | Potrebbe richiedere modifiche al codice per l'allineamento NUMA se viene eseguito un processo a doppia interfaccia. |
Utilizzare SNAT per scegliere l'interfaccia di origine | Processo a doppia interfaccia Shard di processo per interfaccia | ✅ | ✅ (la configurazione richiede privilegi amministrativi) | ✅ (la configurazione richiede privilegi amministrativi) | Può essere più difficile da configurare correttamente |
* Questa opzione non è generalmente consigliata, ma potrebbe essere utile per carichi di lavoro limitati sulle piattaforme x86 (A3 Ultra e A4).
Modificare l'applicazione per utilizzare un'interfaccia specifica
Requisiti:
- Questo metodo richiede modifiche al codice dell'applicazione.
- Richiede le autorizzazioni per uno o più dei seguenti metodi:
bind()
richiede autorizzazioni speciali solo se utilizza una porta di origine con privilegi.SO_BINDTODEVICE
: richiede l'autorizzazioneCAP_NET_RAW
.
- Questo metodo potrebbe richiedere la modifica della tabella di routing del kernel per stabilire le route e impedire il routing asimmetrico.
Panoramica di alto livello
Con questo pattern, completi le seguenti operazioni:
- Aggiungi il binding dell'interfaccia di rete al codice sorgente dell'applicazione utilizzando una delle seguenti opzioni:
- Utilizzare
bind()
per associare un socket a un determinato indirizzo IP di origine - Utilizza l'opzione socket
SO_BINDTODEVICE
per associare un socket a una particolare interfaccia di rete.
- Utilizzare
- Modifica la tabella di routing del kernel in base alle esigenze per assicurarti che esista una route dall'interfaccia di rete di origine all'indirizzo di destinazione. Inoltre, potrebbero essere necessarie route per impedire il routing asimmetrico. Ti consigliamo di configurare il routing basato su criteri come descritto in Configurare il routing per un'interfaccia di rete aggiuntiva.
- Puoi anche utilizzare il comando
numactl
per eseguire l'applicazione. Con questo approccio, utilizzi la memoria e le CPU che si trovano sullo stesso nodo NUMA dell'interfaccia di rete che hai scelto.
Dopo aver completato i passaggi precedenti, le istanze dell'applicazione vengono eseguite utilizzando un'interfaccia di rete specifica.
Modificare l'applicazione per utilizzare entrambe le interfacce
Requisiti:
- Questo metodo richiede modifiche al codice dell'applicazione.
- Devi disporre delle autorizzazioni per uno o più dei seguenti metodi:
bind()
richiede autorizzazioni speciali solo se utilizza una porta di origine con privilegi.SO_BINDTODEVICE
: richiede l'autorizzazioneCAP_NET_RAW
.
- Questo metodo potrebbe richiedere la modifica della tabella di routing del kernel per stabilire le route e impedire il routing asimmetrico.
Panoramica di alto livello
Per implementare questo pattern:
- Aggiungi il binding dell'interfaccia di rete al codice sorgente dell'applicazione utilizzando una delle seguenti opzioni:
- Utilizza la chiamata di sistema
bind()
per associare un socket a un indirizzo IP di origine specifico - Utilizza l'opzione socket
SO_BINDTODEVICE
per associare un socket a una particolare interfaccia di rete.
- Utilizza la chiamata di sistema
- Se la tua applicazione funge da client, devi creare un socket client separato per ogni interfaccia di rete di origine.
- Modifica la tabella di routing del kernel in base alle esigenze per assicurarti che esista una route dall'interfaccia di rete di origine all'indirizzo di destinazione. Inoltre, potresti aver bisogno anche di route per impedire il routing asimmetrico. Ti consigliamo di configurare il routing dei criteri come descritto in Configurare il routing per un'interfaccia di rete aggiuntiva.
- Ti consigliamo di partizionare l'attività di rete in thread eseguiti sullo stesso nodo NUMA dell'interfaccia gVNIC. Un modo comune per richiedere un
nodo NUMA specifico per un thread è chiamare
pthread_setaffinity_np
.- Poiché l'applicazione utilizza risorse su più nodi NUMA,
evita di utilizzare
numactl
o assicurati che il comandonumactl
includa i nodi NUMA di tutte le interfacce di rete utilizzate dall'applicazione.
- Poiché l'applicazione utilizza risorse su più nodi NUMA,
evita di utilizzare
Utilizzare uno spazio dei nomi di rete dedicato per applicazioni specifiche
Requisiti:
- Richiede la funzionalità
CAP_SYS_ADMIN
. - Non compatibile con GKE Autopilot.
- Se utilizzi GKE, devi disporre di un container privilegiato.
Questa sezione descrive i pattern che puoi utilizzare per creare uno spazio dei nomi di rete che utilizza un'interfaccia di rete secondaria. Il pattern giusto per il tuo carico di lavoro dipende dal tuo scenario specifico. Gli approcci che utilizzano lo switch virtuale o IPvlan sono più adatti ai casi in cui più applicazioni devono utilizzare l'interfaccia secondaria da spazi dei nomi di rete diversi.
Panoramica generale: spostamento dell'interfaccia secondaria in uno spazio dei nomi di rete dedicato
Questo pattern prevede la creazione di uno spazio dei nomi di rete, lo spostamento dell'interfaccia gVNIC secondaria nel nuovo spazio dei nomi e l'esecuzione dell'applicazione da questo spazio dei nomi. Questo pattern potrebbe essere meno complicato da configurare e ottimizzare rispetto all'utilizzo di un interruttore virtuale. Tuttavia, le applicazioni al di fuori del nuovo spazio dei nomi di rete non potranno accedere alla gVNIC secondaria.
L'esempio seguente mostra una serie di comandi che possono essere utilizzati per spostare eth1 nel nuovo spazio dei nomi di rete denominato 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>
Quando viene eseguito questo comando, l'espressione <command> viene eseguita all'interno dello spazio dei nomi di rete e utilizza l'interfaccia eth1.
Le applicazioni in esecuzione all'interno del nuovo spazio dei nomi di rete ora utilizzano gVNIC
secondario. Puoi anche utilizzare il comando numactl
per eseguire l'applicazione utilizzando la memoria e le CPU che si trovano sullo stesso nodo NUMA dell'interfaccia di rete scelta.
Panoramica di alto livello: utilizzo di un commutatore virtuale e di uno spazio dei nomi di rete per un'interfaccia secondaria. Questo pattern prevede la creazione di una configurazione di commutatore virtuale per utilizzare gVNIC secondario da uno spazio dei nomi di rete.
I passaggi generali sono i seguenti:
- Crea una coppia di dispositivi Ethernet virtuali (veth). Regola l'unità massima di trasmissione (MTU) su ciascuno dei dispositivi in modo che corrisponda alla MTU della gVNIC secondaria.
- Esegui questo comando per assicurarti che l'inoltro IP sia abilitato per IPv4: sysctl -w net.ipv4.ip_forward=1
- Sposta un'estremità della coppia veth in un nuovo spazio dei nomi di rete e lascia l'altra estremità nello spazio dei nomi root.
- Mappa il traffico dal dispositivo veth all'interfaccia gVNIC secondaria. Esistono diversi modi per farlo, ma ti consigliamo di creare un intervallo di IP alias per l'interfaccia secondaria della VM e assegnare un indirizzo IP da questo intervallo all'interfaccia secondaria nello spazio dei nomi.
- Esegui l'applicazione dal nuovo spazio dei nomi di rete. Puoi utilizzare il comando
numactl
per eseguire l'applicazione utilizzando la memoria e le CPU che si trovano nello stesso nodo NUMA dell'interfaccia di rete scelta.
A seconda della configurazione del guest e del carico di lavoro, in alternativa puoi utilizzare il driver IPvlan con un'interfaccia IPvlan collegata a gVNIC secondario anziché creare i dispositivi veth.
Mappare l'intero traffico di un container a una singola interfaccia
Requisiti:
- L'applicazione deve essere eseguita all'interno di un container che utilizza uno spazio dei nomi di rete per il networking dei container, ad esempio GKE, Docker o Podman. Non puoi utilizzare la rete host.
Molte tecnologie di containerizzazione, come GKE, Docker e Podman, utilizzano uno spazio dei nomi di rete dedicato per un container per isolare il relativo traffico. Questo spazio dei nomi di rete può quindi essere modificato, direttamente o utilizzando gli strumenti della tecnologia dei container per mappare il traffico a un'interfaccia di rete diversa.
GKE richiede la presenza dell'interfaccia principale per la comunicazione interna di Kubernetes. Tuttavia, la route predefinita nel pod può essere modificata per utilizzare l'interfaccia secondaria, come mostrato nel seguente manifest del pod GKE.
metadata:
…
annotations:
networking.gke.io/default-interface: 'eth1'
networking.gke.io/interfaces: |
[
{"interfaceName":"eth0","network":"default"},
{"interfaceName":"eth1","network":"secondary-network"},
]
Questo approccio non garantisce l'allineamento NUMA tra l'interfaccia di rete predefinita e le CPU o la memoria.
Esegui il peering dei VPC e lascia che il sistema bilanci il carico delle sessioni tra le interfacce
Requisiti:
- Il peering VPC deve essere stabilito tra i VPC delle gNIC virtuali principali e secondarie.
- Per bilanciare il carico delle sessioni TCP tra le interfacce di origine se l'invio avviene a un singolo IP e porta di destinazione, è necessaria la versione 6.16 del kernel Linux.
- Il workload può comunque soddisfare i requisiti di prestazioni quando lo stack di rete genera trasferimenti di memoria tra socket.
Panoramica di alto livello
In alcuni casi, è difficile partizionare le connessioni di rete all'interno di un'applicazione o tra le istanze di un'applicazione. In questo scenario, per alcune applicazioni in esecuzione su VM A3U o A4 che non sono sensibili al trasferimento cross-NUMA o cross-socket, può essere conveniente trattare le due interfacce come fungibili.
Un metodo per ottenere questo risultato è utilizzare sysctl fib_multipath_hash_policy e una route 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}
Distribuire il traffico tra le reti
Requisiti:
nic0
enic1
sulla VM si trovano in VPC e subnet separati. Questo pattern richiede che gli indirizzi di destinazione siano distribuiti tra i VPC dinic0
enic1
.
Panoramica di alto livello
Per impostazione predefinita, il kernel Linux crea route per la subnet di nic0
e la subnet di nic1
che instradano il traffico per destinazione tramite l'interfaccia di rete appropriata.
Ad esempio, supponiamo che nic0
utilizzi il VPC net1
con la subnet subnet-a
e che nic1
utilizzi il VPC net2
con la subnet subnet-b
. Per impostazione predefinita, le comunicazioni con gli indirizzi IP peer in subnet-a
utilizzeranno nic0
, mentre le comunicazioni con gli indirizzi IP peer in subnet-b
utilizzeranno nic1
. Ad esempio, questo scenario può verificarsi con un insieme
di VM peer con una sola NIC connesse a net1
e un insieme connesso a net2
.
Utilizzare SNAT per scegliere l'interfaccia di origine
Requisiti:
CAP_NET_ADMIN
è necessario per configurare le regole iptables iniziali, ma non per eseguire l'applicazione.- Devi valutare attentamente le regole quando le utilizzi in combinazione con altre regole iptables o configurazioni di routing non banali.
Nota:
- Il binding NIC è corretto solo al momento della creazione della connessione. Se un thread viene spostato su una CPU associata a un nodo NUMA diverso, la connessione subirà penalità cross-NUMA. Pertanto, questa soluzione è più utile quando esiste un meccanismo per associare i thread a set di CPU specifici.
- Solo le connessioni originate da questa macchina verranno associate a una NIC specifica. Le connessioni in entrata verranno associate alla scheda di interfaccia di NIC corrispondente all'indirizzo di destinazione.
Panoramica di alto livello
Negli scenari in cui è difficile utilizzare gli spazi dei nomi di rete o apportare modifiche all'applicazione, puoi utilizzare NAT per scegliere un'interfaccia di origine. Puoi utilizzare strumenti come iptables per riscrivere l'IP di origine di un flusso in modo che corrisponda all'IP di una particolare interfaccia in base a una proprietà dell'applicazione di invio, ad esempio cgroup, utente o CPU.
L'esempio seguente utilizza regole basate sulla CPU. Il risultato finale è che un flusso che ha origine da un thread in esecuzione su una determinata CPU viene trasmesso dalla gVNIC collegata al nodo NUMA corrispondente della 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"