Исторически роль обратного прокси (reverse proxy) можно назвать первоначальным предназначением для Nginx, а значит и для Angie. В этой статье разберёмся, почему он обратный, какие тонкости настройки проксирования нужно учитывать при настройке.
Навигация по циклу
Настройка location в Angie. Разделение динамических и статических запросов.
Перенаправления в Angie: return, rewrite и примеры их применения.
Сжатие текста в Angie: статика, динамика, производительность.
Настройка Angie в роли обратного HTTP-прокси.
Видеоверсия
Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.
Что такое обратный прокси
Термин прокси‑сервер происходит от proxy (посредник). Клиент отправляет запросы к удалённым серверам не напрямую, а через прокси‑сервер. Стандартные применение прокси — кэширование ресурсов Интернета, контроль трафика, сокрытие адреса клиента. То есть, обычный прокси имеет ограниченный круг клиентов и неограниченный список серверов для запросов. Пример классической реализации прокси на Linux — squid. Ранее прокси‑сервер был практически обязательным атрибутом любой офисной сети, так как позволял экономить ценную полосу интернет‑канала и трафик.
Обратный прокси решает другую задачу: они принимает подключения от неограниченного круга клиентов и перенаправляет запросы на жестко ограниченный набор серверов (обычно их называют апстримами (upstream) или бэкендами). В качестве обратных прокси можно использовать различные веб‑серверы: Angie, Nginx, Apache и т. д.
Задачи обратного прокси разнообразны:
эффективная обработка большого количества соединений с клиентами;
обеспечение защищенного подключения (TLS);
ограничение доступа;
кэширование ответов бэкендов;
динамическое сжатие ответов;
балансировка нагрузки;
ограничение частоты запросов;
маршрутизация запросов на различные серверы.
Многие веб‑приложения предполагают наличия в архитектуре обратного прокси‑сервера для решения таких задач. Далее мы разберём популярные конфигурации Angie для исполнения роли обратного прокси.
Проксирование по HTTP
За проксирование по протоколу HTTP(S) отвечает модуль http_proxy. С помощью этого модуля можно организовать доступ к веб‑сервису через Angie. Базовая директива, отвечающая за проксирования запроса — proxy_pass. Однако, типичная конфигурация location для проксирования включает несколько дополнительных директив.
Рассмотрим пример динамического location c проксированием по HTTP.
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
Такая конфигурация характерна для серверов, где в рамках одной системы работает фронтенд и бэкенд приложения. Запросы в location будут проксироваться на TCP‑сокет localhost:8000
c использованием протокола HTTP. В качестве адреса бэкенд‑сервера можно использовать UNIX‑сокет или имя группы серверов. Если в параметре proxy_pass помимо адреса сервера фигурирует URI (на забывайте, что / — это тоже URI), то возникают различные побочные эффекты, которые мы рассмотрим отдельно. При этом создаётся отдельное подключение к бэкенду, которое будет разорвано сразу же после обработки запроса. По умолчанию ответ буферизуется в памяти или на диске (если не хватило буферов в памяти).
Далее следуют несколько директив proxy_set_header
, призванные передать дополнительные данные о запросе на бэкенд. Первая директива устанавливает заголовок Host
для разделения серверов по доменам на стороне бэкенда. По умолчанию этот заголовок заполняется адресом, указанным в proxy_pass
(сервер или название upstream
).
Подключение к бэкенду будет производиться от Angie, поэтому IP реального клиента нужно передавать отдельно. Для этого передаётся заголовок X‑Forwarded‑For
со специальным значением из переменной $proxy_add_x_forwarded_for
, содержащей значение исходного заголовка запроса X‑Forwarded‑For
и переменной $remote_addr
через запятую. Некоторые бэкенды ориентируются на заголовок X‑Real‑IP
, поэтому мы передаём исходный IP клиента в этом заголовке.
Также нужно учитывать, что некоторые заголовки ответа бэкенда скрываются от клиента: Date, Server, X‑Pad и X‑Accel‑*
. Для их передачи можно указать директиву proxy_pass_header
с соответствующими названиями заголовков.
Заголовки клиентского запроса (или его тело) также можно полностью заблокировать от передачи на бэкенд с помощью директив proxy_pass_request_headers
и proxy_pass_request_body
соответственно.
Взаимодействие URI в location и proxy_pass
Для полного понимания работы директивы proxy_pass
нужно рассмотреть вопрос передачи URI запроса на бэкенд. Передаваемый URI будет зависеть от наличия URI в адресе сервера в proxy_pass
и, в некоторых случаях, от префикса в location
.
Самая простая конфигурация будет в случае, если proxy_pass включает только адрес сервера, без URI, например:
proxy_pass http://127.0.0.1:8000;
В этом варианте URI запроса будет передаваться без изменений на бэкенд.
У любых location
с proxy_pass
внутри (а также с любыми *_pass
) существует особенность: если префик в location
оканчивается на слеш, а запрос без слеша, то получим 301 редирект на URI со слешем в конце.
location /test/ {
proxy_pass http://127.0.0.1:8000;
}
Здесь запрос GET /test
будет перенаправлен с кодом 301
на /test/
. Чтобы избежать этого поведения можно будет создать отдельный location = /test
или отключить редирект с помощью директивы auto_redirect off
.
Более сложное поведение проявляется, если в proxy_pass
указать URI. Например:
location /test/ {
proxy_pass http://127.0.0.1:8000/real/;
}
Как минимум, стоит запомнить, что URI в location
и proxy_pass
взаимодействуют между собой и результат может быть неожиданным. Дальше можно всегда свериться с документацией.
Использование keep-alive соединений с бэкендом
Примеры проксирования в этой статье содержали локальные адреса бэкендов. Однако, часто инфраструктура веб‑приложения распределена по нескольким серверам и проксирование происходит на удалённую машину. То есть, между Angie и сервером будет реальная сеть, которая имеет ненулевые задержки (RTT).
Здесь можно вспомнить, что Angie по умолчанию открывает соединение с проксируемым сервером на каждый запрос, то есть поддержка keep‑alive в сторону бэкенда отключена. Такой режим работы не вызывает проблем при локальном проксировании, но в реальной сети мы должны учитывать накладные расходы на TCP‑рукопожатие (1 RTT), которые будут добавлены к каждому запросу.
Также можно упомянуть особенность работы TCP‑подключений, которые выходят на рабочую пропускную способность только через некоторое время передачи данных (алгоритмы congestion control). Чем больше сетевая задержка между Angie и бэкендом, тем сильнее эти факторы будут влиять на результирующее время ответа для клиента. Особенно важно использовать keep‑alive для проксирования по HTTPS, так как помимо TCP добавляется TLS‑handshake (1–2 RTT).
Включить поддержку keep‑alive со стороны Angie можно следующей конфигурацией, но конечно бэкенд также должен поддерживать keep‑alive. Здесь нужно будет ввести блок upstream (даже если мы используем один сервер) с директивой keepalive. Кстати, блок upstream можно использовать и для других задач, например можно ограничить количество подключений к бэкенду, подробнее в описании директивы server
. Время жизни соединения и количество запросов управляется директивами keepalive_timeout
и keepalive_requests
соответственно. В самом location
, где происходит проксирование, нужно установить заголовок Connection
и использовать протокол HTTP/1.1.
upstream backend {
server 127.0.0.1:8080;
keepalive 100;
keepalive_requests 1000;
keepalive_timeout 60s;
}
...
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://backend;
}
При передаче данных по сети может возникать требование безопасного подключения, чтобы исключить риск перехвата данных. В таком случае можно настроить проксирование по защищённому протоколу HTTPS (разберём этот вариант ниже). В этом примере мы начали работу с заголовками запроса при общении с бэкендом, но можно также влиять на заголовки ответа, поговорим об этом подробнее.
Работа с заголовками ответа и сookie
Ранее в конфигурации мы использовали директиву proxy_set_header, которая устанавливает заголовок запроса от Angie к бэкенду.
Если мы хотим установить заголовок ответа от Angie клиенту, то используем директиву add_header.
При использовании этих директив нужно помнить правило переопределения таких настроек на разных уровнях. Дело в том, что эти директивы разрешено указывать несколько раз в одном блоке и наследование для них работает следующим образом.
Если директива указана на более высоком уровне и не указана на вложенном, то значение наследуется от высокого уровня. Но есть директива указана и на верхнем, и на вложенном уровне, то директива на вложенном уровне не наследуется и используется как указано в блоке.
Посмотрим на следующий пример.
http {
add_header X-HTTP-TEST 1;
server {
listen 80;
location / {
return 200 "OK";
}
}
}
Здесь при проверке сервера получим наследование заголовка из контекста http
:
curl --head localhost | grep TEST
X-HTTP-TEST: 1
А теперь следующий пример:
http {
add_header X-HTTP-TEST 1;
server {
listen 80;
add_header X-SERVER-TEST 1;
location / {
return 200 "OK";
}
}
}
При тесте видим, что директива не суммируется с уровнем http
, а переопределяется на уровне server
:
curl --head localhost | grep TEST
X-SERVER-TEST: 1
В случае использования cookie, Angie позволяет вносить изменения в их свойства. Например, если бэкенд передаёт некорректный домен (domain=
localhost
), то его можно переписать с помощью директивой proxy_cookie_domain
:
proxy_cookie_domain localhost $host;
Директива позволяет использовать регулярные выражения и переменные. В cookie можно добавлять флаги (например, httponly
) через настройку proxy_cookie_flags
. Наконец, можно вносить изменения в атрибут path
через proxy_cookie_path
.
Обработка ошибок и перенаправлений бэкенда
К сожалению, не всегда бэкенд отвечает успешным кодом 200. В случае ошибок на стороне бэкенда он может ответить с помощью своей страницы ошибки, что может быть нежелательно с точки зрения безопасности и общего впечатления от сервиса. Для перехвата таких ошибок и обработки их средствами Angie (с помощью директивы error_page
) достаточно включить директиву proxy_intercept_errors
.
proxy_intercept_errors on;
Второй вариант особых ответов бэкенда: перенаправления. Иногда в значение заголовка Location
требуется внести изменения (поменять хост или путь). Такие изменения происходят по умолчанию на основе префикса в location
и URI в proxy_pass. Отвечает за управление такими изменениями директива proxy_redirect
, её поведение по умолчанию (значение default
) проще всего проиллюстрировать двумя конфигурациями, которые будут работать идентично. В первом случае используется значение по умолчанию, а во втором явным образом настроено изменение адреса перенаправления с http://upstream:port/two/ на http://upstream:port/one/.
location /one/ {
proxy_pass http://upstream:port/two/;
proxy_redirect default;
}
location /one/ {
proxy_pass http://upstream:port/two/;
proxy_redirect http://upstream:port/two/ /one/;
}
Если в строке замены нет адреса сервера и порта, то будут использоваться значения из proxy_pass
.
Проксирование по HTTPS
Использование протокола HTTPS для подключения между Angie и бэкенд-сервером позволяет повысить безопасность, но добавляет накладных расходов на процесс создания соединения и шифрование трафика. Как правило, необходимость HTTPS внутри инфраструктуры обусловлена отсутствием доверия к сети между Angie и бэкендом или регуляторными ограничениями. Задачи при настройке такие же, как при настройке HTTPS-соединений для клиентов: минимизировать количество полных TLS-рукопожатий, использовать эффективные алгоритмы шифрования. Есть и отличия от клиентских соединений: мы полностью контролируем ПО и можем не заботиться о совместимости. Также можем отказаться от валидации сертификатов (использовать самоподписанные), если не стоит задачи аутентифицировать сервер. Исходя из наших требований конфигурация может выглядеть следующим образом:
location / {
proxy_pass https://192.168.0.2;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_ssl_name $host;
proxy_ssl_server_name on;
proxy_ssl_verify off;
proxy_ssl_session_reuse on;
proxy_ssl_protocols TLSv1.3;
}
В приведённой выше конфигурации используются keep‑alive соединений и возобновление TLS‑сессий (proxy_ssl_session_reuse
), включена передача доменного имени (proxy_ssl_server_name
и proxy_ssl_name
), отключена проверка валидности сертификата (proxy_ssl_verify
). Протокол зафиксирован на TLS 1.3, так как мы полностью контролируем софт с обеих сторон подключения. Если ваш бэкенд имеет другие возможности, можно поменять настройки шифров и протокола под него.
Отдельно замечу, что Angie поддерживает протокол HTTP/3 для подключения к бэкенду. Для его использования достаточно выставить версию протокола:
proxy_http_version 3;
Замечу, что версии протоколов подключения клиентских и серверных подключений в Angie никак не связаны. На этом стоит перейти к общим настройкам проксирования: буферам и параметрам таймаутов.
Настройка буферизации и таймаутов
По умолчанию ответ от сервера считывается в буфер для максимально быстрого освобождения бэкенда и последующей отправке ответа клиенту. Таким образом оптимизируется нагрузка на бэкенд, он освобождается как можно быстрее. Параметры буферизации можно изменять, но самая частая настройка в этой части: proxy_buffers
. Она включает в себя два параметра — количество буферов и размер одного буфера. Если размер ответа будет превышать суммарный размер буферов, то Angie запишет часть ответа на диск, что может замедлить процесс ответа клиенту. Пример proxy_buffers
:
proxy_buffers 32 4k;
В некоторых ситуациях требуется отключить буферизацию ответа. Тогда Angie получит в буфер только небольшую часть ответа, а затем будет в синхронном режиме читать остаток ответа бэкенда. Это можно сделать с помощью директивы proxy_buffering
:
proxy_buffering off;
Если буферизация нужна, но требуется отключить запись на диск временных файлов, то необходимо установить директиву proxy_max_temp_file_size
.
proxy_max_temp_file_size 0;
При работе с проксируемым сервером Angie учитывает таймауты на различных стадиях работы с запросом (конкретные директивы рассмотрим ниже). По умолчанию значение таймаутов составляет 60 секунд, что позволяет в большинстве случаев получить рабочую конфигурацию без их настройки. Но в случае высоких нагрузок или использования балансировки нагрузки такие значения могут принести множество проблем.
Дело в том, что под нагрузкой время ответа бэкенда может возрастать и высокие таймауты будут создавать очередь запросов. При этом Angie будет ожидать ответа и клиент также не получит результата своего запроса. Даже если бэкенд вернётся к нормальной работе, на него будет направлен весь поток ожидающих запросов и приложение ещё какое-то время будет тормозить. В случае сокращения таймаутов Angie будет быстрее отдавать ошибку клиенту без накопления проблемных запросов и восстановление работы приложения будет происходить быстрее. В случае с балансировкой нагрузки переход к следующему серверу будет происходить быстрее. Конфигурация таймаутов задаётся директивами:
proxy_connect_timeout 10s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
Первая отвечает за ожидание подключения к серверу, вторая — за таймаут при чтении ответа от сервера (интервал между двумя чтениями), третья аналогична второй, но отвечает за передачу запроса серверу. При настройке read/send timeout
нужно учитывать особенности бэкенда, потому что не все серверы могут завершать обработку запроса при разрыве соединения со стороны Angie. Также для медленных приложений или конкретных location эти настройки можно повышать для корректной работы.
Для снижения нагрузки на бэкенд можно применить серверное кэширование, которое рассмотрено в отдельной статье.
Сохранение копий файлов
С помощью модуля проксирования можно сохранять (proxy_store
) локальные копии файлов, полученные от бэкендов. Это позволяет создавать копию неизменяемой статики на фронтенде.
Допустим, что исходные файлы для пути /js
находятся на удалённом бэкенде, но мы хотим иметь возможность отдавать их с локального диска. Настроить такое поведение можно так.
location /js/ {
root /var/www;
error_page 404 = @fetch;
}
location @fetch {
internal;
proxy_pass http://192.168.0.2;
proxy_store on;
proxy_store_access user:rw group:rw all:r;
proxy_temp_path /var/temp;
root /var/www;
}
Работа этой конфигурации основана на обработке 404 ошибки. То есть, если файл не найден в локальной директории, он отправляется на обработку во внутренний location @fetch
, который отправляет запросы на бэкенд (proxy_pass
) и одновременно сохраняет результат в локальную директорию с корнем /var/www
.
Итоги
В этой статье мы разобрали различные аспекты работы Angie в режиме обратного прокси, включая особенности настройки оптимального взаимодействия фронтенд‑сервера и бекенда. Если сюда добавить работу с балансировкой и кэшированием, то можно получить полную картину возможностей Angie по проксированию.