Назад | Перейти на главную страницу

Сделайте так, чтобы nginx передавал имя хоста восходящего потока при обратном проксировании

Я запускаю несколько док-контейнеров с именами хостов:

web1.local web2.local web3.local

Маршрутизация к ним выполняется на основе имени хоста nginx. У меня есть прокси-сервер перед этой настройкой (на другом компьютере, подключенном к Интернету), где я определяю восходящий поток как:

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

И собственно описание виртуального хоста:

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

Теперь, поскольку контейнеры получают имя хоста «main» вместо «web1.local», они не отвечают на запрос должным образом.

Вопрос: как я могу указать nginx передать имя вышестоящего сервера вместо имени вышестоящей группы серверов в заголовке Host: при проксировании запроса?

На самом деле вы можете сделать это через proxy_set_header.

Подробнее смотрите здесь: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header или посмотрите пример использования здесь: https://stackoverflow.com/questions/12847771/configure-nginx-with-proxy-pass

Я включил динамический подход в вашу конфигурацию, опубликованную выше:

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

Вот пример со статическим именем хоста:

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

У меня была та же проблема, и я наконец решил ее, используя два уровня прокси. Вот как вы могли бы поступить в своей ситуации (я думаю):

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

Как видите, фокус состоит в том, чтобы создать локальный сервер, отвечающий на определенный порт, который будет прокси-сервером, переписав правильный хост для каждого сервера. Затем вы можете использовать эти локальные серверы в своем восходящем потоке и, наконец, использовать его в реальном прокси.

Итак, прочитав всю документацию для nginx (я не мог действительно разобрать код для восходящего модуля = (), я придумал это ублюдочное решение. К сожалению, это решение не отслеживает отказавшие хосты, а просто выбирает случайный и перенаправляет запрос на него. Поэтому мне нужно настроить какой-то мониторинг, чтобы убедиться, что все серверы работают.

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

Мы передаем восходящий адрес в виде отдельного заголовка, как этот

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

Что, если бы вы попробовали?

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

Хотя цель кажется логичной, nginx не собирается изменять заголовок Host: для соответствия восходящему потоку. Вместо этого лечит upstream доменные имена, такие как CNAME в DNS - как способ добраться до IP-адреса.

Заголовки (и тело) запроса фиксируются до выбора восходящего потока. Восходящий поток может изменить средний запрос, если он не отвечает, но запрос не меняется.

Хм. У меня аналогичная установка, в которой я просто сделал

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

Использование $http_host (заголовок HTTP Host из входящего запроса) здесь, а не $host (конфигурация имени хоста сервера) в моем тестировании заставляет тот же заголовок Host, переданный клиентом, передается восходящему потоку.

Смотрите также https://stackoverflow.com/questions/14352690/change-host-header-in-nginx-reverse-proxy.

Поскольку другие люди уже писали с использованием переменной скрипта (например, $ upstream), вы можете установить ее так, как вам нравится, и это решит проблему без дополнительного взлома заголовка.

Переменные сценария угроз обработчика Proxy Pass по-другому: если значение не является условным (в имени нет $), оно передается в вышестоящий поток на этапе настройки и используется позже.

Простой способ опустить эту проблему и получить большинство преимуществ (бесплатной версии) апстрима - это использовать что-то вроде 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;
}

Приведенный выше пример выглядит почти так же, как и восходящий поток. Существуют другие модули, выполняющие отображение, т.е. chash_map_module, но поскольку они не входят в дерево, вам нужно будет построить их самостоятельно, что невозможно для некоторых случаев использования /