Algumas máquinas otimizadas para aceleradores, incluindo A3 Ultra, A4 e A4X, têm duas interfaces de rede de host além das interfaces MRDMA. No host, elas são IPUs Titanium, que são conectadas a soquetes de CPU separados e nós de acesso à memória não uniforme (NUMA). Essas IPUs estão disponíveis na VM como NICs virtuais do Google (gVNICs) e fornecem largura de banda de rede para atividades de armazenamento, como checkpointing, carregamento de dados de treinamento, carregamento de modelos e outras necessidades gerais de rede. A topologia NUMA da máquina, incluindo a das gVNICs, fica visível para o sistema operacional (SO) convidado.
Neste documento, descrevemos as práticas recomendadas para usar as duas gVNICs nessas máquinas.
Visão geral
Em geral, recomendamos que você use as seguintes configurações, independente de como planeja usar várias NICs de host:
- Configurações de rede:cada gVNIC precisa ter uma rede VPC exclusiva. Para uma configuração de VPC, considere o seguinte:
- Use uma unidade máxima de transmissão (MTU) grande para cada rede VPC. 8896 é a MTU máxima aceita e uma opção recomendada. O desempenho de entrada para algumas cargas de trabalho pode ser reduzido devido à descartagem de pacotes de dados recebidos pelo sistema no lado do receptor. Use a ferramenta ethtool para verificar esse problema. Nesse cenário, pode ser útil ajustar o MSS do TCP, a MTU da interface ou a MTU da VPC para permitir uma alocação eficiente de dados do cache de página, o que permite que o frame de camada 2 de entrada se ajuste a dois buffers de 4 KB.
- Configurações do aplicativo
- Alinhe o aplicativo ao NUMA. Use núcleos de CPU, alocações de memória e uma interface de rede do mesmo nó NUMA. Se você estiver executando uma
instância dedicada do aplicativo para usar um nó NUMA ou
interface de rede específica, use ferramentas como
numactl
para anexar os recursos de CPU e memória do aplicativo a um nó NUMA específico.
- Alinhe o aplicativo ao NUMA. Use núcleos de CPU, alocações de memória e uma interface de rede do mesmo nó NUMA. Se você estiver executando uma
instância dedicada do aplicativo para usar um nó NUMA ou
interface de rede específica, use ferramentas como
- Configurações do sistema operacional
- Ative a descarga de segmentação TCP (TSO) e a descarga de recebimento grande (LRO).
- Para cada interface gVNIC, verifique se a afinidade SMP está configurada para que
as solicitações de interrupção (IRQs) sejam processadas no mesmo nó NUMA que a
interface e espalhe as interrupções pelos núcleos. Se você estiver executando uma
imagem de SO convidado fornecida pelo Google, esse processo vai acontecer automaticamente usando
o script
google_set_multiqueue
. - Avalie configurações como RFS, RPS e XPS para verificar se elas podem ser úteis para sua carga de trabalho.
- Para A4X, a Nvidia recomenda desativar o agendamento NUMA automático.
- A vinculação do kernel do Linux não é compatível com as gVNICs nessas máquinas.
Padrões para usar várias NICs de host
A seção descreve padrões gerais para usar várias NICs de host noGoogle Cloud.
Caminhos de implantação compatíveis | |||||
---|---|---|---|---|---|
Padrão | Layout de processo compatível | GCE (geral) | GKE | SLURM | Observações |
Mudar o aplicativo para usar uma interface específica | Processar fragmento por interface | ✅ | ✅ | ✅ | Requer mudanças no código do aplicativo |
Mudar o aplicativo para usar as duas interfaces | Processo de interface dupla | ✅ | ✅ | ✅ | Requer mudanças no código do aplicativo |
Usar um namespace de rede dedicado para aplicativos específicos | Processar fragmento por interface | ✅ | ✅ (somente contêineres privilegiados) | ⛔ | |
Mapear todo o tráfego de um contêiner para uma única interface | Todo o tráfego de contêineres mapeado para uma interface | ✅ | ✅ | ⛔ | |
Fazer peering das VPCs e permitir que o sistema faça o balanceamento de carga das sessões entre as interfaces | Processo de interface dupla | ✅* | ✅* | ✅* | Difícil ou impossível de alinhar ao NUMA. É necessário o kernel do Linux 6.16 ou mais recente* |
Dividir o tráfego entre redes | Processo de interface dupla: fragmento de processo por interface | ✅* | ✅* | ✅* | Pode exigir mudanças no código para alinhamento de NUMA se estiver executando um processo de interface dupla. |
Usar SNAT para escolher a interface de origem | Processo de interface dupla: fragmento de processo por interface | ✅ | ✅ (a configuração exige privilégios de administrador) | ✅ (a configuração exige privilégios de administrador) | Pode ser mais difícil de configurar corretamente |
* Essa opção geralmente não é recomendada, mas pode ser útil para cargas de trabalho limitadas em plataformas x86 (A3 Ultra e A4).
Mudar o aplicativo para usar uma interface específica
Requisitos:
- Esse método exige mudanças no código do aplicativo.
- Requer permissões para um ou mais dos seguintes métodos:
- O
bind()
só exige permissões especiais se estiver usando uma porta de origem privilegiada. SO_BINDTODEVICE
: exige a permissãoCAP_NET_RAW
.
- O
- Esse método pode exigir que você modifique a tabela de roteamento do kernel para estabelecer rotas e evitar o roteamento assimétrico.
Visão geral de alto nível
Com esse padrão, você conclui o seguinte:
- Adicione a vinculação de interface de rede ao código-fonte do aplicativo usando uma
das seguintes opções:
- Use
bind()
para vincular um soquete a um endereço IP de origem específico. - Use a opção de soquete
SO_BINDTODEVICE
para vincular um soquete a uma interface de rede específica.
- Use
- Modifique a tabela de rotas do kernel conforme necessário para garantir que haja uma rota da interface de rede de origem para o endereço de destino. Além disso, rotas podem ser necessárias para evitar o roteamento assimétrico. Recomendamos que você configure o roteamento de políticas conforme descrito em Configurar o roteamento para uma interface de rede adicional.
- Também é possível usar o comando
numactl
para executar o aplicativo. Nessa abordagem, você usa a memória e as CPUs que estão no mesmo nó NUMA da interface de rede escolhida.
Depois de concluir as etapas anteriores, as instâncias do aplicativo serão executadas usando uma interface de rede específica.
Mudar o aplicativo para usar as duas interfaces
Requisitos:
- Esse método exige mudanças no código do aplicativo.
- Você precisa de permissões para um ou mais dos seguintes métodos:
- O
bind()
só exige permissões especiais se estiver usando uma porta de origem privilegiada. SO_BINDTODEVICE
: exige a permissãoCAP_NET_RAW
.
- O
- Esse método pode exigir que você modifique a tabela de roteamento do kernel para estabelecer rotas e evitar o roteamento assimétrico.
Visão geral de alto nível
Para implementar esse padrão, faça o seguinte:
- Adicione a vinculação de interface de rede ao código-fonte do aplicativo usando uma
das seguintes opções:
- Use a chamada de sistema
bind()
para vincular um soquete a um endereço IP de origem específico. - Use a opção de soquete
SO_BINDTODEVICE
para vincular um soquete a uma interface de rede específica.
- Use a chamada de sistema
- Se o aplicativo estiver atuando como cliente, será necessário criar um soquete de cliente separado para cada interface de rede de origem.
- Modifique a tabela de rotas do kernel conforme necessário para garantir que haja uma rota da interface de rede de origem para o endereço de destino. Além disso, talvez você precise de rotas para evitar o roteamento assimétrico. Recomendamos que você configure o roteamento de políticas conforme descrito em Configurar o roteamento para uma interface de rede adicional.
- Recomendamos que você particione a atividade de rede em linhas de execução que são executadas no mesmo nó NUMA que a interface gVNIC. Uma maneira comum de solicitar um nó NUMA específico para uma linha de execução é chamar
pthread_setaffinity_np
.- Como o aplicativo usa recursos em vários nós NUMA, evite usar
numactl
ou verifique se o comandonumactl
inclui os nós NUMA de todas as interfaces de rede usadas pelo aplicativo.
- Como o aplicativo usa recursos em vários nós NUMA, evite usar
Usar um namespace de rede dedicado para aplicativos específicos
Requisitos:
- Requer a capacidade
CAP_SYS_ADMIN
. - Não é compatível com o Autopilot do GKE.
- Se você estiver usando o GKE, precisará de um contêiner privilegiado.
Esta seção descreve padrões que podem ser usados para criar um namespace de rede que usa uma interface de rede secundária. O padrão certo para sua carga de trabalho depende do seu cenário específico. As abordagens que usam switch virtual ou IPvlan são mais adequadas para casos em que vários aplicativos precisam usar a interface secundária de namespaces de rede diferentes.
Visão geral: mover a interface secundária para um namespace de rede dedicado
Esse padrão envolve a criação de um namespace de rede, a movimentação da interface gVNIC secundária para o novo namespace e a execução do aplicativo nesse namespace. Esse padrão pode ser menos complicado de configurar e ajustar em comparação com o uso de um switch virtual. No entanto, aplicativos fora do novo namespace de rede não poderão acessar a gVNIC secundária.
O exemplo a seguir mostra uma série de comandos que podem ser usados para mover eth1 para o novo namespace de rede chamado "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 esse comando é executado, a expressão <command> é executada dentro do namespace de rede e usa a interface eth1.
Os aplicativos em execução no novo namespace de rede agora usam a gVNIC secundária. Também é possível usar o comando numactl
para executar o aplicativo usando a memória e as CPUs que estão no mesmo nó NUMA da interface de rede escolhida.
Visão geral de alto nível: usar um namespace de rede e um comutador virtual para uma interface secundária. Esse padrão envolve a criação de uma configuração de comutador virtual para usar o gVNIC secundário de um namespace de rede.
As etapas gerais são as seguintes:
- Crie um par de dispositivos Ethernet virtual (veth). Ajuste a unidade máxima de transmissão (MTU) em cada um dos dispositivos para corresponder à MTU da gVNIC secundária.
- Execute o seguinte comando para garantir que o encaminhamento de IP esteja ativado para IPv4: sysctl -w net.ipv4.ip_forward=1
- Mova uma extremidade do par veth para um novo namespace de rede e deixe a outra extremidade no namespace raiz.
- Mapeie o tráfego do dispositivo veth para a interface gVNIC secundária. Há várias maneiras de fazer isso, mas recomendamos que você crie um intervalo de alias de IP para a interface secundária da VM e atribua um endereço IP desse intervalo à interface filha no namespace.
- Execute o aplicativo no novo namespace de rede. Use o comando
numactl
para executar o aplicativo usando memória e CPUs que estão no mesmo nó NUMA da interface de rede escolhida.
Dependendo da configuração do convidado e da carga de trabalho, também é possível usar o driver IPvlan com uma interface IPvlan vinculada à gVNIC secundária em vez de criar os dispositivos veth.
Mapear todo o tráfego de um contêiner para uma única interface
Requisitos:
- Seu aplicativo precisa ser executado em um contêiner que usa um namespace de rede para rede de contêineres, como GKE, Docker ou Podman. Não é possível usar a rede do host.
Muitas tecnologias de contêiner, como GKE, Docker e Podman, usam um namespace de rede dedicado para isolar o tráfego de um contêiner. Esse namespace de rede pode ser modificado diretamente ou usando as ferramentas da tecnologia de contêiner para mapear o tráfego para uma interface de rede diferente.
O GKE exige que a interface principal esteja presente para a comunicação interna do Kubernetes. No entanto, a rota padrão no pod pode ser alterada para usar a interface secundária, conforme mostrado no seguinte manifesto do pod do GKE.
metadata:
…
annotations:
networking.gke.io/default-interface: 'eth1'
networking.gke.io/interfaces: |
[
{"interfaceName":"eth0","network":"default"},
{"interfaceName":"eth1","network":"secondary-network"},
]
Essa abordagem não garante o alinhamento NUMA entre a interface de rede padrão e as CPUs ou a memória.
Faça peering das VPCs e deixe o sistema balancear a carga das sessões entre as interfaces.
Requisitos:
- O peering de VPC precisa ser estabelecido entre as VPCs das gVNICs primária e secundária.
- A versão 6.16 do kernel do Linux é necessária para fazer o balanceamento de carga de sessões TCP em interfaces de origem ao enviar para um único IP e porta de destino.
- A carga de trabalho ainda pode atender aos requisitos de desempenho quando a pilha de rede gera transferências de memória entre sockets.
Visão geral de alto nível
Em alguns casos, é difícil fragmentar conexões de rede em um aplicativo ou entre instâncias dele. Nesse cenário, para alguns aplicativos executados em VMs A3U ou A4 que não são sensíveis à transferência entre NUMA ou entre sockets, pode ser conveniente tratar as duas interfaces como fungíveis.
Um método para fazer isso é usar o sysctl fib_multipath_hash_policy e uma rota de 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}
Dividir o tráfego entre redes
Requisitos:
nic0
enic1
na VM estão em VPCs e sub-redes separadas. Esse padrão exige que os endereços de destino sejam fragmentados nas VPCs denic0
enic1
.
Visão geral de alto nível
Por padrão, o kernel do Linux cria rotas para a sub-rede de nic0
e a sub-rede de nic1
que roteiam o tráfego por destino pela interface de rede apropriada.
Por exemplo, suponha que nic0
use a VPC net1
com a sub-rede subnet-a
, e nic1
use a VPC net2
com a sub-rede subnet-b
. Por padrão, as comunicações com endereços IP de pares em subnet-a
usam nic0
, e as comunicações com endereços IP de pares em subnet-b
usam nic1
. Por exemplo, esse cenário pode ocorrer com um conjunto de VMs de peering com uma única NIC conectadas a net1
e outro conjunto conectado a net2
.
Usar SNAT para escolher a interface de origem
Requisitos:
- O
CAP_NET_ADMIN
é necessário para configurar as regras iniciais do iptables, mas não para executar o aplicativo. - Avalie com cuidado as regras ao usá-las em combinação com outras regras de iptables ou configurações de roteamento não triviais.
Observação:
- A vinculação de NIC só está correta no momento em que a conexão é criada. Se uma thread for movida para uma CPU associada a um nó NUMA diferente, a conexão sofrerá penalidades entre NUMAs. Portanto, essa solução é mais útil quando há um mecanismo para vincular linhas de execução a conjuntos de CPU específicos.
- Somente as conexões originadas por essa máquina serão vinculadas a uma NIC específica. As conexões de entrada serão associadas à NIC correspondente ao endereço de destino.
Visão geral de alto nível
Em cenários em que é difícil usar namespaces de rede ou fazer mudanças no aplicativo, é possível usar o NAT para escolher uma interface de origem. É possível usar ferramentas como iptables para reescrever o IP de origem de um fluxo e corresponder ao IP de uma interface específica com base em uma propriedade do aplicativo de envio, como cgroup, usuário ou CPU.
O exemplo a seguir usa regras baseadas na CPU. O resultado final é que um fluxo originado de uma linha de execução em execução em qualquer CPU é transmitido pela gVNIC anexada ao nó NUMA correspondente dessa 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"