обратный прокси-сервер nginx — попробуйте восходящий поток A, затем B, затем снова A

обратный прокси-сервер nginx — попробуйте восходящий поток A, затем B, затем снова A

Я пытаюсь настроить nginx как обратный прокси с большим количеством бэкенд-серверов. Я хотел бы запускать бэкенды по требованию (по первому входящему запросу), поэтому у меня есть процесс управления (контролируемый HTTP-запросами), который запускает бэкенд в зависимости от получаемого запроса.

Моя проблема в настройке nginx для этого. Вот что у меня есть на данный момент:

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

Это не работает - nginx, похоже, игнорирует любые коды статуса, возвращаемые сервером управления. Ни одна из error_pageдиректив в @handle_502расположении не работает, и код 451 отправляется клиенту как есть.

Я отказался от попыток использовать внутреннее перенаправление nginx для этого и попытался изменить сервер управления, чтобы он выдавал перенаправление 307 в то же место (чтобы клиент повторил тот же запрос, но теперь с запущенным сервером бэкенда). Однако теперь nginx тупо перезаписывает код статуса тем, который он получил при попытке запроса бэкенда (502), несмотря на то, что сервер управления отправляет заголовок «Location». Я наконец заставил это «работать», изменив строку error_page на error_page 502 =307 @handle_502;, тем самым заставив все ответы сервера управления отправляться обратно клиенту с кодом 307. Это очень хакерски и нежелательно, потому что 1) нет контроля над тем, что nginx должен делать дальше в зависимости от ответа сервера управления (в идеале мы хотим повторять бэкенд только в том случае, если сервер управления сообщает об успешном выполнении), и 2) не все клиенты HTTP поддерживают перенаправления HTTP (например, пользователи curl и приложения, использующие libcurl, должны явно включить следующие перенаправления).

Как правильно заставить nginx попытаться подключиться к вышестоящему серверу A, затем к B, затем снова к A (в идеале, только когда B возвращает определенный код состояния)?

решение1

Ключевые моменты:

  • Не беспокойтесь о upstreamблокировках для отказоустойчивости, если пинг одного сервера поднимет другой — нет способа сообщить nginx (по крайней мере, версии FOSS), что первый сервер снова доступен. nginx будет пробовать серверы по порядку при первом запросе, но не при последующих запросах, несмотря на любые настройки backupили weightнастройки fail_timeout.
  • Тыдолженвключить recursive_error_pagesпри реализации отказоустойчивости с использованием error_pageименованных расположений.
  • Включите proxy_intercept_errorsобработку кодов ошибок, отправленных с вышестоящего сервера.
  • Синтаксис =(например error_page 502 = @handle_502;, ) необходим для правильной обработки кодов ошибок в указанном месте. Если =не используется, nginx будет использовать код ошибки из предыдущего блока.

Вот краткое изложение:

server {
    listen ...;
    server_name $DOMAINS;

    recursive_error_pages on;

    # First, try "Upstream A"
    location / {
        error_page 418 = @backend;
        return 418;
    }

    # Define "Upstream A"
    location @backend {
        proxy_pass http://$IP:81;
        proxy_set_header  X-Real-IP     $remote_addr;
        # Add your proxy_* options here
    }

    # On error, go to "Upstream B"
    error_page 502 @handle_502;

    # Fallback static error page, in case "Upstream B" fails
    root /home/nginx/www;
    location = /_static_error.html {
        internal;
    }

    # Define "Upstream B"
    location @handle_502 { # What to do when the backend server is not up
        proxy_pass ...;
        # Add your proxy_* options here
        proxy_intercept_errors on;          # Look at the error codes returned from "Upstream B"
        error_page 502 /_static_error.html; # Fallback to error page if "Upstream B" is down
        error_page 451 = @backend;          # Try "Upstream A" again
    }
}

Исходный ответ / журнал исследования:


Вот лучший способ решения этой проблемы, который я нашел. Он является улучшением, поскольку не требует перенаправления клиента:

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

Затем просто заставьте сервер управления возвращать код 502 при «успешном выполнении» и надейтесь, что этот код никогда не будет возвращен бэкэндами.


Обновление: nginx продолжает отмечать первую запись в upstreamблоке как неработающую, поэтому он не пробует серверы по порядку при последовательных запросах. Я пробовал добавлять weight=1000000000 fail_timeout=1к первой записи, но безрезультатно. Пока что я не нашел решения, которое не включало бы перенаправление клиента.


Редактировать: Еще одна вещь, которую я хотел бы знать - чтобы получить статус ошибки от error_pageобработчика, используйте следующий синтаксис: error_page 502 = @handle_502;- этот знак равенства заставит nginx получить статус ошибки от обработчика.


Редактировать: И у меня это заработало! В дополнение к error_pageисправлению выше, все, что нужно было сделать, это включить recursive_error_pages!

решение2

Вы можете попробовать что-то вроде следующего

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

}

Связанный контент