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

22

Я пытаюсь настроить 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 возвращает определенный код состояния)?

Владимир Пантелеев
источник

Ответы:

20

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

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

Оригинальный ответ / журнал исследований:


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

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!

Владимир Пантелеев
источник
1
Для меня proxy_next_upstreamэто помогло (ну, мой сценарий был не так сложен, как ваш), я просто хотел, чтобы nginx попробовал следующий сервер, если произошла ошибка, поэтому мне пришлось добавить proxy_next_upstream error timeout invalid_header non_idempotent;( non_idempotentпотому что я хочу в основном пересылать POSTзапросы).
Филипп
1

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

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
источник
nginx не будет пытаться повторить попытку a.example.netпосле того, как произойдет сбой по одному и тому же запросу. Он отправит клиенту сообщение об ошибке, возникшей при попытке подключения b.example.net, что не будет тем, что они ожидали, если бы я не внедрил прокси на управляющем сервере.
Владимир Пантелеев
И что будет с вашим конфигом в следующей ситуации: запрос к обратному потоку обратного потока А, обратный отказ восходящего потока В, затем мы снова пробуем восходящий поток А и также получим ошибку (502)?
ALex_hha
Upstream B является управляющим сервером. Его цель - убедиться, что следующий запрос к восходящему каналу A будет успешным. Цель состоит в том, чтобы попробовать восходящий поток A, если это не удалось, попробовать восходящий поток B, если он "преуспел" (используя наше внутреннее соглашение об "успехе"), попробовать снова восходящий поток A. Если мой вопрос не был достаточно ясен, дайте мне знать, как я могу улучшить его.
Владимир Пантелеев
Хмм, давайте предположим, что вышестоящий А отключен, например, из-за некоторых проблем с оборудованием. Что сделает вверх по течению Б? Способен ли он вернуть ответ на запрос от клиента?
ALex_hha
Эта проблема не связана с отказом при сбое оборудования. Эта проблема связана с запуском бэкэндов по требованию. Если управляющий сервер (вышестоящий B) не может повторно активировать бэкэнд (вышестоящий A), то в идеале пользователь должен получить соответствующее сообщение об ошибке, но это не проблема, которую я пытаюсь решить - проблема в том, чтобы попытаться повторить nginx А после Б снова в рамках того же запроса.
Владимир Пантелеев