Используемые термины: NGINX , http , https , веб-сервер .
В данной инструкции мы рассмотрим процесс настройки балансировки, в основном, http-запросов с помощью веб-сервера NGINX. По большей части, инструкция подойдет для любого дистрибутива Linux, и даже, Windows (за исключением путей расположения конфигурационных файлов). Таким образом настроенный NGINX сможет обеспечить распределение нагрузки и отказоустойчивость нашим сетевым сервисам.
Обратите внимание, что NGINX умеет распределять не только http-запросы. Его можно использовать для балансировки запросов на 4-м уровне модели OSI (TCP и UDP), например, подключения к СУБД, DNS и так далее — по сути, любой сетевой запрос может быть обработан и распределен с помощью данного программного продукта.
Постепенно рассмотрим разные варианты настройки распределения нагрузки в NGINX. Начнем с простого понимания, как работает данная функция и закончим некоторыми примерами настройки балансировки.
Базовая настройка
Распределение нагрузки по весам
Время ожидания
Способы балансировки
Round Robin
Hash
IP Hash
Least Connections
Random
Least Time
Запросы Stream
Балансировка по браузерам
Примеры настроек
Backend на https
Разные бэкенды для разных страниц
На другой хост
Распределение по значению cookie
TCP-запрос (PostgreSQL)
UDP-запрос
Доступ по SSH
Решение возможных проблем
Читайте также
Чтобы наш сервер мог распределять нагрузку, создадим группу веб-серверов, на которые будут переводиться запросы:
vi /etc/nginx/conf.d/upstreams.conf
* в данном примере мы создаем файл upstreams.conf , в котором можем хранить все наши апстримы. NGINX автоматически читает все конфигурационные файлы в каталоге conf.d .
Добавим:
upstream remontka_backend {
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
* предполагается, что во внутренней сети есть кластер из трех веб-серверов — 192.168.10.10 , 192.168.10.11 и 192.168.10.12 . Мы создали апстрим с названием remontka_backend . Позже, мы настроим веб-сервер, чтобы он умел обращаться к данному бэкенду.
В настройках сайта (виртуального домена) нам необходимо теперь проксировать запросы на созданный upstream. Данная настройка будет такой:
server {
…
location / {
proxy_pass http://remontka_backend;
}
…
}
* в данном примере все запросы должны переводиться на апстрим remontka_backend (который, в нашем случае, будет отправлять запросы на три сервера).
Проверяем корректность нашего конфигурационного файла и перечитываем конфигурацию:
nginx -t && nginx -s reload
При настройке бэкендов мы можем указать, кому наш веб-сервер будет отдавать больше предпочтение, а кому меньше.
Синтаксис при указании веса:
server <имя сервера> weight=<числовой эквивалент веса>;
По умолчанию приоритет равен 1.
Также мы можем указать опции:
Давайте немного преобразуем нашу настройку upstreams:
vi /etc/nginx/conf.d/upstreams.conf
upstream remontka_backend {
server 192.168.10.10 weight=100 ;
server 192.168.10.11 weight=10 ;
server 192.168.10.12;
server 192.168.10.13 backup ;
}
* итак, мы указали нашему серверу:
По умолчанию, NGINX будет считать сервер недоступным после 1-й неудачной попытки отправить на него запрос. После в течение 10 секунд не будут продолжаться попытки работы с ним. Каждый сервер не имеет ограничений по количеству подключений к нему.
Изменить поведение лимитов и ограничений при балансировке можно с помощью опций:
Синтаксис:
server <имя сервера> max_fails=<число попыток> fail_timeout=<числовой показатель времени><еденица времени>;
В нашем примере мы преобразуем настройку так:
vi /etc/nginx/conf.d/upstreams.conf
upstream remontka_backend {
server 192.168.10.10 weight=100 max_conns=1000 ;
server 192.168.10.11 weight=10 max_fails=2 fail_timeout=90s ;
server 192.168.10.12 max_fails=3 fail_timeout=2m ;
server 192.168.10.13 backup;
}
* в итоге:
Рассмотрим способы балансировки, которые можно использовать в NGINX:
Настройка метода балансировки выполняется в директиве upstream. Синтаксис:
upstream <название апстрима> {
<метод балансировки>
…
}
Веб-сервер будет передавать запросы бэкендам по очереди с учетом их весов. Данный метод является методом по умолчанию и его указывать в конфигурационном файле не нужно.
Данный метод определяет контрольную сумму на основе произвольного текста и/или переменных и ассоциирует каждый полученный результат с конкретным бэкендом. Пример настройки:
upstream remontka_backend {
hash $scheme$request_uri;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
* это самый распространенный пример настройки hash — с использованием переменных $scheme (http или https) и $request_uri . При данной настройке каждый конкретный URL будет ассоциирован с конкретным сервером.
Ассоциация выполняется исходя из IP-адреса клиента и только для HTTP-запросов. Таким образом, для каждого посетителя устанавливается связь с одним и тем же сервером. Это, так называемый, Sticky Session метод.
Для адресов IPv4 учитываются только первые 3 октета — это позволяет поддерживать одинаковые соединения с клиентами, чьи адреса меняются (получение динамических адресов от DHCP провайдера). Для адресов IPv6 учитывается адрес целиком.
Пример настройки:
upstream remontka_backend {
ip_hash;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
NGINX определяет, с каким бэкендом меньше всего соединений в данный момент и перенаправляет запрос на него (с учетом весов).
Настройка выполняется с помощью опции least_conn:
upstream remontka_backend {
least_conn;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
Запросы передаются случайным образом (с учетом весов). Дополнительно можно указать опцию two — если она задана, то NGINX сначала выберет 2 сервера случайным образом, затем на основе дополнительных параметров отдаст предпочтение одному из них. Это следующие параметры:
Пример настройки:
upstream remontka_backend {
random two least_conn;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
Данная опция будет работать только в платной версии NGINX Plus. Балансировка выполняется исходя из времени ответа сервера. Предпочтение отдается тому, кто отвечает быстрее.
Опция для указания данного метода — least_time. Также необходимо указать, что мы считаем ответом — получение заголовка (header) или когда страница возвращается целиком (last_byte).
Пример 1:
upstream remontka_backend {
least_time header;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
* в данном примере мы будем делать расчет исходя из того, как быстро мы получаем в ответ заголовки.
Пример 2:
upstream remontka_backend {
least_time last_byte;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
* в данном примере мы будем делать расчет исходя из того, как быстро мы получаем в ответ целую страницу.
Запросы не http, например, запрос к базе данных должен работать как stream. Для этого дополнительно устанавливаем одноименный модуль.
В зависимости от операционной системы команды будут разные.
а) Для систем на базе Deb (Debian / Ubuntu):
apt update
apt install libnginx-mod-stream
Для некоторых версий дистрибутивов deb нужно устанавливать другой пакет:
apt install nginx-mod-stream
б) Для систем на базе RPM (Rocky / РЕД ОС):
yum install nginx-mod-stream
Установка выполнена.
Для применения изменений перезапускаем веб-сервер:
systemctl restart nginx
Один из самых удобных способов балансировки нагрузки — использовать уникальные идентификаторы для каждого браузера. В результате, запросы будут распределяться по посетителям, но каждый посетитель будет попадать на один и тот же сервер.
Данная задача реализуется с помощью метода sticky, но он доступен только в платной версии nginx. Однако, есть альтернативный модуль nginx-sticky-module-ng, который не стоит денег и позволяет реализовать такую балансировку. Для того, чтобы модуль работал с веб-сервером, необходимо пересобрать nginx. Рассмотрим данный процесс, а также настройку sticky-балансировки по шагам.
Если nginx еще нет в системе, устанавливаем его.
а) На Linux DEB (Debian, Ubuntu, Astra Linux):
apt update
apt install nginx
б) На Linux RPM (Rocky Linux, РЕД ОС):
yum install nginx
NGINX установлен.
Мы могли и не устанавливать nginx, а сразу собрать его с необходимым модулем. Однако, при установке приложения из пакета выполняются дополнительные настройки, которые делают работу с ним немного удобнее.
Теперь установим пакеты, когорые нам понадобятся при сборке. Для разных систем будет немного разный набор данных пакетов.
а) На Linux DEB (Debian, Ubuntu, Astra Linux):
apt install wget git make gcc libpcre3-dev libssl-dev libzip-dev libxslt-dev libgd-dev
б) На Linux RPM (Rocky Linux, РЕД ОС):
yum install wget git make gcc pcre-devel openssl-devel libxslt-devel gd-devel perl-ExtUtils-Embed geoip-devel
* обратите внимание, что данный набор установленных компонентов может быть неполным для вашей системы. Все зависит от используемых возможностей nginx. Так или иначе, если какого-то компонента не хватит, мы получим ошибку при сбоке и необходимо будет по ее тексту разобраться. какой пакет нужно доустановить.
Первый шаг выполнен.
Переходим в каталог для хранения исходников:
cd /usr/local/src/
Копируем в него исходные файлы nginx-sticky-module-ng:
git clone https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng.git
* все исходники модуля можно найти на bitbucket.org .
Посмотрим, какой версии у нас установлен nginx:
nginx -v
В моем случае было:
nginx version: nginx/1.19.3
Значит я буду искать исходник для nginx версии 1.19.3.
Идем на страницу загрузки nginx и копируем ссылку на установленную версию nginx (архив tar.gz):
Используя данную ссылку, загружаем исходник на сервер:
wget https://nginx.org/download/nginx-1.19.3.tar.gz
Распаковываем ранее скачанный архив и переходим в него:
tar -zxf nginx-*.tar.gz
cd nginx-1.19.3/
Смотрим, с какими опциями собран nginx, установленный в системе:
nginx -V
В моем примере было так:
nginx version: nginx/1.19.3
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: —prefix=/etc/nginx —sbin-path=/usr/sbin/nginx —modules-path=/usr/lib64/nginx/modules —conf-path=/etc/nginx/nginx.conf —error-log-path=/var/log/nginx/error.log —http-log-path=/var/log/nginx/access.log —pid-path=/var/run/nginx.pid —lock-path=/var/run/nginx.lock —http-client-body-temp-path=/var/cache/nginx/client_temp —http-proxy-temp-path=/var/cache/nginx/proxy_temp —http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp —http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp —http-scgi-temp-path=/var/cache/nginx/scgi_temp —user=nginx —group=nginx —with-http_ssl_module —with-http_realip_module —with-http_addition_module —with-http_sub_module —with-http_dav_module —with-http_flv_module —with-http_mp4_module —with-http_gunzip_module —with-http_gzip_static_module —with-http_random_index_module —with-http_secure_link_module —with-http_stub_status_module —with-http_auth_request_module —with-http_xslt_module=dynamic —with-http_image_filter_module=dynamic —with-http_geoip_module=dynamic —with-http_perl_module=dynamic —with-threads —with-stream —with-stream_ssl_module —with-http_slice_module —with-mail —with-mail_ssl_module —with-file-aio —with-ipv6 —with-http_v2_module —with-cc-opt=’-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong —param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic’
Копируем данные аргументы и конфигурируем наш исходник nginx с добавлением опции —add-module :
./configure —prefix=/etc/nginx —sbin-path=/usr/sbin/nginx —modules-path=/usr/lib64/nginx/modules —conf-path=/etc/nginx/nginx.conf —error-log-path=/var/log/nginx/error.log —http-log-path=/var/log/nginx/access.log —pid-path=/var/run/nginx.pid —lock-path=/var/run/nginx.lock —http-client-body-temp-path=/var/cache/nginx/client_temp —http-proxy-temp-path=/var/cache/nginx/proxy_temp —http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp —http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp —http-scgi-temp-path=/var/cache/nginx/scgi_temp —user=nginx —group=nginx —with-http_ssl_module —with-http_realip_module —with-http_addition_module —with-http_sub_module —with-http_dav_module —with-http_flv_module —with-http_mp4_module —with-http_gunzip_module —with-http_gzip_static_module —with-http_random_index_module —with-http_secure_link_module —with-http_stub_status_module —with-http_auth_request_module —with-http_xslt_module=dynamic —with-http_image_filter_module=dynamic —with-http_geoip_module=dynamic —with-http_perl_module=dynamic —with-threads —with-stream —with-stream_ssl_module —with-http_slice_module —with-mail —with-mail_ssl_module —with-file-aio —with-ipv6 —with-http_v2_module —with-cc-opt=’-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong —param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic’ —add-module=/usr/local/src/nginx-sticky-module-ng
После делаем сборку:
make
И установку:
make install
Проверить, что nginx теперь используем новую опцию сборки можно командой:
nginx -V
В Ubuntu я столкнулся с проблемой, что после пересборки nginx, он по прежнему запускал бинарник, скомпилированный без нужной нам опции. Как оказалось, в данной системе nginx собирается с немного другой опцией sbin-path , которая по умолчанию ведет в каталог /usr/share/nginx/sbin . Чтобы решить проблему, добавляем при сбоке опцию sbin-path :
./configure … —sbin-path=/usr/sbin/nginx
После можно по новой собрать и установить nginx. Все должно работать.
Наша система готова к настройке балансировки по методу sticky.
Настройка апстрима сводится к указанию опции sticky :
upstream sticky_backend {
sticky;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}
Готово.
В реальной жизни настройки могут быть несколько сложнее, чем приведенные здесь или в официальной документации. Рассмотрим несколько примеров, что может понадобиться настроить при балансировке.
После выполнения настроек не забываем проверить конфигурации и перечитать ее для применения изменений:
nginx -t && nginx -s reload
Предположим, что наши внутренние серверы отвечают по SSL-каналу. Таким образом, нам нужно отправлять запрос по порту 443. Также схема проксирования должна быть https.
Настройка сайта:
server {
…
location / {
proxy_pass https://remontka_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
…
}
* обратите внимание на 2 момента:
Настройка upstream:
upstream remontka_backend {
server 192.168.10.10:443;
server 192.168.10.11:443;
server 192.168.10.12:443;
}
* в данном примере мы указали конкретный порт, по которому должно выполняться соединение с бэкендом. Для упрощения конфига дополнительные опции упущены.
Нам может понадобиться разные страницы сайта переводить на разные группы внутренних серверов.
Настройка сайта:
server {
…
location /page1 {
proxy_pass http://backend1;
}
location /page2 {
proxy_pass http://backend2;
}
…
}
* при такой настройке мы будем передавать запросы к странице page1 на группу backend1 , а к page2 — backend2 .
Настройка upstream:
upstream backend1 {
server 192.168.10.10;
server 192.168.10.11;
}
upstream backend2 {
server 192.168.10.12;
server 192.168.10.13;
}
* в данном примере у нас есть 2 апстрима, каждый со своим набором серверов.
Может быть необходимым делать обращение к внутреннему ресурсу по другому hostname, нежели чем будет обращение к внешнему. Для этого в заголовках проксирования мы должны указать опцию Host.
Настройка сайта:
server {
…
location / {
proxy_pass https://remontka_backend;
proxy_set_header Host internal.domain.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
…
}
* в данном примере мы будем проксировать запросы на бэкенды, передавая им имя хоста internal.domain.com .
Мы можем задать куки для определенной сессии клиента и гарантировать, что он будет подключаться к одному и тому же серверу. Данного поведения можно добиться с помощью метода балансировки hash.
Настройка upstream:
upstream backend_hash {
hash $cookie_session_id consistent;
server 192.168.10.10;
server 192.168.10.11;
}
* в данном примере nginx будет делать отбор запросов по значению для куки session_id (то, что идет после $cookie_ ). Таким образом, разработчик может каждому посетителю выставлять свой уникальный cookie с ключом session_id , гарантируя, что данный посетитель будет перенаправляться на один и тот же сервер (начиная со второго запроса).
Стоит сделать важное отступление при работе с cookie в веб-сервере nginx. Переменная $cookie_<имя вашей куки> умеет работать только с обычными символами и нижним подчеркиванием. Любой спецсимвол, например дефис или точка не будет восприниматься nginx. В качестве обходного решения может использоваться такая конфигурация:
map $http_cookie $upstream_cookie {
default «»;
«~*cookie-name-with-dash=(.*?)($|;.*)» «$1»;
}
upstream backend_hash {
hash $upstream_cookie consistent;
server 192.168.10.10;
server 192.168.10.11;
}
* map читаем так — проверить содержимое переменной $http_cookie (она содержит список всех куки с их значениями), если в ней встретиться определенная кука (нам интересна cookie-name-with-dash ), то ее содержимое записать в переменную $upstream_cookie . После в апстриме backend_hash мы будем использовать значение данной меременной.
Рассмотрим, в качестве исключения, TCP-запрос на порт 5432 — подключение к базе PostgreSQL. Данная настройка выполняется на уровне stream:
vi /etc/nginx/nginx.conf
…
http {
…
}
stream {
upstream postgres {
server 192.168.10.14:5432;
server 192.168.10.15:5432;
}
server {
listen 5432 so_keepalive=on;
proxy_pass postgres;
}
}
* в данном примере мы слушаем TCP-порт 5432 и проксируем все запросы на апстрим postgres . Запросы будут случайным образом передаваться на серверы 192.168.10.14 и 192.168.10.15 .
Рассмотрим также и возможность балансировки UDP-запросов — подключение к DNS по порту 53.
Настройка сайта:
server {
listen 53 udp;
proxy_pass udp_dns;
proxy_responses 1;
}
* в данном примере мы слушаем UDP-порт 53 и проксируем все запросы на апстрим udp_dns . Опция proxy_responses говорит о том, что на один запрос нужно давать один ответ.
Настройка upstream:
upstream udp_dns {
server 192.168.10.16:53;
server 192.168.10.17:53;
}
* запросы будут случайным образом передаваться на серверы 192.168.10.16 и 192.168.10.17 .
С помощью stream запросов мы можем проксировать подключения по SSH:
stream {
upstream ssh {
server 1.2.3.4:22;
}
server {
listen 2222;
proxy_pass ssh;
}
}
* при обращении к серверу на порт 2222 нас перекинет на 22 порт сервера 1.2.3.4 .
** обратите внимание, что в данном примере мы конфигурацию описали без разделения на server и upstream .
Рассмотрим некоторые ошибки, с которыми мы можем столкнуться, настраивая проксирование в nginx.
Ошибка появляется при настройке проксирования с помощью stream (не http-запросов). Данную ошибку мы можем увидеть при попытке выполнить проверку конфигурации:
nginx -t
Причина: nginx собран без поддержки stream.
Решение: проверить поддержку stream можно командой:
nginx -V
Мы должны увидеть:
… —with-stream …
Если данной записи нет, то можно попробовать установить динамический модуль, как описано выше , либо пересобрать nginx с добавлением данной опции.
Возможно, данные инструкции также будут полезны:
1. Примеры редиректов в NGINX .
Zulip — программное обеспечение для реализации корпоративного чата. Разработан в 2012 году, в 2014 был…
Zookeeper — cервис-координатор, который позволяет обеспечить контроль синхронизации данных. Разработан на Java компанией Apache Software…
Zimbra — программное обеспечение для реализации почтового сервиса или, если сказать точнее, автоматизации совместной деятельности…
Zabbix — бесплатная система мониторинга. Позволяет отслеживать состояние сетевых узлов, компьютеров и серверов. Возможности: Поддержка…
YouTube — компания-владелец одноименного портала для просмотра и хранения видео. Чтобы пользоваться данным порталом достаточно…
Yota — провайдер, предоставляющий доступ к сети Интернет по беспроводной связи. Впервые, сервис начал работать…