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

89

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

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: при передаче запроса?

pavel_karoukin
источник
3
Я не думаю, что ты можешь. Почему бы вам не настроить свои серверы на ответ main или example.com? Дело не в том, что бэкэнд не знает, кто это . Обратное вполне возможно: proxy_set_header Host $ host; заменит любую переменную хоста, возвращающуюся из апстрима, именем хоста из исходного запроса.
Андрей Домашек
Надо исправить приложение.
Майкл Хэмптон

Ответы:

109

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

Для получения более подробной информации смотрите здесь: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header или посмотрите пример использования здесь: https://stackoverflow.com/questions/12847771/configure-nginx- с-прокси-проход

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

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;
  }
}
Дженс Брэдлер
источник
7
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for; кажется лучше
сиванн
1
@ pavel: понял. На самом деле я также провел некоторые исследования и некоторые тесты. Кажется, что нет прямого подхода для выполнения вашего требования. Таким образом, даже «ублюдочное» решение является решением. Я не люблю спрашивать, почему вы хотите это сделать. Я уверен, что у тебя есть свои причины. :-)
Дженс Брэдлер
@JensBradler Вы, кажется, более опытный, чем я, не могли бы вы сказать мне, что вы думаете о моем решении? Я хочу сделать то же самое , потому что я запустить две копию моего сайта с двух счетов на моем провайдере: site1.myisp.comи site2.myisp.comони реагируют только на их соответствующее название. Теперь мне принадлежит мое доменное имя, и я хотел бы использовать свой интернет-провайдер для балансировки нагрузки на свои серверы. Разве это не хорошая причина? Большое спасибо;)
ncenerar
1
@ncenerar Вы можете сделать это, но это приведет вас к единственной точке отказа: балансировщику нагрузки. Если это для балансировки нагрузки (не избыточности), вы можете также использовать балансировку нагрузки на основе DNS в сочетании с отказоустойчивостью DNS.
Дженс Брэдлер,
2
Этот ответ отражает совет официального блога .
Бернард Россет
28

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

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

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

ncenerar
источник
Изначально я использовал подход Lua, но теперь полностью переключился на HAProxy, который позволяет делать то, что я хотел со стандартной конфигурацией.
pavel_karoukin
3

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

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
источник
2

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

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;
  }
}
dalore
источник
2

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

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

GreenReaper
источник
0

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

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 .

lyngvi
источник
0

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

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

Простой способ избежать этой проблемы и получить большинство преимуществ (бесплатной версии) в апстриме - использовать что-то вроде 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 , но, поскольку они находятся вне дерева, вам нужно будет создать их самостоятельно, что невозможно в некоторых случаях использования /

Mazeryt
источник