odwrotne proxy nginx - spróbuj w górę A, potem B, a potem jeszcze raz A.

22

Próbuję skonfigurować nginx jako zwrotny serwer proxy z dużą liczbą serwerów zaplecza. Chciałbym uruchomić backendy na żądanie (przy pierwszym przychodzącym żądaniu), więc mam proces kontroli (kontrolowany przez żądania HTTP), który uruchamia backend w zależności od otrzymanego żądania.

Moim problemem jest skonfigurowanie nginx do tego. Oto co mam do tej pory:

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;
    }
}

To nie działa - nginx wydaje się ignorować wszelkie kody statusu zwrócone z serwera kontrolnego. Żadna z error_pagedyrektyw w @handle_502lokalizacji nie działa, a kod 451 jest wysyłany „tak jak jest” do klienta.

Zrezygnowałem z próby użycia do tego wewnętrznego przekierowania nginx i próbowałem zmodyfikować serwer sterujący, aby wysyłał przekierowanie 307 do tej samej lokalizacji (aby klient ponowił to samo żądanie, ale teraz z uruchomionym serwerem zaplecza). Jednak teraz nginx głupio nadpisuje kod statusu kodem otrzymanym z próby żądania zaplecza (502), mimo że serwer sterujący wysyła nagłówek „Lokalizacja”. W końcu „działa”, zmieniając wiersz error_page naerror_page 502 =307 @handle_502;, zmuszając w ten sposób wszystkie odpowiedzi serwera sterującego do wysłania z powrotem do klienta z kodem 307. Jest to bardzo hackerskie i niepożądane, ponieważ 1) nie ma kontroli nad tym, co nginx powinien zrobić dalej, w zależności od odpowiedzi serwera kontrolnego (idealnie chcemy ponowić próbę zaplecza tylko wtedy, gdy serwer kontrolny zgłasza sukces), i 2) nie wszystkie HTTP klienci obsługują przekierowania HTTP (np. użytkownicy curl i aplikacje korzystające z libcurl muszą wyraźnie włączyć następujące przekierowania).

Jaki jest właściwy sposób, aby nginx spróbował proxy do serwera nadrzędnego A, a następnie B, a następnie A (najlepiej, gdy B zwróci określony kod stanu)?

Vladimir Panteleev
źródło

Odpowiedzi:

20

Kluczowe punkty:

  • Nie przejmuj się upstreamblokami przełączania awaryjnego, jeśli pingowanie jednego serwera spowoduje podniesienie innego - nie ma sposobu, aby powiedzieć nginx (przynajmniej nie wersja FOSS), że pierwszy serwer znów działa. nginx spróbuje serwery w kolejności na pierwsze żądanie, ale nie follow-up żądań, pomimo wszelkich backup, weightlub fail_timeoutustawień.
  • Państwo musi umożliwiać recursive_error_pagesprzy wykonywaniu pracy awaryjnej przy użyciu error_pagei nazwany lokalizacje.
  • Włącz proxy_intercept_errorsobsługę kodów błędów wysyłanych z serwera nadrzędnego.
  • =Składni (np error_page 502 = @handle_502;) jest potrzebny do prawidłowej obsługi kodów błędów w nazwie lokalizacji. Jeśli =nie jest używany, nginx użyje kodu błędu z poprzedniego bloku.

Oryginalny dziennik odpowiedzi / dziennika badań:


Oto lepsze obejście, które znalazłem, które jest ulepszeniem, ponieważ nie wymaga przekierowania klienta:

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;
}

Następnie po prostu poproś serwer sterujący, aby zwrócił 502 po „sukcesie” i mam nadzieję, że kod nigdy nie zostanie zwrócony przez backendy.


Aktualizacja: nginx ciągle zaznacza pierwszy wpis w upstreambloku jako wyłączony, więc nie sprawdza serwerów po kolei przy kolejnych żądaniach. Próbowałem dodać weight=1000000000 fail_timeout=1do pierwszego wpisu bez efektu. Do tej pory nie znalazłem żadnego rozwiązania, które nie wymagałoby przekierowania klienta.


Edycja: Jeszcze jedna rzecz, o której chciałbym wiedzieć - aby uzyskać status błędu z error_pageprocedury obsługi, użyj tej składni: error_page 502 = @handle_502;- znak równości spowoduje, że nginx otrzyma status błędu z procedury obsługi.


Edycja: I mam to działa! Oprócz error_pagepowyższej poprawki wystarczyło włączyć recursive_error_pages!

Vladimir Panteleev
źródło
1
Dla mnie proxy_next_upstreamzrobiłem lewę (cóż, mój scenariusz nie był tak skomplikowany jak twój), chciałem tylko, aby nginx wypróbował następny serwer, jeśli wystąpił błąd, dlatego musiałem dodać proxy_next_upstream error timeout invalid_header non_idempotent;( non_idempotent, ponieważ chcę głównie przekazywać POSTżądania).
Philipp
1

Możesz spróbować czegoś takiego

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
źródło
nginx nie spróbuje ponownie a.example.netpo tym, jak raz zakończy się niepowodzeniem dla tego samego żądania. Wyśle klientowi napotkany błąd podczas próby połączenia b.example.net, co nie będzie zgodne z oczekiwaniami, chyba że zaimplementuję także proxy na serwerze sterującym.
Vladimir Panteleev
A co by było z twoją konfiguracją w następnej sytuacji: żądanie do zwrotnego upstream A nie powiodło się, upstream B powiodło się niepowodzenie, a następnie znów próbujemy upstream A, a także uzyskać błąd (502)?
ALex_hha,
Upstream B jest serwerem kontrolnym. Jego celem jest upewnienie się, że kolejne żądanie do wyższego poziomu A powiedzie się. Celem jest wypróbowanie w górę A, jeśli nie powiodło się, próba w górę B, jeśli „udało się” (przy użyciu naszej wewnętrznej konwencji „sukces”), spróbuj ponownie w górę A. Jeśli moje pytanie nie było wystarczająco jasne, daj mi znać, jak mogę je poprawić.
Vladimir Panteleev
Hmm, załóżmy, że upstream A jest wyłączony, na przykład z powodu jakiegoś problemu sprzętowego. Co zrobi upstream B? Czy jest w stanie zwrócić odpowiedź na żądanie klienta?
ALex_hha
Ten problem nie dotyczy przełączania awaryjnego w przypadku awarii sprzętu. Ten problem dotyczy uruchamiania backendów na żądanie na żądanie. Jeśli serwer sterujący (upstream B) nie może reaktywować backendu (upstream A), to idealnie, że użytkownik powinien otrzymać odpowiedni komunikat o błędzie, ale nie jest to problem, który próbuję rozwiązać - problemem jest ponowienie próby nginx Znów po B, w ramach tego samego żądania.
Vladimir Panteleev