Nginx. Reverse Proxy

Разберём, что такое Reverse Proxy. А также я покажу как настроить Nginx в качестве Reverse Proxy (обратного прокси сервера).















Теоретическая часть




Иногда бывает нужно чтобы различные url запросы обрабатывались на разных серверах, но первоначально приходили на один сервер. Например, вы пробросили порт на своем роутере на один веб-сервер в вашей внутренней сети. Но хотите чтобы каталог /xxx открывался на втором веб-сервере, а /yyy открывался на третьем, и не хотите пробрасывать порты на каждый web-сервер. Получается вот такая схема:




Схема работы Nginx - Reverse Proxy
Nginx — Reverse Proxy




То что мы хотим сделать из сервера web1, называется reverse proxy (обратный прокси).




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




Reverse proxy принимает запросы от клиентов для того чтобы проксировать их на проксируемые web-сервера (как показано на рисунке).




В качестве обратного прокси сервера для веб серверов может выступать apace или nginx. Есть и другие решения, но в этой статье разберём настройку nginx. На официальном сайте nginx есть небольшой раздел, посвящённый reverse proxy.




Схема сети




Я буду использовать операционную систему Devuan, это облегченный вариант Debian без systemd. Про установку Devuan я уже писал здесь.




У нас будет три сервера:




  • proxy — reverse proxy — установлен nginx;
  • web1 — backend web server — установлен apache2;
  • web2 — backend web server — установлен apache2;




Настраиваем Nginx (Revers Proxy)




Устанавливаем nginx на сервере proxy:




# apt install -y nginx




У меня установился nginx такой версией:




# nginx -v
nginx version: nginx/1.14.2




Для настройки прокси создадим новый виртуальный хост:




# nano /etc/nginx/sites-available/proxy
server {
        # Основные настройки
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name proxy;

        # Управление заголовками на прокси сервере
        proxy_set_header X-Scheme http;
        proxy_set_header X-Forwarded-Proto http;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Real-IP $remote_addr;

        # настройка буфера для прокси сервера
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;

        location / {
                # вначале попытаемся обработать запрос как файл,
                # затем как каталог, затем вернём ошибку 404
                try_files $uri $uri/ =404;
        }
        
        # проксируем запрос /xxx на web1
        location /xxx {
                proxy_pass http://web1/test/;
        }
        
        # проксируем запрос /yyy на web2
        location /yyy {
                proxy_pass http://web2/test/;
        }
}




После чего отключим конфиг «default«, включим созданный нами конфиг «proxy» и перезагрузим службу сервера:




# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/proxy /etc/nginx/sites-enabled/proxy
# service nginx restart




На основных настройках я не буду заострять внимание, потому как они не относятся к настройке reverse proxy. Если вкратце там мы указали какие порты слушает наш сервер, корень сайта, индексные страницы и имя сервера.




Далее рассмотрим те опции, которые относятся к самому проксированию.




Заголовки X-Scheme и X-Forwarded-Proto




При проксировании мы можем менять заголовки, чтобы backend сервер правильно обрабатывал запросы.




Вначале нужно поменять схему и протокол в запросе, если у вас backend сервер работает на одном протоколе (например http), а к proxy подключаются по другому протоколу (например https). То-есть в заголовках X-Scheme и X-Forwarded-Proto нужно указать протокол подключения к backend серверу.




Схема и протокол это разные понятия. То что вы пишите в браузере (http или https) — это схема. А уже схема указывает браузеру по какому протоколу подключаться к серверу.




В общем, чтобы поменять значения этих заголовков, мы используем следующие параметры в конфиге:




proxy_set_header X-Scheme http;
proxy_set_header X-Forwarded-Proto http;




Заголовок host




Заголовок host, один из самых важных заголовков. Он определяет адрес домена, который запрашивает браузер. Сервер, получив запрос, ищет у себя сайт с доменом из заголовка host, а также указанную страницу.




Подменить этот заголовок можно используя следующие переменные:




  • $http_host — это то что указано в адресной строке вместе с портом, если он указан в адресной строке. Это работает только если порт не стандартный, например 8080. В нашем случае это proxy;
  • $host — это имя прокси сервера. Оно записано в /etc/nginx/sites-available/proxy, в параметре server_name. В нашем случае proxy;
  • $proxy_host — это имя backend сервер на который мы проксируем. В нашем случае это web1 или web2.




В конфиге мы указали следующее:




proxy_set_header Host $http_host;




Заголовок X-Forwarded-For




Заголовок X-Forwarded-For содержит список прокси серверов по которым прошёлся клиент перед этим сервером, а переменная $proxy_add_x_forwarded_for содержит полученный заголовок X-Forwarder-For плюс добавляет свой сервер в этот список (это используется для передачи реального ip-клиента на backend).




В конфиге мы указали следующее:




proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;




Заголовки X-Real-PORT и X-Real-IP




Эти заголовки содержать содержат ip адрес и порт с которого подключается клиент. Чтобы передать эти значения backend серверу, мы указали следующие параметры:




proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Real-IP $remote_addr;




Параметры отвечающие за буферную память




А следующие строки отвечают за настройку работы буферной памяти для проксируемой информации:




  • proxy_buffering — позволяет включить или отключить буферную память для прокси сервера;
  • proxy_buffer_size — размер буфера для первой части ответа получаемого от проксируемого сервера, такая часть ответа включает в себя только заголовки и хранится отдельно от остальной информации;
  • proxy_buffers — число и размер буферов для одного соединения, а вот сюда помещается ответ от проксируемого сервера.




proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 8k;




Что и куда проксируем




Теперь разберем часть конфига, где мы указываем что проксировать и куда проксировать:




location /xxx {
        proxy_pass http://web1/test/;
}

location /yyy {
        proxy_pass http://web2/test/;
}




  • /xxx — запрос который будем проксировать;
  • proxy_pass http://web1/test/ — куда будем проксировать, то есть на сервер web1 и на каталог /test.
  • Ниже подобный блок для /yyy .




Настраиваем Apache2 (backend сервера)




На обоих серверах проделаем одно и тоже! Во-первых установим apache2, затем создадим новый каталог /var/www/html/test. В этом каталоге сделаем индексную страничку index.html в которую запишем html код. Вдобавок поменяем владельца нового каталога на www-data.




Проделываем следующее на обоих серверах:




# apt install -y apache2
# mkdir /var/www/html/test

# nano /var/www/html/test/index.html #(на втором сервере поменяйте web1 на web2)
<!DOCTYPE HTML>
 <html>
  <head>
   <title>web1</title>
  </head>
 <body>
  <p>web1</p>
 </body>
</html>

# chown -R www-data:www-data /var/www/html/test/




Проверка




Используя адрес прокси сервера, откроем xxx:




web1




Используя адрес прокси сервера, откроем yyy:




web2




Как видим у нас открылись разные странички, которые лежат на разных серверах!




Резервирование серверов




Теперь сделаем так, чтобы один и тот же запрос ходил по следующим правилам:




  • /xxx — проксируем на http:/web1/test/ — если он доступен;
  • а если не доступен, то проксируем на http://web2/test/.




Для этого поправим наш конфиг /etc/nginx/sites-enabled/proxy и перед блоком server добавим блок upstream:




# nano /etc/nginx/sites-enabled/proxy
upstream backend {
        server web1:80 fail_timeout=120s max_fails=3;
        server web2:80 backup;
}

server {
        listen 80 default_server;

*** сократил ***




В этом блоке указываем оба web-сервера. При этом web1 будет основным сервером. Но если proxy в течении 120 секунд получит три ошибки при обращении к web1, то на 120 секунд все запросы пойдут на web2.




Ниже в блоке server, где мы указывали что на что проксировать (блоки location), поменяем записи. Вместо web1 укажем название апстрима, которое мы придумали выше (backend). Второй блок (location /yyy) — удаляем. Получится так:




# проксируем запрос /xxx
location /xxx {
         proxy_pass http://backend/test/;
}




Полностью конфиг будет выглядеть так:




upstream backend {
        server web1:80 fail_timeout=120s max_fails=3;
        server web2:80 backup;
}

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name proxy;

        proxy_set_header X-Scheme http;
        proxy_set_header X-Forwarded-Proto http;
        proxy_set_header Host $proxy_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;

        location / {
                try_files $uri $uri/ =404;
        }

        location /xxx {
                proxy_pass http://backend/test/;
        }
}




Перезапустим nginx:




# service nginx restart




Проверка резервирования




В браузере, используя адрес прокси сервера, открываем /xxx:




Запрос xxx идет на web1




Затем отключаем сервер web1 и обновляем страничку:




Запрос xxx идет на web2




Как видим мы перешли на второй сервер. Теперь включим обратно web1 и обновим страничку еще раз:




Запрос xxx идет на web1




Теперь мы вернулись на первый сервер.




Таким образом вы можете создать два одинаковых веб сайта и в случае неисправности первого сервера, ваш сайт продолжит работать на втором сервере. Клиенты могут даже ничего не заметить.




Проксирование и абсолютные ссылки




Теперь приведу одну проблему revers proxy серверов — абсолютные ссылки. Пусть наш сайт (на сервере web1) будет содержать ссылку на некоторую свою страничку. Создадим каталог и в нём новую страничку:




# mkdir /var/www/html/test/test/
# nano /var/www/html/test/test/test.html
<!DOCTYPE HTML>
<html>
 <body>
  <p>Hello WEB1</p>
 </body>
</html>




Там же поправим индексную страничку и добавим в неё ссылку:




# nano /var/www/html/test/index.html
<!DOCTYPE HTML>
 <html>
  <head>
   <title>web1</title>
  </head>
 <body>
  <p>web1</p>
  <p><a href="/test/test/test.html">test</a></p>
 </body>
</html>




Обратите внимание, ссылка не относительная, а абсолютная. То-есть начинается с корня сайта.




Откроем web1 напрямую и проверим работу ссылки:




Проверка работы перехода по ссылки на web1




Как видим, при нажатии на ссылку мы переходим по адресу http://web1/test/test/test.html. И у нас всё работает!




Теперь проверим работу на proxy:




Проверка работы перехода по ссылки на proxy




На proxy ссылка не работает. Обратите внимание, на каталог к которому мы пытаемся подключиться /test/test/test.html. А у нас на proxy есть только /xxx, который проксируется на /test. А самого /test нет, вот поэтому мы получили ошибку.




Для того, чтобы решить это, нужно на proxy придумывать одноимённые пути для проксирования. Для этого на proxy отредактируйте конфиг:




# nano /etc/nginx/sites-enabled/proxy
location /test {
        proxy_pass http://backend/test/;
}

# service nginx restart




Проверим работу ссылки на proxy ещё раз:




Проверка работы перехода по ссылки на proxy




Дополнительная информация




Вообще проксирование не простая тема.




Например, проксировать можно https на http или наоборот, но при этом могут возникать проблемы, так как приложение работающее на backend сервере может генерировать ссылки основываясь на схеме. Получается вы к прокси подключаетесь по https, передаёте на backend в заголовке схему http. Затем backend отвечает на http, и приложение генерирует другие ссылки на http и ваш браузер перескакивает на работу по http к proxy, а он допустим умеет только https (и редирект на https не настроен). Получается вот такая картина:




Проблема проксирования, когда proxy и backend работают на разных протоколах




Поэтому, в идеале, нужно чтобы Revers Proxy работал на том же протоколе, что и Backend сервер.




Или проблема может быть ещё хуже. Ссылки на Backend сервере абсолютные и помимо пути содержат адрес сервера, например такие: http://backend/test/ . Это приведёт к тому, что нажав на ссылку вы перескочите с Proxy сервера к Backend серверу, а он может быть напрямую и недоступен.




Поэтому revers proxy делают в основном к своим внутренним сервисам, которые тоже контролируются. А не на внешние сайты. Которые могут и не уметь работать через revers proxy. Для этой задачи применяют forward proxy, например Squid.









Итог




Здесь я разобрал многое, но не всё. Например, можно настроить не резервирование, а балансировку. Про неё, кстати, статей в интернете больше чем про резервный сервер. Я постарался использовать многие http заголовки, чтобы показать как их можно изменять при проксировании. Возможно не все заголовки вам будут нужны в реальной конфигурации. Это зависит от web-приложения, которое вы используете. Буфер для прокси сервера тоже нужно настраивать под конкретную задачу. Поэтому просто копировать приведённые выше конфиги не желательно, нужно анализировать свои действия и все перепроверять.



2021-07-21T12:00:00
Сервера Linux