Faça o nginx passar o nome do host do upstream ao fazer a reversão

89

Eu executo vários contêineres de docker com nomes de host:

web1.local web2.local web3.local

O roteamento para estes é feito com base no nome do host pelo nginx. Eu tenho um proxy na frente desta configuração (em uma máquina diferente conectada à Internet) onde eu defino o upstream como:

    upstream main {
      server web1.local:80;
      server web2.local:80;
      server web3.local:80;
    }

E descrição real do host virtual:

    server {
      listen 80;
      server_name example.com;
      location / {
        proxy_pass http://main;
      }
    }

Agora, como os contêineres recebem o nome do host "main" em vez de "web1.local", eles não respondem corretamente à solicitação.

Pergunta: como posso dizer ao nginx para passar o nome do servidor upstream em vez do nome do grupo upstream de servidores no cabeçalho Host: ao solicitar o proxy?

pavel_karoukin
fonte
3
Eu acho que você não pode. Por que você não define seus servidores de back-end para responder a main ou example.com? Não é como se o backend não sabe quem ele é. O inverso é facilmente possível: proxy_set_header Host $ host; substituirá qualquer variável Host retornando do upstream pelo nome do host da solicitação original.
Andrew Domaszek
A coisa certa a fazer é corrigir o aplicativo.
Michael Hampton

Respostas:

109

Na verdade, você pode fazer isso via proxy_set_header.

Para obter mais detalhes, consulte aqui: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header ou veja um exemplo de caso de uso aqui: https://stackoverflow.com/questions/12847771/configure-nginx- com-proxy-pass

Eu incluí a abordagem dinâmica na sua configuração postada acima:

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $host;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}

Aqui está um exemplo com um nome de host estático:

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            www.example.com;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}
Jens Bradler
fonte
7
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for; parece melhor
sivann
11
@ pavel: entendi. Na verdade, eu também fiz algumas pesquisas e alguns testes. Parece que não há uma abordagem direta para atender às suas necessidades. Portanto, mesmo uma solução "bastardizada" é uma solução. Não gosto de perguntar por que você gostaria de fazer isso. Tenho certeza que você tem seus motivos. :-)
Jens Bradler
@JensBradler Você parece mais especialista do que eu, poderia me dizer o que acha da minha solução? Eu quero fazer o mesmo, porque eu executar duas cópias do meu site a partir de duas contas no meu ISP: site1.myisp.come site2.myisp.come eles só respondem ao respectivo nome. Agora, sou o proprietário do meu nome de domínio e gostaria de usar o site do ISP para equilibrar a carga em meus servidores. Essa não é uma boa razão? Muito obrigado;)
ncenerar
11
@ ncenerar Você pode fazer isso, mas isso o levará a um único ponto de falha: o balanceador de carga. Se for para balanceamento de carga (não redundância), você também poderá usar o balanceamento de carga baseado em DNS em combinação com o failover de DNS.
Jens Bradler
2
Esta resposta reflete os conselhos do blog oficial .
Bernard Rosset
28

Eu tive o mesmo problema e finalmente o resolvi usando dois níveis de proxy. Aqui está como você poderia fazer por sua situação (eu acho):

server {
  listen      8001 default_server;
  server_name web1.example.com;
  location / {
    proxy_pass       http://web1.local:80;
    proxy_set_header Host web1.local:80;
  }
}

server {
  listen      8002 default_server;
  server_name web2.example.com;
  location / {
    proxy_pass       http://web2.local:80;
    proxy_set_header Host web2.local:80;
  }
}

server {
  listen      8003 default_server;
  server_name web3.example.com;
  location / {
    proxy_pass       http://web3.local:80;
    proxy_set_header Host web3.local:80;
  }
}

upstream main {
  server 127.0.0.1:8001;
  server 127.0.0.1:8002;
  server 127.0.0.1:8003;
}

server {
  listen      80;
  server_name example.com;
  location / {
    proxy_pass http://main;
  }
}

Como você pode ver, o truque é criar um servidor local que responda a uma porta específica que fará proxy do servidor, reescrevendo o host correto para cada servidor. Em seguida, você pode usar esses servidores locais no seu upstream e, finalmente, usá-lo no proxy real.

ncenerar
fonte
Eu originalmente usei a abordagem Lua, mas agora mudei completamente para o HAProxy, que permite fazer exatamente o que eu queria com a configuração padrão.
pavel_karoukin
3

Então, ao ler toda a documentação do nginx (eu realmente não consegui analisar o código do módulo upstream = (), eu vim com essa solução bastardizada. Infelizmente, essa solução não controla os hosts com falha, mas simplesmente seleciona um aleatório e redireciona a solicitação para ele. Portanto, tenho que configurar algum tipo de monitoramento para garantir que todos os back-ends estejam em execução.

server {
        listen 80;
        server_name example.com;
        resolver 127.0.0.1;

        location / {
                set $upstream "";
                rewrite_by_lua '
                        local upstreams = {
                                "http://web1.dokku.localdomain",
                                "http://web2.dokku.localdomain",
                                "http://web3.dokku.localdomain",
                                "http://web4.dokku.localdomain"
                        }
                        ngx.var.upstream = upstreams[ math.random( #upstreams ) ] 
                ';
                proxy_pass $upstream;
        }
}
pavel_karoukin
fonte
2

Passamos no endereço upstream como um cabeçalho separado como este

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    add_header       X-Upstream      $upstream_addr;
  }
}

E se você tentasse?

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $upstream_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    add_header       X-Host          $host;
  }
}
dalore
fonte
2

Embora o objetivo pareça lógico, o nginx não mudará o cabeçalho Host: para corresponder ao upstream . Em vez disso, trata upstreamnomes de domínio como um CNAMEno DNS - como uma maneira de acessar um endereço IP.

Os cabeçalhos da solicitação (e o corpo) são corrigidos antes da seleção do upstream. O upstream pode mudar no meio da solicitação se um determinado upstream não responder, mas a solicitação não muda.

GreenReaper
fonte
0

Hmm. Eu tenho uma configuração semelhante, na qual eu simplesmente fiz

location / {
    ... 
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_pass ...;
}

O uso de $http_host(o cabeçalho do host HTTP da solicitação de entrada) aqui em vez de $host(a configuração do nome do host do servidor) faz com que o mesmo cabeçalho do host passado pelo cliente seja passado para o upstream no meu teste.

Consulte também https://stackoverflow.com/questions/14352690/change-host-header-in-nginx-reverse-proxy .

lyngvi
fonte
0

Como outras pessoas já postaram usando a variável de script (como $ upstream), você pode configurá-lo da maneira que desejar, e isso corrigirá o problema, sem invasão adicional do cabeçalho.

As variáveis ​​de script de ameaça do manipulador do Proxy Pass de uma maneira diferente, se um valor não for condicional (não tiver $ no nome) for feito backup no upstream na fase de configuração e usado posteriormente.

Uma maneira simples de omitir esse problema e ter as vantagens de (versão gratuita) upstream seria usar algo como Split_Clients:

split_clients $request_uri $my_upstream {
              33%          server1.domainX.com;
              33%          server2.domainX.com;
# Always use DOT at end entry if you wonder why, read the SC code.
              *            server3.domainX.com;  
}
location / {
    ... 
    proxy_pass http://$my_upstream;
}

O exemplo acima parece quase o mesmo que upstream. Existem outros módulos para o mapeamento, por exemplo , chash_map_module , mas como eles estão fora da árvore, você precisará construí-los por conta própria, o que não é possível em alguns casos de uso /

Mazeryt
fonte