Bei Anwendungen, die den internen Lastenausgleich von AWS im selben Subnetz aufrufen, tritt eine Zeitüberschreitung auf

7

Einige Hintergrundinformationen:

Ich habe mit dem vpc von Amazon ein mäßig komplexes Netzwerk erstellt. Es ist ein dreistufiges Netzwerk in zwei Verfügbarkeitszonen. Jede Schicht hat ein Subnetz in Zone a und Zone b. Die Präsentationsebene befindet sich oben, eine Anwendungsebene in der Mitte und eine Kernebene unten.

Alle Sicherheitsgruppen und ACLs für die Subnetze erlauben derzeit ALLEN eingehenden und ausgehenden Datenverkehr, um die Oberfläche des Problems zu reduzieren.

Die Routing-Tabelle der Präsentationsschicht verweist den gesamten Datenverkehr auf ein Internet-Gateway. Das NAT-Gateway befindet sich in einem getrennten Subnetz und verweist den gesamten Datenverkehr auf das Internet-Gateway.

Meine Anwendung besteht aus zwei Komponenten, einer Benutzeroberfläche (React.js) und einer API (Node / Express). Sie werden als Docker-Images bereitgestellt. Vor jedem steht ein klassischer Load Balancer.

Die UI-ELB ist mit dem Internet verbunden und befindet sich in der Präsentationsschicht. Sie leitet den Datenverkehr von 80/443 an Port 8080 weiter und ist meiner App-ec2 zugeordnet, die sich im Subnetz der Anwendungsschicht befindet.

Meine API verfügt über einen internen Load Balancer. Der API-ELB befindet sich in der Anwendungsschicht (im selben Subnetz wie der app-ec2) und nimmt den Datenverkehr auf Port 80/443 auf und leitet ihn an den api-ec2 im Core auf Port 3000 weiter.

Beide Load Balancer entladen das Zertifikat, bevor sie Datenverkehr an ihre Instanzen weiterleiten.

Ich habe meine beiden Load Balancer in Route53 als Alias ​​verknüpft und in den Anwendungen durch ihre hübsche URL ( https://app.website.com ) referenziert . Jeder Load Balancer besteht die definierten Integritätsprüfungen und meldet alle verwendeten ec2-Instanzen.

Zuletzt habe ich auf der API cors mit dem Paket cors nodejs aktiviert.

Hier ist ein schnelles und schmutziges Diagramm meines Netzwerks.

Das Problem:

Die APP-ELB leitet mich erfolgreich zur Anwendung weiter. Wenn die App jedoch versucht, eine GET-Anforderung an die API-ELB zu senden, sendet sie zuerst eine OPTIONS-Anforderung, die mit dem Fehlercode 408 übereinstimmt.

Wo es komisch wird

Einige der seltsamsten Dinge, denen ich beim Debuggen begegnet bin, sind:

  1. Ich kann SSH in die App-ec2-Instanz und kann eine erfolgreiche Curl gegen die API-ELB ausführen. Ich habe viele ausprobiert und alle funktionieren. Einige Beispiele sind: curl -L https://api.website.com/system/healthcheckund curl -L -X OPTIONS https://api.website.com/system/healthcheck. Es werden immer die gewünschten Informationen zurückgegeben.
  2. Ich habe die gesamte Anwendung aus meinem Netzwerk in einen öffentlichen Standard-VPC verschoben und sie funktioniert wie vorgesehen.
  3. Ich habe die api-ec2 alle Netzwerkanforderungen an die Konsole schreiben. Während die Healthcheck-Anforderungen angezeigt werden, werden keine Anforderungen von app-ec2 angezeigt. Dies lässt mich glauben, dass der Verkehr nicht einmal die API erreicht.

Wirklich das Größte, was mich völlig ratlos macht, ist, dass das Einrollen des internen API-Elbs funktioniert, die Axios-Anforderung an dieselbe exakte URL jedoch nicht. Das ergibt für mich überhaupt keinen Sinn.

Was ich versucht habe

Ich habe ursprünglich viel Zeit damit verbracht, mit ACL-Regeln und Sicherheitsgruppen zu spielen, weil ich dachte, ich hätte etwas falsch gemacht. Schließlich sagte ich nur "Scheiß drauf" und öffnete alles, um zu versuchen, dieses Stück aus der Gleichung herauszunehmen.

Ich habe viel zu viel Zeit damit verbracht, mit Cors auf meiner API zu spielen. Irgendwann auf der Konfiguration landen, die ich jetzt habe, das ist der Standard- app.use(cors())Rückruf, der vom cors-Knotenpaket bereitgestellt wird. Ich habe auch das app.options('*', cors())empfohlen, was in der Dokumentation empfohlen wird.

Ich habe alles unter der Sonne googelt, aber speziell, ob ich mit den Elbs einige spezielle benutzerdefinierte Header definieren muss? Kann aber scheinbar nichts finden. Außerdem hat es gut funktioniert, als ich meine App aus dem Netzwerk entfernt habe.

Ich bin sicher, ich habe viele andere Dinge ausprobiert, aber diese scheinen am relevantesten zu sein. Was vermisse ich? Mir ist klar, dass dies möglicherweise ein sehr vages und weit gefasstes Thema und ein enormer Beitrag ist, aber ich schätze jeden Einblick und Ihre Zeit beim Lesen!

David Meents
quelle
Das sind zwei Subnetze pro Zone: eine Präsentationsschicht, eine Anwendungsschicht und eine Kernschicht. Das sind drei. Können Sie das klarstellen? Ich habe die Routentabelle für die Präsentationsschicht und das NAT-Laufwerk, die den gesamten Datenverkehr über ein Internet-Gateway leiten. Ich habe sowohl die Präsentations- als auch die Kernschicht, die den gesamten Datenverkehr durch das nat-Laufwerk leiten. Das scheint sich zu widersprechen. Wenn die Präsentationsschicht über das NAT (Laufwerk?) (Gateway?) Weiterleitet, wird sie nicht auch über das Internet-Gateway weitergeleitet. Welche Ihrer Ebenen befinden sich in welchem ​​Ihrer Subnetze und wie lautet die Standardroute für jedes Subnetz?
Michael - sqlbot
1
... Insbesondere muss sich Ihre nach außen gerichtete ELB in Subnetzen befinden, deren Standardroute auf das Internet-Gateway verweist. Dies bedeutet fast immer, dass es nicht korrekt ist, sie in denselben Subnetzen wie den Instanzen zu platzieren, für die der Datenverkehr ausgeglichen wird. Diese Instanzen befinden sich in einem Subnetz, dessen Standardroute das NAT-Gateway ist ... und das NAT-Gateway selbst befinden sich wiederum nicht in denselben Subnetzen wie die Instanzen, für die ausgehende Dienste bereitgestellt werden, sondern möglicherweise in demselben Subnetz als ELB.
Michael - sqlbot
Ja, tut mir leid, ich fing an, ein paar Worte durcheinander zu bringen. Ich habe zwei Subnetze (eines in Zone-a und eines in Zone-b) in jeder der drei Schichten. Die Präsentationsschicht wird ebenso wie die Nat durch das Internet-Gateway geleitet. Eine Sache, die ich nicht erwähnt habe, war, dass sich NAT in einem eigenen Subnetz befindet. Dann die App und die Kernroute durch das Nat-Gateway.
David Meents
1
Vielleicht möchten Sie die Frage gründlich prüfen und entsprechend klären - es ist eine gute Frage, aber es gibt einige schwierige Windungen, denen Sie folgen müssen, und viele Dinge, die Sie überprüfen müssen. Wenn Sie sagen "Die Benutzeroberfläche der Anwendung läuft bei einem API-Aufruf auf der Optionsanforderung ab", wer sieht diesen Fehler? Der externe Anrufer? curl -X OPTIONS 127.0.0.1...auf app-ec2? Nur OPTIONSist kaputt? Die ELBs sind "Classic", nicht "Application", richtig? Können alle Instanzen über NAT korrekt auf das Internet zugreifen, z curl ipv4.icanhazip.com. (Ja, ich frage nach einem Grund, der dunkel erscheinen mag.)
Michael - sqlbot
1
Sofern ich mich nicht völlig irre, werden die Apps von react.js im Browser ausgeführt und müssen sich an den API-Server wenden. Ihr Frontend-Server stellt lediglich HTML- und JS-Dateien bereit und leitet keine Anfragen an die API weiter
Tensibai

Antworten:

7

Was Sie also tatsächlich haben, ist Folgendes:

Schema der OP-Architektur

Da sich Ihre API-ELB in einer privaten Zone befindet, kann über das Internet nicht auf sie zugegriffen werden.
Ihr Frontend in React.js wird nur im Browser des Benutzers ausgeführt und nicht auf den UI-Servern. Diese Server dienen nur statischen Dateien.

Sie haben zwei Möglichkeiten: Konfigurieren Sie Ihre Frontend-Server so, dass API-Aufrufe an die API-ELB umgeleitet werden, oder aktualisieren Sie die API-ELB einfach so, dass sie mit dem Internet verbunden ist.

Die übliche Gefahr von JavaScript-Apps besteht darin, zu vergessen, dass sie im Browser des Benutzers und nicht wie bei einer JEE-Anwendung auf den Frontend-Servern ausgeführt werden.

Tensibai
quelle
1

Dies klingt nach einem asymmetrischen oder n-Pfad-Routing-Problem. Folgendes passiert wahrscheinlich:

Maschine A unter der IP-Adresse 192.168.1.1 initiiert eine [SYN] -Anforderung über die LB unter 192.168.1.10. Die LB überträgt dann die Nutzlast an Maschine B unter 192.168.1.2, sodass die Nutzlast jetzt die Quelle: 192.168.1.1 und das Ziel: 192.168.1.2 (früher 192.168.1.10) hat.

Was passiert nun, wenn 192.168.1.2 mit einem [SYN, ACK] antwortet? Was passieren sollte , ist, dass Maschine B über den Load Balancer auf Maschine A reagiert- In der Regel aufgrund einer Standardroute oder eines Standardgateways auf dem Server, das den Datenverkehr über die LB weiterleitet. In diesem Fall befindet sich der Computer jedoch im selben Subnetz, sodass die Route / das Gateway nicht verwendet wird und die Routing-Tabelle vom Server ignoriert wird. Dies bedeutet, dass, wenn der Server antwortet, die [SYN, ACK] für Maschine A von einer anderen IP als der IP stammt, mit der Maschine A die Anforderung initiiert hat - es wurde jedoch eine Quell-IP von 192.168.1.10 (LB) erwartet Es wird ein [SYN, ACK] von 192.168.1.2 (Maschine B) angezeigt, und daher kann die LB in diesem Szenario keine Verbindung mit Maschine B herstellen, da die Antwort an das falsche Gerät gesendet wurde.

Der Grund, warum dies für externen Datenverkehr funktioniert, liegt in Ihrer Standardroute - die Antworten an alle anderen werden über die ELB geleitet. Die ELB erkennt, dass sie eine Verbindung initiiert hat, fängt die Antwort automatisch ab und tauscht die Quelle von 192.168.1.2 zurück auf 192.168.1.10.

Für eine Lösung dieses Problems können Sie also einen einarmigen Lastausgleich implementieren (auch als Lastausgleich auf einem Stick bezeichnet). Dazu wird ein Quell-NAT auf der inneren Schnittstelle des Load Balancers verwendet (nehmen Sie also an, Sie hatten die äußere Schnittstelle 192.168.1.10 auf Ihrem Load Balancer und 192.168.1.11 auf der inneren Schnittstelle). Dies lässt den gesamten Datenverkehr aus Sicht von Maschine B aus 192.168.1.11 kommen, wodurch Ihr Verbindungsproblem gelöst werden sollte.

Es scheint jedoch, dass Ihre AWS ELB SNAT nicht unterstützt. Sie müssen daher entweder Ihre Hosts und ELB in verschiedenen Subnetzen platzieren oder SNATs wie die Virtual Edition von F5 verwenden, die stündlich oder BYOL erhältlich ist. Beachten Sie jedoch die Verbindungsbeschränkungen bei SNATing. Wenn Sie mehr als 30.000 gleichzeitige Verbindungen benötigen, wird der SNAT-Port erschöpft und Sie müssen einen SNAT-Pool verwenden. .

Daher besteht Ihre beste Lösung (aus Kostengründen und um zukünftige Probleme zu vermeiden) darin, sicherzustellen, dass sich Client und Server in unterschiedlichen Subnetzen befinden.

Die beste Möglichkeit zur Bestätigung besteht darin, tcpdump auf dem Verbindungshost und / oder dem Back-End-Server zu verwenden und nach Antworten zu suchen, die direkt zum / vom Back-End-Server kommen, anstatt den Load Balancer zu durchlaufen. Sie können dann Ihre Dump-Datei in WireShark laden, um genau herauszufinden, was los ist.

James Shewey
quelle
ELB leitet keine Pakete weiter. Es stellt neue TCP-Verbindungen her und leitet Nutzdaten weiter. Routenasymmetrie ist eine Sache, die das Problem nicht sein kann.
Michael - sqlbot
F5s auch, und sie leiden immer noch unter asymmetrischen Routing-Problemen. Selbst bei einer vollständigen Proxy-Architektur und einer separaten neuen TCP-Verbindung verwendet ein F5-Load-Balancer standardmäßig die Quelladresse des Verbindungsclients, sodass das Problem weiterhin genau wie oben beschrieben auftritt. Ich gehe davon aus, dass die ELB auf ähnliche Weise funktioniert. Ich weiß, dass sich A10s genauso verhalten.
James Shewey
Sie haben keine, ELBs haben eine separate IP. The
Robo
1
AWS ELB kann als Reverse-Proxy fungieren, nicht nur als TCP-Load-Balancer. Wie OP sagte, führt die ELB das SSL-Offloading durch, kann kein TCP-Balancer sein und muss ein HTTP-Reverse-Proxy sein. Ihre Antwort gilt nicht für den Kontext und ELB wird niemals für ausgehende Pakete verwendet (sie sind überhaupt keine Router). Wenn Sie versuchen, mit einem F5 mit 2 Schnittstellen einen Proxy zu erstellen und auf jeder Schnittstelle dasselbe Subnetz festzulegen, entsteht tatsächlich ein Problem. Die Lösung mit SNAT ist nur eine schlechte Lösung.
Tensibai
1
Nun, hier sieht die Maschine B aus Ihrem Beispiel die ELB-IP, die Client-IP befindet sich im X-Forwarded-Port-Header. Der Client könnte sich neben dem Server befinden, was kein Problem darstellt. Im HTTP-Modus fungiert ein ELB nicht als F5 mit SSL-Terminierung. (Selbst im TCP-Modus ist es immer noch ein Nginx-ähnlicher Load Balancer, überhaupt nichts Vergleichbares). Was ich denke, dass Sie hängen bleiben, ist "Proxy". Wir sprechen in der Tat über das Proxying von Paketen und nicht über das Weiterleiten von Paketen. Ich kann Ihnen einen TCP-Dump einer ELB innerhalb desselben Subnetzes wie 2 Maschinen senden, wenn Sie möchten, es funktioniert.
Tensibai