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_page
diretivas no @handle_502
local 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
upstream
blocos 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çãobackup
ouweight
configuraçãofail_timeout
. - Vocêdevehabilite
recursive_error_pages
ao implementar failover usandoerror_page
locais nomeados. - Habilite
proxy_intercept_errors
para lidar com códigos de erro enviados do servidor upstream. - A
=
sintaxe (por exemploerror_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 upstream
bloco 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_page
manipulador, 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_page
correçã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;
}
}