Proxy inverso nginx: intente en sentido ascendente A, luego B, luego A nuevamente

Proxy inverso nginx: intente en sentido ascendente A, luego B, luego A nuevamente

Estoy intentando configurar nginx como un proxy inverso, con una gran cantidad de servidores backend. Me gustaría iniciar los backends a pedido (en la primera solicitud que llega), por lo que tengo un proceso de control (controlado por solicitudes HTTP) que inicia el backend dependiendo de la solicitud que recibe.

Mi problema es configurar nginx para hacerlo. Esto es lo que tengo hasta ahora:

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

Esto no funciona: nginx parece ignorar los códigos de estado devueltos por el servidor de control. Ninguna de las error_pagedirectivas de la @handle_502ubicación funciona y el código 451 se envía tal cual al cliente.

Dejé de intentar usar la redirección nginx interna para esto e intenté modificar el servidor de control para emitir una redirección 307 a la misma ubicación (para que el cliente volviera a intentar la misma solicitud, pero ahora con el servidor backend iniciado). Sin embargo, ahora nginx está sobrescribiendo estúpidamente el código de estado con el que obtuvo del intento de solicitud de backend (502), a pesar de que el servidor de control está enviando un encabezado de "Ubicación". Finalmente lo hice "funcionar" cambiando la línea error_page a error_page 502 =307 @handle_502;, forzando así que todas las respuestas del servidor de control se envíen al cliente con un código 307. Esto es muy complicado e indeseable, porque 1) no hay control sobre lo que nginx debe hacer a continuación dependiendo de la respuesta del servidor de control (idealmente solo queremos volver a intentar el backend si el servidor de control informa que tuvo éxito), y 2) no todo HTTP los clientes admiten redireccionamientos HTTP (por ejemplo, los usuarios de curl y las aplicaciones que utilizan libcurl deben habilitar los siguientes redireccionamientos explícitamente).

¿Cuál es la forma correcta de hacer que nginx intente conectarse al servidor ascendente A, luego a B y luego a A nuevamente (idealmente, solo cuando B devuelve un código de estado específico)?

Respuesta1

Puntos clave:

  • No se moleste con upstreamlos bloques para la conmutación por error, si al hacer ping a un servidor se activa otro; no hay forma de decirle a nginx (al menos, no a la versión FOSS) que el primer servidor está activo nuevamente. nginx probará los servidores en orden en la primera solicitud, pero no en las solicitudes posteriores, a pesar de backupcualquier weightconfiguración fail_timeout.
  • debehabilítelo recursive_error_pagesal implementar la conmutación por error utilizando error_pageubicaciones con nombre.
  • Habilite esta opción proxy_intercept_errorspara manejar códigos de error enviados desde el servidor ascendente.
  • La =sintaxis (p. ej. error_page 502 = @handle_502;) es necesaria para manejar correctamente los códigos de error en la ubicación nombrada. Si =no se usa, nginx usará el código de error del bloque anterior.

Aquí hay un resumen:

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

A continuación se muestra el registro de respuesta/investigación original:


Aquí hay una mejor solución que encontré, que es una mejora ya que no requiere una redirección del cliente:

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

Luego, simplemente haga que el servidor de control devuelva 502 en caso de "éxito" y espere que los servidores nunca devuelvan el código.


Actualización: nginx sigue marcando la primera entrada del upstreambloque como inactiva, por lo que no prueba los servidores en orden en solicitudes sucesivas. Intenté agregar weight=1000000000 fail_timeout=1a la primera entrada sin ningún efecto. Hasta ahora no he encontrado ninguna solución que no implique una redirección del cliente.


Editar: Una cosa más que desearía saber: para obtener el estado de error del error_pagecontrolador, use esta sintaxis: error_page 502 = @handle_502;- ese signo igual hará que nginx obtenga el estado de error del controlador.


Editar: ¡Y lo hice funcionar! Además de la error_pagesolución anterior, ¡todo lo que se necesitaba era habilitar recursive_error_pages!

Respuesta2

Podrías probar algo como lo siguiente

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

}

información relacionada