Categories: Шпаргалки

Балансировка нагрузки с помощью NGINX

Используемые термины: NGINX , http , https , веб-сервер .

В данной инструкции мы рассмотрим процесс настройки балансировки, в основном, http-запросов с помощью веб-сервера NGINX. По большей части, инструкция подойдет для любого дистрибутива Linux, и даже, Windows (за исключением путей расположения конфигурационных файлов). Таким образом настроенный NGINX сможет обеспечить распределение нагрузки и отказоустойчивость нашим сетевым сервисам.

Обратите внимание, что NGINX умеет распределять не только http-запросы. Его можно использовать для балансировки запросов на 4-м уровне модели OSI (TCP и UDP), например, подключения к СУБД, DNS и так далее — по сути, любой сетевой запрос может быть обработан и распределен с помощью данного программного продукта.

Постепенно рассмотрим разные варианты настройки распределения нагрузки в NGINX. Начнем с простого понимания, как работает данная функция и закончим некоторыми примерами настройки балансировки.

Основы

Чтобы наш сервер мог распределять нагрузку, создадим группу веб-серверов, на которые будут переводиться запросы:

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.

Также мы можем указать опции:

  • backup , которая будет говорить о наличие резервного сервера, к которому будет выполняться подключение только при отсутствии связи с остальными.
  • down , при указании которой, сервер будет считаться постоянно недоступным. Может оказаться полезной, чтобы остановить временно запросы для проведения обслуживания.

Давайте немного преобразуем нашу настройку 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 ;
}

* итак, мы указали нашему серверу:

  • переводить на сервер 192.168.10.10 в 10 раз больше запросов, чем на 192.168.10.11 и в 100 раз больше — чем на 192.168.10.12 .
  • переводить на сервер 192.168.10.11 в 10 раз больше запросов, чем на 192.168.10.12 .
  • на сервер 192.168.10.13 запросы переводятся, только если не доступны все три сервера, описанные выше.

Задержки, лимиты и таймауты

По умолчанию, NGINX будет считать сервер недоступным после 1-й неудачной попытки отправить на него запрос. После в течение 10 секунд не будут продолжаться попытки работы с ним. Каждый сервер не имеет ограничений по количеству подключений к нему.

Изменить поведение лимитов и ограничений при балансировке можно с помощью опций:

  • max_fails — количество неудачных попыток, после которых будем считать сервер недоступным.
  • fail_timeout — время, в течение которого сервер нужно считать недоступным и не отправлять на него запросы.
  • max_conns — максимальное число подключений, при превышении которого запросы на бэкенд не будут поступать. По умолчанию равно 0 (безлимитно).

Синтаксис:

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

* в итоге:

  • сервер 192.168.10.10 будет принимать на себя, максимум, 1000 запросов.
  • сервер 192.168.10.10 будет иметь настройки по умолчанию.
  • если на сервер 192.168.10.11 будет отправлено 2-е неудачные попытки отправки запроса, то в течение 90 секунд на него не будут отправлять новые запросы.
  • сервер 192.168.10.12 будет недоступен в течение 2-х минут, если на него будут отправлены 3 неудачных запроса.

Метод балансировки

Рассмотрим способы балансировки, которые можно использовать в NGINX:

  1. Round Robin.
  2. Hash.
  3. IP Hash.
  4. Least Connections.
  5. Random.
  6. Least Time (только в платной версии NGINX).

Настройка метода балансировки выполняется в директиве upstream. Синтаксис:

upstream <название апстрима> {
<метод балансировки>

}

Round Robin

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

Hash

Данный метод определяет контрольную сумму на основе произвольного текста и/или переменных и ассоциирует каждый полученный результат с конкретным бэкендом. Пример настройки:

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 Hash

Ассоциация выполняется исходя из 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;
}

Least Connections

NGINX определяет, с каким бэкендом меньше всего соединений в данный момент и перенаправляет запрос на него (с учетом весов).

Настройка выполняется с помощью опции least_conn:

upstream remontka_backend {
least_conn;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}

Random

Запросы передаются случайным образом (с учетом весов). Дополнительно можно указать опцию two — если она задана, то NGINX сначала выберет 2 сервера случайным образом, затем на основе дополнительных параметров отдаст предпочтение одному из них. Это следующие параметры:

  • least_conn — исходя из числа активных подключений.
  • least_time=header (только в платной версии) — на основе времени ответа (расчет по заголовку).
  • least_time=last_byte (только в платной версии) — на основе времени ответа (расчет по полной отдаче страницы).

Пример настройки:

upstream remontka_backend {
random two least_conn;
server 192.168.10.10;
server 192.168.10.11;
server 192.168.10.12;
}

Least Time

Данная опция будет работать только в платной версии 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;
}

* в данном примере мы будем делать расчет исходя из того, как быстро мы получаем в ответ целую страницу.

Stream-запросы

Запросы не 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-балансировки по шагам.

1. Установка компонентов

Если 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. Так или иначе, если какого-то компонента не хватит, мы получим ошибку при сбоке и необходимо будет по ее тексту разобраться. какой пакет нужно доустановить.

Первый шаг выполнен.

2. Загрузка исходников

Переходим в каталог для хранения исходников:

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

3. Сборка nginx

Распаковываем ранее скачанный архив и переходим в него:

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.

4. Настройка балансировки

Настройка апстрима сводится к указанию опции 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

1. Backend на https

Предположим, что наши внутренние серверы отвечают по 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 момента:

  1. Мы в схеме подключения proxy_pass указали https . В противном случае при подключении NGINX будет возвращать ошибку 400.
  2. Мы задали дополнительные опции proxy_set_header , которых не было в примерах выше.

Настройка upstream:

upstream remontka_backend {
server 192.168.10.10:443;
server 192.168.10.11:443;
server 192.168.10.12:443;
}

* в данном примере мы указали конкретный порт, по которому должно выполняться соединение с бэкендом. Для упрощения конфига дополнительные опции упущены.

2. Разные бэкенды для разных страниц

Нам может понадобиться разные страницы сайта переводить на разные группы внутренних серверов.

Настройка сайта:

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 апстрима, каждый со своим набором серверов.

3. На другой хост

Может быть необходимым делать обращение к внутреннему ресурсу по другому 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 мы будем использовать значение данной меременной.

5. TCP-запрос на СУБД PostgreSQL

Рассмотрим, в качестве исключения, 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 .

6. UDP-запрос

Рассмотрим также и возможность балансировки 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 .

7. SSH

С помощью 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.

Unknown directive stream

Ошибка появляется при настройке проксирования с помощью stream (не http-запросов). Данную ошибку мы можем увидеть при попытке выполнить проверку конфигурации:

nginx -t

Причина: nginx собран без поддержки stream.

Решение: проверить поддержку stream можно командой:

nginx -V

Мы должны увидеть:

… —with-stream …

Если данной записи нет, то можно попробовать установить динамический модуль, как описано выше , либо пересобрать nginx с добавлением данной опции.

Читайте также

Возможно, данные инструкции также будут полезны:

1. Примеры редиректов в NGINX .

2. Настройка веб-сервера в Docker .

3. Установка и настройка HAProxy на CentOS .

admin

Recent Posts

Что такое Zulip

Zulip — программное обеспечение для реализации корпоративного чата. Разработан в 2012 году, в 2014 был…

2 месяца ago

Что такое Zookeeper

Zookeeper — cервис-координатор, который позволяет обеспечить контроль синхронизации данных. Разработан на Java компанией Apache Software…

2 месяца ago

Что такое Zimbra

Zimbra — программное обеспечение для реализации почтового сервиса или, если сказать точнее, автоматизации совместной деятельности…

2 месяца ago

Что такое Zabbix

Zabbix — бесплатная система мониторинга. Позволяет отслеживать состояние сетевых узлов, компьютеров и серверов. Возможности: Поддержка…

2 месяца ago

Что такое YouTube

YouTube — компания-владелец одноименного портала для просмотра и хранения видео. Чтобы пользоваться данным порталом достаточно…

2 месяца ago

Что такое yota

Yota — провайдер, предоставляющий доступ к сети Интернет по беспроводной связи. Впервые, сервис начал работать…

2 месяца ago