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, na sigla em inglês). Essas IPUs estão disponíveis na VM como placas de rede virtuais do Google (gVNICs, na sigla em inglês) e fornecem largura de banda de rede para atividades de armazenamento, como estabelecer checkpoints, carregar dados de treinamento, carregar modelos e outras necessidades gerais de rede. A topologia de 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, independentemente de como planeja usar várias placas de rede (NIC, na sigla em inglês) 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 ao descarte 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
numactlpara 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, na sigla em inglês) e a descarga de recebimento grande (LRO, na sigla em inglês).
- 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 (em inglês) para verificar se elas podem ser úteis para sua carga de trabalho.
- Para A4X, a Nvidia recomenda desativar a programação automática de NUMA (em inglês).
- 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 | ⛔ | |
| Associar todo o tráfego de um contêiner a 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 balanceamento de carga das sessões entre 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* |
| Fragmentar tráfego entre redes | Processo de interface dupla: processar fragmento 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: processar fragmento 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_BINDTODEVICErequer 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 detalhada
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_BINDTODEVICEpara 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 da 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
numactlpara 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_BINDTODEVICErequer 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 detalhada
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_BINDTODEVICEpara 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 da 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 particionar 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
numactlou verifique se o comandonumactlinclui 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 competência
CAP_SYS_ADMIN. - Incompatí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 chave 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 detalhada: 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. Ele pode ser menos complicado de configurar e ajustar em comparação com o uso de uma chave 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 detalhada: como usar um comutador virtual e um namespace de rede para uma interface secundária. Esse padrão envolve a criação de uma configuração de chave virtual para usar a gVNIC secundária de um namespace de rede.
As etapas gerais são as seguintes:
- Crie um par de dispositivos Virtual Ethernet (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.
- Associe o tráfego do dispositivo veth à 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
numactlpara executar o aplicativo com 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 (em inglês) com uma interface IPvlan vinculada à gVNIC secundária em vez de criar os dispositivos veth.
Associar todo o tráfego de um contêiner a 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 alinhamento de NUMA entre a interface de rede padrão e as CPUs ou a memória.
Fazer peering das VPCs e permitir que o sistema faça balanceamento de carga das sessões entre interfaces
Requisitos:
- O peering das VPCs 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 soquetes.
Visão geral detalhada
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 nós NUMA ou entre soquetes, 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 vários caminhos:
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}
Fragmentar o tráfego entre redes
Requisitos:
nic0enic1na VM estão em VPCs e sub-redes separadas. Esse padrão exige que os endereços de destino sejam fragmentados nas VPCs denic0enic1.
Visão geral detalhada
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 para fazer peering de endereços IP em subnet-a usam nic0, e as comunicações para fazer peering de endereços IP em subnet-b usam nic1. Por exemplo, esse cenário pode ocorrer com um conjunto de VMs pareadas, cada uma com apenas uma NIC, conectado 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 da NIC só está correta no momento em que a conexão é criada. Se uma linha de execução for movida para uma CPU associada a um nó NUMA diferente, a conexão sofrerá penalidades entre nós NUMA. 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 detalhada
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. Use 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 que está rodando 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"