O aplicativo que chama o balanceador de carga interno da AWS na mesma sub-rede está atingindo o tempo limite

7

Alguns antecedentes:

Criei uma rede moderadamente complexa usando o vpc da Amazon. É uma rede de três camadas em duas zonas de disponibilidade. Cada camada possui uma sub-rede na zona-ae na zona-b. A camada de apresentação está no topo, há uma camada de aplicativo no meio e uma camada de núcleo na parte inferior.

Todos os grupos de segurança e ACLs das sub-redes estão atualmente permitindo TODO o tráfego de entrada e saída para me ajudar a reduzir a área de superfície do problema.

A tabela de roteamento da camada de apresentação está apontando todo o tráfego para um gateway da Internet. O gateway NAT está em uma sub-rede segregada, apontando também todo o tráfego para o gateway da Internet.

Meu aplicativo possui dois componentes, uma interface do usuário (React.js) e uma API (Node / Express). Eles são implantados como imagens de janela de encaixe. Na frente de cada um existe um balanceador de carga clássico.

O UI-ELB está voltado para a Internet e reside na camada de apresentação, roteando o tráfego de 80/443 para a porta 8080 e está associado ao meu app-ec2, que é colocado na sub-rede da camada de aplicativo.

Minha API tem um balanceador de carga interno à sua frente. O API-ELB está na camada de aplicativo (na mesma sub-rede que o app-ec2) e pega o tráfego na porta 80/443 e o direciona para o api-ec2 no núcleo na porta 3000.

Ambos os balanceadores de carga estão transferindo o certificado antes de passar o tráfego para suas instâncias.

Eu tenho ambos os meus balanceadores de carga associados como alias no Route53 e referenciados nos aplicativos por seu bonito URL ( https://app.website.com ). Cada balanceador de carga passa as verificações de integridade definidas e relata todas as instâncias ec2 em uso.

Por fim, na API, habilitei o cors usando o pacote cors nodejs.

Aqui está um diagrama rápido e sujo da minha rede.

O problema:

O APP-ELB me encaminha com êxito para o aplicativo. No entanto, quando o aplicativo tenta enviar uma solicitação GET para o API-ELB, ele primeiro envia uma solicitação OPTIONS que atinge o tempo limite com o código de erro 408.

Onde fica estranho

Algumas das coisas mais estranhas que encontrei durante a depuração são:

  1. Posso fazer o SSH na instância app-ec2 e executar uma curvatura bem-sucedida no API-ELB. Eu tentei muitos, e todos eles funcionam. Alguns exemplos são: curl -L https://api.website.com/system/healthchecke curl -L -X OPTIONS https://api.website.com/system/healthcheck. Ele sempre retorna as informações desejadas.
  2. Mudei o aplicativo inteiro da minha rede para um vpc padrão público e ele funciona como deveria.
  3. Eu tenho o api-ec2 gravando todas as solicitações de rede no console. Embora mostre as solicitações de verificação de integridade, não mostra nenhuma solicitação do app-ec2. Isso me leva a acreditar que o tráfego nem sequer está atingindo a API.

Realmente, a maior coisa que me deixa com uma perda total é que o enrolamento do api elb interno funciona, mas os axios solicitam o mesmo URL exato não. Isso não faz sentido para mim.

O que eu tentei

No início, passei muito tempo jogando com as regras da ACL e com os grupos de segurança, pensando que fiz algo errado. Eventualmente, acabei de dizer "estrague tudo" e abri tudo para tentar tirar esse pedaço da equação.

Passei muito tempo brincando com Cors na minha API. Eventualmente, aterrissando na configuração que tenho agora, que é o app.use(cors())retorno de chamada padrão fornecido pelo pacote do nó cors. Também incluí o app.options('*', cors())recomendado na documentação.

Eu pesquisei tudo no google, mas especificamente se preciso definir alguns cabeçalhos personalizados especiais com os elbs? Mas parece que não consigo encontrar nada. Além disso, quando mudei meu aplicativo para fora da rede, ele funcionou muito bem.

Tenho certeza de que tentei muitas outras coisas, mas essas parecem ser as mais pertinentes. o que estou perdendo? Sei que essa é uma questão potencialmente muito vaga e ampla, e um post enorme, mas agradeço qualquer insight e seu tempo de leitura!

David Meents
fonte
São duas sub-redes por zona: uma camada de apresentação, uma camada de aplicativo e uma camada principal. São três. Você pode esclarecer isso? Eu tenho a tabela de rotas para a camada de apresentação e a unidade NAT roteando todo o tráfego através de um gateway da Internet. Eu tenho as camadas de apresentação e núcleo roteando todo o tráfego através da unidade nat. Isso parece auto-contraditório. Se a camada de apresentação estiver roteando através do NAT (drive?) (Gateway?), Também não estará roteando através do gateway da Internet. Quais das suas camadas estão em quais das suas sub-redes e qual é a rota padrão para cada sub-rede?
Michael - sqlbot
11
... Especificamente, o ELB voltado para o exterior deve estar em sub-redes cuja rota padrão aponta para o Gateway da Internet, o que quase sempre significa que não é correto colocá-lo na (s) mesma (s) sub-rede (s) das instâncias para as quais está equilibrando o tráfego. Essas instâncias estariam em uma sub-rede cuja rota padrão é o Gateway NAT ... e o próprio Gateway NAT, por sua vez, não estariam nas mesmas sub-redes das instâncias para as quais está fornecendo serviços de saída, mas podem estar na mesma sub-rede como o ELB.
Michael - sqlbot
Sim, desculpe por isso, comecei a misturar algumas palavras. Eu tenho duas sub-redes (uma na zona-a e uma na zona-b) em cada uma das três camadas. A camada de apresentação é roteada através do gateway da Internet, como é o nat. Uma coisa que deixei de mencionar foi que o NAT está em sua própria sub-rede separada. Em seguida, o aplicativo e o núcleo são roteados pelo gateway nat.
David Meents
11
Você pode revisar completamente a questão e esclarecer adequadamente - é uma boa pergunta, mas há algumas complicações complicadas a seguir e muitas coisas para verificar. Quando você diz "A interface do usuário do aplicativo está atingindo o tempo limite na solicitação de opções ao fazer uma chamada de API", quem vê esse erro? O chamador externo? curl -X OPTIONS 127.0.0.1...no app-ec2? Só OPTIONSestá quebrado? Os ELBs são "Clássico" e não "Aplicativo", correto? Todas as instâncias conseguem acessar corretamente a Internet via NAT, por exemplo curl ipv4.icanhazip.com? (Sim, eu pergunto por um motivo que pode parecer obscuro.)
Michael - sqlbot
11
A menos que eu esteja totalmente enganado, os aplicativos react.js são executados no navegador e precisam entrar em contato com o servidor da API, mas o servidor front-end serve apenas arquivos html e js e não faz roteamento / proxy de solicitação em relação à API
Tensibai

Respostas:

7

Então, o que você tem é:

Esquema da arquitetura do OP

Como seu API ELB está em uma zona privada, ele não pode ser acessado da Internet.
Seu frontend no React.js é executado no navegador do usuário e não nos servidores da interface do usuário; esses servidores servem apenas arquivos estáticos.

Você tem duas opções: configurar seus servidores front-end para redirecionar as chamadas da API para o API ELB ou apenas atualizar o API ELB para a Internet.

A armadilha usual dos aplicativos JavaScript é esquecer que eles são executados no navegador do usuário e não nos servidores front-end, como faria um aplicativo JEE.

Tensibai
fonte
1

Isso soa como um problema de roteamento assimétrico ou n-path. Aqui está o que provavelmente está acontecendo:

A máquina A no endereço IP 192.168.1.1 inicia uma solicitação [SYN] através do LB em 192.168.1.10. o LB faz o proxy da carga útil para a Máquina B em 192.168.1.2, então a carga útil agora tem origem: 192.168.1.1 e destino: 192.168.1.2 (que costumava ser 192.168.1.10).

Então, o que acontece agora quando 192.168.1.2 responde com um [SYN, ACK]? O que deve acontecer é que a Máquina B deve responder à Máquina A através do balanceador de carga- normalmente devido a uma rota ou gateway padrão no servidor que roteia o tráfego através do LB. Nesse caso, no entanto, a máquina está na mesma sub-rede, portanto, a rota / gateway não é usada e a tabela de roteamento ignorada pelo servidor. Isso significa que, quando o servidor responde, o [SYN, ACK] parece que a Máquina A é proveniente de um IP diferente do IP com o qual a Máquina A iniciou a solicitação - esperava um IP de origem 192.168.1.10 (o LB), mas está vendo um [SYN, ACK] vindo de 192.168.1.2 (máquina B) e, portanto, o LB não consegue estabelecer uma conexão com a máquina B nesse cenário porque a resposta foi para o dispositivo errado.

A razão pela qual isso funciona para o tráfego externo é devido à sua rota padrão - as respostas para todos os outros são roteadas através do ELB. O ELB vê que estava iniciando uma conexão e intercepta automaticamente a resposta e troca a fonte de 192.168.1.2 de volta para 192.168.1.10.

Portanto, para uma solução para esse problema, você pode implementar o balanceamento de carga com um braço (também conhecido como balanceador de carga em um stick). O que isso fará é usar um NAT de origem na interface interna do balanceador de carga (então, suponha que você tenha uma interface externa 192.168.1.10 no seu balanceador de carga e 192.168.1.11 na interface interna). Isso fará com que todo o tráfego pareça vir do 192.168.1.11 da perspectiva da Máquina B, o que deve resolver o problema de conexão.

Parece, no entanto, que o seu AWS ELB não oferece suporte ao SNAT , portanto, você precisará colocar seus hosts e ELB em sub-redes diferentes ou usar algo que suporte SNATs, como o Virtual Edition da F5, que vem com sabores de hora em hora ou BYOL . Porém, tenha cuidado com as limitações de conexão com o SNAT - se você precisar de mais de 30k conexões simultâneas, encontrará a exaustão da porta SNAT e precisará começar a usar um pool SNAT. .

Portanto, sua melhor solução (por custos e para evitar problemas futuros) seria garantir que o cliente e o servidor estejam em sub-redes diferentes.

A melhor maneira de confirmar seria usar o tcpdump no host de conexão e / ou no servidor back-end e procurar respostas vindas diretamente do / para o servidor back-end em vez de passar pelo balanceador de carga. Você pode carregar seu arquivo de despejo no WireShark para descobrir exatamente o que está acontecendo.

James Shewey
fonte
ELB não encaminha pacotes. Faz novas conexões TCP e encaminha cargas úteis. A assimetria de rota é uma coisa que o problema não pode ser.
Michael - sqlbot
O mesmo acontece com os F5 e eles ainda sofrem com problemas de roteamento assimétrico. Mesmo com uma arquitetura de proxy completa e uma nova conexão TCP separada, um balanceador de carga F5, por padrão, o endereço de origem do cliente que está se conectando; portanto, o problema ainda ocorre exatamente como descrito acima. Estou assumindo que o ELB funcione de maneira semelhante. Eu sei que os A10 se comportam da mesma maneira.
precisa saber é o seguinte
Eles não têm, os ELBs têm um IP separado. The
Robo
11
O AWS ELB pode atuar como um proxy reverso, não apenas como um balanceador de carga TCP. Como a OP disse que o ELB está realizando o descarregamento de SSL, ele não pode ser um balanceador de TCP e precisa ser um proxy reverso HTTP. Sua resposta não é aplicável ao contexto e o ELB nunca é usado para pacotes de saída (eles não são roteadores). Além disso, se você tentar fazer proxy com um F5 com 2 interfaces e configurar a mesma sub-rede em cada interface, estará criando um problema, resolvê-lo com o SNAT é apenas uma solução ruim.
Tensibai
11
Bem, aqui o que a máquina B do seu exemplo vê é o IP ELB, o IP do cliente estará no cabeçalho X-Forwarded-Port. O cliente pode deixar de lado o servidor que não será um problema. Quando no modo HTTP, um ELB não atua como um F5 com terminação SSL. (mesmo no modo TCP, ainda é um nginx como o balanceador de carga, nada remotamente comparável). O que eu acho que você está esperando é o "proxy", na verdade estamos falando de pacotes de proxy e não de encaminhamento de pacotes. Posso enviar um tcpdump de um ELB na mesma sub-rede que 2 máquinas, se você quiser, ele funciona.
Tensibai