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

22

Estou tentando configurar o nginx como um proxy reverso, com um grande número de servidores back-end. Eu gostaria de iniciar os back-end sob demanda (na primeira solicitação que entra), para ter 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 eu 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 qualquer código de status retornado 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 back-end iniciado). No entanto, agora o nginx está substituindo estupidamente o código de status pelo código obtido na tentativa de solicitação de back-end (502), apesar de o servidor de controle estar enviando um cabeçalho "Local". Finalmente consegui "mudar" a linha error_page paraerror_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 hacky 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, queremos apenas tentar novamente o back-end somente se o servidor de controle relatar êxito) e 2) nem todo o HTTP os clientes suportam redirecionamentos HTTP (por exemplo, usuários curl e aplicativos que usam libcurl precisam ativar explicitamente os seguintes redirecionamentos).

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

Vladimir Panteleev
fonte

Respostas:

20

Pontos chave:

  • Não se preocupe com os upstreamblocos de failover, se o ping de um servidor abrir outro - não há como dizer ao nginx (pelo menos, não a versão do FOSS) que o primeiro servidor está novamente ativo. nginx tentará os servidores em ordem sobre o primeiro pedido, mas não os pedidos de acompanhamento, apesar de nenhum backup, weightou fail_timeoutconfigurações.
  • Você deve habilitar recursive_error_pagesao implementar o failover usando error_pagelocais nomeados.
  • Habilite proxy_intercept_errorspara manipular códigos de erro enviados do servidor upstream.
  • A =sintaxe (por exemplo error_page 502 = @handle_502;) é necessária para manipular 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.

A resposta original / registro da pesquisa segue:


Aqui está uma solução alternativa melhor que encontrei, que é uma melhoria, pois não requer um 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, faça com que o servidor de controle retorne 502 em "success" e espere que o código nunca seja retornado pelos back-ends.


Atualização: o nginx continua marcando a primeira entrada do upstreambloco como inativa, para que não tente os servidores em ordem em solicitações sucessivas. Eu tentei adicionar weight=1000000000 fail_timeout=1à primeira entrada sem efeito. Até agora, não encontrei nenhuma solução que não envolva um redirecionamento de cliente.


Edit: 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.


Edit: E eu tenho que trabalhar! Além da error_pagecorreção acima, tudo o que era necessário era habilitar recursive_error_pages!

Vladimir Panteleev
fonte
1
Para mim proxy_next_upstream, o truque (bem, meu cenário não era tão complexo quanto o seu), eu só queria que o nginx tentasse o próximo servidor se ocorresse um erro; portanto, tive que adicionar proxy_next_upstream error timeout invalid_header non_idempotent;( non_idempotentporque eu quero encaminhar principalmente POSTsolicitações).
Philipp
1

Você pode 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;
    }

}
ALex_hha
fonte
O nginx não tentará novamente a.example.netdepois que falhou uma vez na mesma solicitação. Ele enviará ao cliente o erro encontrado ao tentar se conectar b.example.net, o que não será o que eles esperavam, a menos que eu implementasse o proxy no servidor de controle também.
Vladimir Panteleev
E o que seria da sua configuração na próxima situação: solicitação para o retorno A upstream falha, retorno B upstream falha, depois tentamos novamente o upstream A e também obtemos falha (502)?
ALex_hha 08/09/2013
Upstream B é o servidor de controle. Seu objetivo é garantir que a próxima solicitação para o upstream A seja bem-sucedida. O objetivo é tentar a montante A, se falhar, tentar a montante B, se "for bem-sucedido" (usando nossa convenção interna de "sucesso"), tente a montante A novamente. Se minha pergunta não foi clara o suficiente, informe-me como melhorá-la.
Vladimir Panteleev 08/09
Hmm, vamos supor que o montante A esteja inativo, por exemplo, algum problema de hardware. O que fará o B upstream? É capaz de retornar a resposta do pedido do cliente?
precisa saber é o seguinte
Esse problema não é sobre failover para falhas de hardware. Esse problema é sobre o início de back-up de upstream sob demanda. Se o servidor de controle (upstream B) não puder reativar o back-end (upstream A), o ideal é que o usuário receba uma mensagem de erro apropriada, mas não é o problema que estou tentando resolver - o problema está fazendo com que o nginx tente novamente A depois de B novamente, dentro da mesma solicitação.
Vladimir Panteleev