Nginx-Reverse-Proxy - versuchen Sie es mit Upstream A, dann B, dann A erneut

22

Ich versuche, Nginx als Reverse-Proxy mit einer großen Anzahl von Back-End-Servern einzurichten. Ich möchte das Backend auf Anforderung starten (bei der ersten eingehenden Anforderung), daher habe ich einen Steuerungsprozess (gesteuert durch HTTP-Anforderungen), der das Backend abhängig von der empfangenen Anforderung startet.

Mein Problem ist die Konfiguration von Nginx, um es zu tun. Folgendes habe ich bisher:

server {
    listen 80;
    server_name $DOMAINS;

    location / {
        # redirect to named location
        #error_page 418 = @backend;
        #return 418; # doesn't work - error_page doesn't work after redirect

        try_files /nonexisting-file @backend;
    }

    location @backend {
        proxy_pass http://$BACKEND-IP;
        error_page 502 @handle_502; # Backend server down? Try to start it
    }

    location @handle_502 { # What to do when the backend server is not up
        # Ping our control server to start the backend
        proxy_pass http://127.0.0.1:82;
        # Look at the status codes returned from control server
        proxy_intercept_errors on;
        # Fallback to error page if control server is down
        error_page 502 /fatal_error.html;
        # Fallback to error page if control server ran into an error
        error_page 503 /fatal_error.html;
        # Control server started backend successfully, retry the backend
        # Let's use HTTP 451 to communicate a successful backend startup
        error_page 451 @backend;
    }

    location = /fatal_error.html {
        # Error page shown when control server is down too
        root /home/nginx/www;
        internal;
    }
}

Dies funktioniert nicht - nginx scheint alle vom Steuerungsserver zurückgegebenen Statuscodes zu ignorieren. Keine der error_pageDirektiven am @handle_502Standort funktioniert, und der Code 451 wird unverändert an den Client gesendet.

Ich habe aufgehört, die interne Nginx-Umleitung zu verwenden, und habe versucht, den Steuerungsserver so zu ändern, dass eine 307-Umleitung an denselben Speicherort gesendet wird (sodass der Client dieselbe Anforderung erneut versucht, der Back-End-Server jedoch jetzt gestartet wird). Jetzt überschreibt nginx jedoch dummerweise den Statuscode mit dem, den es aus dem Back-End-Anforderungsversuch (502) erhalten hat, obwohl der Steuerungsserver einen "Standort" -Header sendet. Ich habe es schließlich "funktioniert", indem ich die error_page-Zeile in geändert habeerror_page 502 =307 @handle_502;Dadurch werden alle Antworten des Steuerungsservers gezwungen, mit einem 307-Code an den Client zurückgesendet zu werden. Dies ist sehr schwierig und unerwünscht, da 1) es keine Kontrolle darüber gibt, was Nginx als nächstes tun soll, abhängig von der Antwort des Kontrollservers (im Idealfall möchten wir das Backend nur wiederholen, wenn der Kontrollserver Erfolg meldet), und 2) nicht alles HTTP Clients unterstützen HTTP-Weiterleitungen (z. B. Curl-Benutzer und libcurl-verwendende Anwendungen müssen die folgenden Weiterleitungen explizit aktivieren).

Was ist der richtige Weg, um Nginx zu veranlassen, einen Proxy für Upstream-Server A, dann B und dann wieder A zu erstellen (idealerweise nur, wenn B einen bestimmten Statuscode zurückgibt)?

Vladimir Panteleev
quelle

Antworten:

20

Wichtige Punkte:

  • Machen Sie sich keine Gedanken upstreamüber Failover-Blockierungen, wenn ein Ping-Befehl einen anderen Server aufruft. Es gibt keine Möglichkeit, nginx (zumindest nicht die FOSS-Version) mitzuteilen, dass der erste Server wieder aktiv ist. nginx wird die Server , um auf der ersten Anfrage versuchen, aber nicht Follow-up - Anforderungen, trotz aller backup, weightoder fail_timeoutEinstellungen.
  • Sie müssenrecursive_error_pages beim Implementieren eines Failovers die Verwendung von error_pageund benannten Speicherorten aktivieren .
  • Aktivieren Sie proxy_intercept_errorsdiese Option , um Fehlercodes zu verarbeiten, die vom Upstream-Server gesendet wurden.
  • Die =Syntax (z. B. error_page 502 = @handle_502;) ist erforderlich, um Fehlercodes am angegebenen Speicherort korrekt zu behandeln. Wenn dies =nicht verwendet wird, verwendet nginx den Fehlercode aus dem vorherigen Block.

Originalantwort / Rechercheprotokoll folgen:


Hier ist eine bessere Problemumgehung, die ich gefunden habe. Dies ist eine Verbesserung, da keine Clientumleitung erforderlich ist:

upstream aba {
    server $BACKEND-IP;
    server 127.0.0.1:82 backup;
    server $BACKEND-IP  backup;
}

...

location / {
    proxy_pass http://aba;
    proxy_next_upstream error http_502;
}

Bringen Sie dann den Steuerungsserver dazu, bei "Erfolg" den Wert 502 zurückzugeben, und hoffen Sie, dass der Code von den Backends nie zurückgegeben wird.


Update: nginx markiert den ersten Eintrag im upstreamBlock weiterhin als inaktiv, sodass die Server nicht nacheinander überprüft werden. Ich habe versucht weight=1000000000 fail_timeout=1, den ersten Eintrag ohne Wirkung hinzuzufügen . Bisher habe ich keine Lösung gefunden, die keine Clientumleitung beinhaltet.


Bearbeiten: Ich wünschte, ich wüsste noch etwas - um den Fehlerstatus vom error_pageHandler abzurufen, verwenden Sie diese Syntax: error_page 502 = @handle_502;- Das Gleichheitszeichen veranlasst nginx, den Fehlerstatus vom Handler abzurufen.


Edit: Und ich habe es funktioniert! Zusätzlich zu dem error_pageobigen Fix war alles, was benötigt wurde, das Aktivieren recursive_error_pages!

Vladimir Panteleev
quelle
1
Für mich war das proxy_next_upstreamder Trick (nun, mein Szenario war nicht so komplex wie deins), ich wollte nur, dass Nginx den nächsten Server ausprobiert, wenn ein Fehler auftritt, also musste ich hinzufügen proxy_next_upstream error timeout invalid_header non_idempotent;( non_idempotentweil ich hauptsächlich POSTAnfragen weiterleiten möchte ).
Philipp
1

Sie können Folgendes ausprobieren

upstream backend {
    server a.example.net;
    server b.example.net backup;
}

server {
    listen   80;
    server_name www.example.net;

    proxy_next_upstream error timeout http_502;

    location / {
        proxy_pass http://backend;
        proxy_redirect      off;
        proxy_set_header    Host              $host;
        proxy_set_header    X-Real-IP         $remote_addr;
        proxy_set_header    X-Forwarded-for   $remote_addr;
    }

}
ALex_hha
quelle
nginx versucht a.example.netes nicht erneut, nachdem es bei derselben Anforderung einmal fehlgeschlagen ist. Es sendet den Fehler, der beim Verbindungsaufbau aufgetreten ist, an den Client. b.example.netDies ist nicht der erwartete Fehler , es sei denn, ich implementiere die Proxy-Funktion auch auf dem Steuerungsserver.
Vladimir Panteleev
Und was wäre mit deiner Konfiguration in der nächsten Situation: Anfrage an den Upstream-A-Return scheitern, Upstream-B-Return scheitern, dann versuchen wir es erneut mit Upstream-A und bekommen auch einen Fehler (502)?
ALex_hha
Upstream B ist der Steuerungsserver. Sie soll sicherstellen, dass die nächste Anforderung an Upstream A erfolgreich ist. Das Ziel ist es, Upstream A zu testen. Wenn es fehlgeschlagen ist, versuchen Sie es mit Upstream B. Wenn es "erfolgreich" war (unter Verwendung unserer internen Konvention von "Erfolg"), versuchen Sie es erneut mit Upstream A. Wenn meine Frage nicht klar genug war, lassen Sie mich wissen, wie ich sie verbessern kann.
Vladimir Panteleev
Hmm, nehmen wir an, dass Upstream A nicht verfügbar ist, z. B. wegen Hardwareproblemen. Was macht Upstream B? Ist es in der Lage, eine Antwort auf die Anfrage vom Client zurückzugeben?
ALex_hha
Bei diesem Problem handelt es sich nicht um ein Failover für Hardwarefehler. Bei diesem Problem geht es darum, Backends bei Bedarf zu starten. Wenn der Kontrollserver (Upstream B) das Backend (Upstream A) nicht reaktivieren kann, sollte der Benutzer im Idealfall eine entsprechende Fehlermeldung erhalten, aber es ist nicht das Problem, das ich zu lösen versuche - das Problem besteht darin, Nginx erneut zu versuchen A wieder nach B, innerhalb der gleichen Anfrage.
Vladimir Panteleev