combined-upstreams: Модуль NGINX Combined Upstreams
Установка на Debian/Ubuntu
Эти документация относятся к пакету APT nginx-module-combined-upstreams, предоставляемому репозиторием GetPageSpeed Extras.
- Настройте APT репозиторий, как описано в настройке APT репозитория.
- Установите модуль:
sudo apt-get update
sudo apt-get install nginx-module-combined-upstreams
Показать дистрибутивы и архитектуры
| Дистрибутив | Версия | Компонент | Архитектуры |
|-------------|--------------------|-------------|----------------|
| debian | bookworm | main | amd64, arm64 |
| debian | bookworm-mainline | main | amd64, arm64 |
| debian | trixie | main | amd64, arm64 |
| debian | trixie-mainline | main | amd64, arm64 |
| ubuntu | focal | main | amd64, arm64 |
| ubuntu | focal-mainline | main | amd64, arm64 |
| ubuntu | jammy | main | amd64, arm64 |
| ubuntu | jammy-mainline | main | amd64, arm64 |
| ubuntu | noble | main | amd64, arm64 |
| ubuntu | noble-mainline | main | amd64, arm64 |
Модуль вводит три директивы add_upstream, combine_server_singlets и extend_single_peers, доступные в блоках конфигурации upstream, а также новый блок конфигурации upstrand для построения супер-слоев upstream. Дополнительно, вводится директива dynamic_upstrand для выбора upstrand'ов во время выполнения.
Директива add_upstream
Наполняет хост-апстрим серверами, перечисленными в уже определенном upstream, указанном обязательным 1-м параметром директивы. Атрибуты серверов, такие как веса, max_fails и другие, сохраняются в хост-апстриме. Дополнительные параметры могут включать значения backup, чтобы пометить все серверы источника upstream как резервные, и weight=N для калибровки весов серверов источника, умножая их на коэффициент N.
Пример
upstream combined {
add_upstream upstream1; # исходный upstream 1
add_upstream upstream2 weight=2; # исходный upstream 2
server some_another_server; # если нужно
add_upstream upstream3 backup; # исходный upstream 3
}
Директива combine_server_singlets
Создает множество singlet upstreams из серверов, уже определенных в хост-апстриме. Singlet upstream содержит только один активный сервер, тогда как другие сервера помечены как резервные или неработающие. Если параметры не переданы, тогда singlet upstream'ы будут иметь имена хост-апстрима с добавленным номером активного сервера в хост-апстриме. Дополнительные 2 параметра могут быть использованы для настройки их имен. 1-й параметр — это суффикс, добавленный после имени хост-апстрима и перед номером. 2-й параметр должен быть целым числом, которое определяет нуль-выравнивание номера. Например, если его значение равно 2, то номера будут
'01', '02', ..., '10', ... '100' ....
Чтобы пометить вторичные серверы как неработающие, а не резервные, используйте другой необязательный параметр nobackup. Этот параметр должен быть указан в конце, после всех других параметров.
Пример
upstream uhost {
server s1;
server s2;
server s3 backup;
server s4;
# построить singlet upstreams uhost_single_01,
# uhost_single_02, uhost_single_03 и uhost_single_04
combine_server_singlets _single_ 2;
server s5;
}
Почему номера, а не имена?
В приведенном выше примере, singlet upstream'ы будут иметь имена, такие как uhost_single_01, но имена, содержащие имена серверов, такие как uhost_single_s1, выглядели бы лучше и удобнее. Почему бы не использовать их вместо порядковых номеров? К сожалению, Nginx не запоминает имена серверов после добавления сервера в upstream, поэтому мы не можем просто извлечь их.
Обновление. Есть хорошие новости! Начиная с версии 1.7.2, Nginx запоминает имена серверов в данных upstream, и теперь мы можем использовать их при обращении к специальному ключевому слову byname. Например,
combine_server_singlets byname;
# или
combine_server_singlets _single_ byname;
Все двоеточия (:) в именах серверов заменяются на подчеркивания (_).
Где это может быть полезно
Singlet upstream действует как один сервер в режиме резервирования. Это может быть использовано для управления липкими HTTP-сессиями, когда серверы на стороне сервера идентифицируют себя с помощью надлежащего механизма, такого как HTTP cookies.
upstream uhost {
server s1;
server s2;
combine_server_singlets;
}
server {
listen 8010;
server_name main;
location / {
proxy_pass http://uhost$cookie_rt;
}
}
server {
listen 8020;
server_name server1;
location / {
add_header Set-Cookie "rt=1";
echo "Передано на $server_name";
}
}
server {
listen 8030;
server_name server2;
location / {
add_header Set-Cookie "rt=2";
echo "Передано на $server_name";
}
}
В этой конфигурации первый запрос клиента выберет сервер на стороне сервера случайным образом, выбранный сервер установит cookie rt на предопределенное значение (1 или 2), и все дальнейшие запросы от этого клиента будут автоматически перенаправляться на выбранный сервер, пока он не выйдет из строя. Скажем, это был server1, тогда, когда он выходит из строя, cookie rt на стороне клиента останется 1. Директива proxy_pass перенаправит следующий запрос клиента на singlet upstream uhost1, где server1 объявлен активным, а server2 резервным. Как только server1 становится недоступным, Nginx перенаправит запрос на server2, который перепишет cookie rt, и все дальнейшие запросы клиента будут перенаправлены на server2, пока он не выйдет из строя.
Директива extend_single_peers
Соперники в upstream'ах выходят из строя в соответствии с правилами, указанными в директиве proxy_next_upstream. Если в upstream есть только один соперник в его основном или резервном части, то этот соперник никогда не выйдет из строя. Это может быть серьезной проблемой при написании пользовательского алгоритма для активных проверок работоспособности соперников upstream. Директива extend_single_peers, объявленная в блоке upstream, добавляет виртуального соперника, помеченного как down в основном или резервном части upstream, если часть изначально содержит только одного соперника. Это заставляет Nginx помечать оригинальный единственный соперник как неработающий, когда он не проходит правила директивы proxy_next_upstream, как это происходит в общем случае с несколькими соперниками.
Пример
upstream upstream1 {
server s1;
extend_single_peers;
}
upstream upstream2 {
server s1;
server s2;
server s3 backup;
extend_single_peers;
}
Обратите внимание, что если часть (основная или резервная) upstream содержит более одного соперника (например, основная часть в upstream2 из примера), то директива не имеет эффекта: в частности, в upstream2 она затрагивает только резервную часть upstream.
Блок upstrand
Предназначен для настройки супер-слоя upstream'ов, которые не теряют свои идентичности. Принимает ряд директив, включая upstream, order, next_upstream_statuses и другие. Upstream'ы с именами, начинающимися с тильды (~), соответствуют регулярному выражению. Только upstream'ы, которые уже были объявлены до определения блока upstrand, считаются кандидатами.
Пример
upstrand us1 {
upstream ~^u0 blacklist_interval=60s;
upstream b01 backup;
order start_random;
next_upstream_statuses error timeout non_idempotent 204 5xx;
next_upstream_timeout 60s;
intercept_statuses 5xx /Internal/failover;
}
Upstrand us1 будет комбинировать все upstream'ы, имена которых начинаются с u0, и upstream b01 как резервный. Резервные upstream'ы проверяются, если все нормальные upstream'ы неудачны. Неудача означает, что все upstream'ы в нормальных или резервных циклах ответили статусами, перечисленными в директиве next_upstream_statuses, или были в черном списке. Здесь ответ upstream означает статус, возвращаемый последним сервером upstream, что сильно зависит от значения директивы proxy_next_upstream. Upstream помечается как находящийся в черном списке, когда он имеет параметр blacklist_interval и отвечает статусом, перечисленным в next_upstream_statuses. Состояние черного списка не разделяется между процессами рабочего процесса Nginx.
Следующие четыре директивы upstrand аналогичны тем, что в модуле прокси Nginx.
Директива next_upstream_statuses принимает обозначения статусов 4xx и 5xx и значения error и timeout, чтобы различать случаи, когда ошибки происходят с соединениями соперников upstream от случаев, когда бэкэнды отправляют статусы 502 или 504 (прямые значения 502 и 504, а также 5xx относятся к обоим случаям). Она также принимает значение non_idempotent, чтобы позволить дальнейшую обработку неидемпотентных запросов, когда они были ответом последнего сервера из upstream, но не прошли по другим статусам, перечисленным в директиве. Запросы считаются неидемпотентными, когда их методы POST, LOCK или PATCH, как это происходит в директиве proxy_next_upstream.
Директива next_upstream_timeout ограничивает общее время работы циклов upstrand через все его upstream'ы. Если время истекает, пока upstrand готов перейти к следующему upstream, возвращается результат последнего цикла upstream.
Директива intercept_statuses позволяет осуществлять переключение upstrand, перехватывая окончательный ответ в местоположении, которое соответствует заданному URI. Перехваты должны происходить даже когда upstrand истекает. Обратите также внимание, что обход upstream'ов в upstrand и URI переключения upstrand не являются перехватываемыми. Более общим образом, любое внутреннее перенаправление (с помощью error_page, proxy_intercept_errors, X-Accel-Redirect и т. д.) нарушит вложенные подзапросы, на которых основана реализация upstrand, что приведет к возврату пустых ответов. Это крайне плохие случаи, и именно поэтому обход upstream'ов был защищён от перехватов. URI переключения upstrand больше подвержен этому, так как реализация имеет меньше контроля над его местоположением. В частности, переключение upstrand имеет только защиту от перехватов с помощью error_page и proxy_intercept_errors. Это означает, что местоположение URI переключения upstrand должно быть как можно более простым (например, используя простые директивы, такие как return или echo).
Тем не менее, существует хорошее решение проблемы с местоположениями переключения upstrand и внутренними перенаправлениями в них. Как именно внутренние перенаправления разрушают подзапросы? Ну, они стирают контексты подзапросов, нужные в фильтрах ответов модуля. Таким образом, если мы сможем сделать контекст подзапроса постоянным, решим ли мы проблему? Ответ положительный! Модуль Nginx nginx-easy-context позволяет создавать постоянные контексты запросов. Upstrand'ы могут воспользоваться ими, включив переключатель в файле config и собрав оба модуля. См. детали в разделе Сборка и тестирование.
Директива order в настоящее время принимает только одно значение start_random, что означает, что начальные upstream'ы в нормальных и резервных циклах после запуска рабочего процесса будут выбраны случайным образом. Начальные upstream'ы в следующих запросах будут перекрыты в круговом порядке. Дополнительно, модификатор per_request также принимается в директиве order: он отключает глобальный круговой цикл для каждого рабочего процесса. Сочетание per_request и start_random делает так, что начальный upstream в каждом новом запросе выбирается случайным образом.
Такое переключение между статусами неудачи может быть достигнуто во время единственного запроса, подав специальную переменную, начинающуюся с upstrand_, в директиву proxy_pass вот так:
location /us1 {
proxy_pass http://$upstrand_us1;
}
Будьте осторожны при доступе к этой переменной из других директив! Это запускает механизм подзапросов, что может быть нежелательным во многих случаях.
Переменные статуса upstrand
Существует ряд доступных переменных статуса upstrand: upstrand_addr, upstrand_cache_status, upstrand_connect_time, upstrand_header_time, upstrand_response_length, upstrand_response_time и upstrand_status. Они являются аналогами соответствующих переменных upstream и содержат значения последних для всех upstream'ов, проходящих через запрос, и всех подзапросов хронологически. Переменная upstrand_path содержит путь всех upstream'ов, посещённых во время запроса.
Где это может быть полезно
upstrand выглядит очень похоже на простой комбинированный upstream, но у него есть важное отличие: upstream'ы внутри upstrand не расплющиваются и продолжают сохранять свои идентичности. Это дает возможность настроить статус резервирования для группы серверов, связанных с одним upstream, без необходимости проверять их всех по очереди. В приведенном выше примере upstrand us1 может содержать список upstream'ов, таких как u01, u02 и т. д. Представьте, что upstream u01 содержит 10 серверов внутри и представляет собой часть географически распределенной системы бэкэнду. Пусть upstrand us1 комбинирует все такие части в одно целое, и давайте запустим клиентское приложение, которое опрашивает части для выполнения каких-либо задач. Пусть бэкэнды отправляют HTTP статус 204, если у них нет новых задач. В плоском комбинированном upstream все 10 серверов могут быть опрошены, прежде чем приложение наконец получит новую задачу от другого upstream. Upstrand us1 позволяет перейти к следующему upstream после проверки первого сервера в upstream, у которого нет задач. Эта механика, безусловно, подходит для вещания upstream, когда сообщения отправляются всем upstream'ам в upstrand.
Примеры выше показывают, что upstrand можно рассматривать как двумерный upstream, состоящий из множества кластеров, представляющих натуральные upstream'ы и позволяющих быстро циклически их.
Чтобы проиллюстрировать это, давайте эмулируем upstream без кругового балансирования. Каждый новый клиентский запрос начнется с проксирования к первому серверу в списке upstream и затем переключится на следующий сервер.
upstream u1 {
server localhost:8020;
server localhost:8030;
combine_server_singlets _single_ nobackup;
}
upstrand us1 {
upstream ~^u1_single_ blacklist_interval=60s;
order per_request;
next_upstream_statuses error timeout non_idempotent 5xx;
intercept_statuses 5xx /Internal/failover;
}
Директива combine_server_singlets в upstream u1 генерирует два singlet upstreams u1_single_1 и u1_single_2, чтобы населить upstrand us1. Благодаря заказу per_request внутри upstrand, два upstream'а будут проходить в порядке u1_single_1 → u1_single_2 в каждом клиентском запросе.
Директива dynamic_upstrand
Позволяет выбирать upstrand из переданных переменных во время выполнения. Директива может быть установлена в сервере, location и clauses location-if.
В следующей конфигурации
upstrand us1 {
upstream ~^u0;
upstream b01 backup;
order start_random;
next_upstream_statuses 5xx;
}
upstrand us2 {
upstream ~^u0;
upstream b02 backup;
order start_random;
next_upstream_statuses 5xx;
}
server {
listen 8010;
server_name main;
dynamic_upstrand $dus1 $arg_a us2;
location / {
dynamic_upstrand $dus2 $arg_b;
if ($arg_b) {
proxy_pass http://$dus2;
break;
}
proxy_pass http://$dus1;
}
}
upstrand'ы, возвращенные в переменных dus1 и dus2, должны быть выбраны из значений переменных arg_a и arg_b. Если arg_b задан, то клиентский запрос будет отправлен на upstrand с именем, равным значению arg_b. Если такого upstrand с этим именем нет, то dus2 останется пустым, и proxy_pass вернет HTTP статус 500. Чтобы избежать инициализации динамической переменной upstrand с пустым значением, ее объявление должно быть завершено литеральным именем, соответствующим существующему upstrand. В этом примере динамическая переменная upstrand dus1 будет инициализирована upstrand us2, если arg_a пустой или не задан. В целом, если arg_b не задан или пуст, а arg_a задан и имеет значение, равное существующему upstrand, то запрос будет отправлен на этот upstrand, иначе (если arg_b не задан или пуст и arg_a задан, но не ссылается на существующий upstrand) proxy_pass скорее всего вернет HTTP статус 500 (за исключением случая, когда существует переменная, составленная из литеральной строки upstrand_ и значения arg_a, указывающая на допустимый адрес), в противном случае (если и arg_b, и arg_a не заданы или пусты) запрос будет отправлен на upstrand us2.
Смотрите также
Существует несколько статей о модуле в моем блоге, в хронологическом порядке:
- Простой модуль nginx для создания комбинированных апстримов (на русском). Полная статья, освещающая детали реализации директивы add_upstream, которую также можно считать небольшим учебником по разработке модулей Nginx.
- nginx upstrand для конфигурации супер-слоев upstreams. Обзор использования блока upstrand и некоторые детали его реализации.
- Не такой уж простой модуль nginx для создания комбинированных апстримов (на русском). Обзор всех функций модуля с примерами конфигурации и образцами сессий тестирования.