proxy reverso nginx - tente upstream A, depois B e A novamente

proxy reverso nginx - tente upstream A, depois B e A novamente

Estou tentando configurar o nginx como proxy reverso, com um grande número de servidores backend. Gostaria de iniciar os back-ends sob demanda (na primeira solicitação recebida), então tenho um processo de controle (controlado por solicitações HTTP) que inicia o back-end dependendo da solicitação que recebe.

Meu problema é configurar o nginx para fazer isso. Aqui está o que tenho até agora:

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

Isso não funciona - o nginx parece ignorar quaisquer códigos de status retornados do servidor de controle. Nenhuma das error_pagediretivas no @handle_502local funciona e o código 451 é enviado como está para o cliente.

Desisti de tentar usar o redirecionamento nginx interno para isso e tentei modificar o servidor de controle para emitir um redirecionamento 307 para o mesmo local (para que o cliente tentasse novamente a mesma solicitação, mas agora com o servidor backend inicializado). No entanto, agora o nginx está sobrescrevendo estupidamente o código de status pelo obtido na tentativa de solicitação de back-end (502), apesar de o servidor de controle estar enviando um cabeçalho "Localização". Finalmente consegui "funcionar" alterando a linha error_page para error_page 502 =307 @handle_502;, forçando assim todas as respostas do servidor de controle a serem enviadas de volta ao cliente com um código 307. Isso é muito hackeado e indesejável, porque 1) não há controle sobre o que o nginx deve fazer a seguir, dependendo da resposta do servidor de controle (idealmente, só queremos tentar novamente o back-end se o servidor de controle relatar sucesso) e 2) nem todos os protocolos HTTP os clientes suportam redirecionamentos HTTP (por exemplo, usuários curl e aplicativos que usam libcurl precisam ativar os seguintes redirecionamentos explicitamente).

Qual é a maneira correta de fazer com que o nginx tente fazer proxy para o servidor upstream A, depois B e A novamente (idealmente, apenas quando B retornar um código de status específico)?

Responder1

Pontos chave:

  • Não se preocupe com upstreamblocos para failover, se o ping em um servidor ativar outro - não há como informar ao nginx (pelo menos, não a versão FOSS) que o primeiro servidor está ativo novamente. O nginx testará os servidores em ordem na primeira solicitação, mas não nas solicitações de acompanhamento, apesar de qualquer configuração backupou weightconfiguração fail_timeout.
  • Vocêdevehabilite recursive_error_pagesao implementar failover usando error_pagelocais nomeados.
  • Habilite proxy_intercept_errorspara lidar com códigos de erro enviados do servidor upstream.
  • A =sintaxe (por exemplo error_page 502 = @handle_502;, ) é necessária para tratar corretamente os códigos de erro no local nomeado. Se =não for usado, o nginx usará o código de erro do bloco anterior.

Aqui está um resumo:

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

Resposta original/registro de pesquisa a seguir:


Aqui está uma solução alternativa melhor que encontrei, o que é uma melhoria, pois não requer redirecionamento de 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;
}

Em seguida, basta fazer com que o servidor de controle retorne 502 em caso de "sucesso" e torcer para que o código nunca seja retornado pelos back-ends.


Atualização: o nginx continua marcando a primeira entrada no upstreambloco como inativa, portanto, não testa os servidores em ordem em solicitações sucessivas. Tentei adicionar weight=1000000000 fail_timeout=1à primeira entrada sem efeito. Até o momento não encontrei nenhuma solução que não envolva redirecionamento de cliente.


Editar: Mais uma coisa que eu gostaria de saber - para obter o status de erro do error_pagemanipulador, use esta sintaxe: error_page 502 = @handle_502;- esse sinal de igual fará com que o nginx obtenha o status de erro do manipulador.


Editar: E consegui funcionar! Além da error_pagecorreção acima, tudo o que foi necessário foi ativar o recursive_error_pages!

Responder2

Você poderia tentar algo como o seguinte

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

}

informação relacionada