Балансировка нагрузки - NGINX

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

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

vi /etc/nginx/conf.d/upstreams.conf

* в дан­ном при­ме­ре мы созда­ем файл upstreams.conf, в кото­ром можем хра­нить все наши апст­ри­мы. NGINX авто­ма­ти­че­ски чита­ет все кон­фи­гу­ра­ци­он­ные фай­лы в ката­ло­ге conf.d.

Доба­вим:

* пред­по­ла­га­ет­ся, что в нашей внут­рен­ней сети есть кла­стер из трех веб-сер­ве­ров — 192.168.10.10192.168.10.11 и 192.168.10.12. Мы созда­ли апст­рим с назва­ни­ем test_backend. Поз­же, мы настро­им веб-сер­вер, что­бы он умел обра­щать­ся к дан­но­му бэкенду.

В настрой­ках сай­та (вир­ту­аль­но­го доме­на) нам необ­хо­ди­мо теперь прок­си­ро­вать запро­сы на создан­ный upstream. Дан­ная настрой­ка будет такой:

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

Про­ве­ря­ем кор­рект­ность наше­го кон­фи­гу­ра­ци­он­но­го файла:

nginx -t

Если оши­бок нет, пере­за­пус­ка­ем сервис:

systemctl restart nginx

Приоритеты

При настрой­ке бэкен­дов мы можем ука­зать, кому наш веб-сер­вер будет отда­вать боль­ше пред­по­чте­ние, а кому меньше.

Син­так­сис при ука­за­нии веса:

server <имя сер­ве­ра> weight=<числовой экви­ва­лент веса>;

По умол­ча­нию при­о­ри­тет равен 1.

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

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

Давай­те немно­го пре­об­ра­зу­ем нашу настрой­ку upstreams:

vi /etc/nginx/conf.d/upstreams.conf

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

  • пере­во­дить на сер­вер 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

* в итоге:

  • сер­вер 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. Синтаксис:

Round Robin

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

Hash

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

* это самый рас­про­стра­нен­ный при­мер настрой­ки hash — с исполь­зо­ва­ни­ем пере­мен­ных $scheme (http или https) и $request_uri. При дан­ной настрой­ке каж­дый кон­крет­ный URL будет ассо­ци­и­ро­ван с кон­крет­ным сервером.

IP Hash

Ассо­ци­а­ция выпол­ня­ет­ся исхо­дя из IP-адре­са кли­ен­та и толь­ко для HTTP-запро­сов. Таким обра­зом, для каж­до­го посе­ти­те­ля уста­нав­ли­ва­ет­ся связь с одним и тем же сер­ве­ром. Это, так назы­ва­е­мый, Sticky Session метод.

Для адре­сов IPv4 учи­ты­ва­ют­ся толь­ко пер­вые 3 окте­та — это поз­во­ля­ет под­дер­жи­вать оди­на­ко­вые соеди­не­ния с кли­ен­та­ми, чьи адре­са меня­ют­ся (полу­че­ние дина­ми­че­ских адре­сов от DHCP про­вай­де­ра). Для адре­сов IPv6 учи­ты­ва­ет­ся адрес целиком.

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

Least Connections

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

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

Random

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

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

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

Least Time

Дан­ная опция будет рабо­тать толь­ко в NGINX Plus. Балан­си­ров­ка выпол­ня­ет­ся исхо­дя из вре­ме­ни отве­та сер­ве­ра. Пред­по­чте­ние отда­ет­ся тому, кто отве­ча­ет быстрее.

Опция для ука­за­ния дан­но­го мето­да — least_time. Так­же необ­хо­ди­мо ука­зать, что мы счи­та­ем отве­том — полу­че­ние заго­лов­ка (header) или когда стра­ни­ца воз­вра­ща­ет­ся цели­ком (last_byte).

При­мер 1:

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

При­мер 2:

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

Сценарии настройки

В реаль­ной жиз­ни настрой­ки могут быть несколь­ко слож­нее, чем при­ве­ден­ные здесь или в офи­ци­аль­ной доку­мен­та­ции. Рас­смот­рим несколь­ко при­ме­ров, что может пона­до­бить­ся настро­ить при балансировке.

1. Backend на https

Пред­по­ло­жим, что наши внут­рен­ние сер­ве­ры отве­ча­ют по SSL-кана­лу. Таким обра­зом, нам нуж­но отправ­лять запрос по пор­ту 443. Так­же схе­ма прок­си­ро­ва­ния долж­на быть https.

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

* обра­ти­те вни­ма­ние на 2 момента:

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

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

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

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

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

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

* при такой настрой­ке мы будем пере­да­вать запро­сы к стра­ни­це page1 на груп­пу backend1, а к page2 — backend2.

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

* в дан­ном при­ме­ре у нас есть 2 апст­ри­ма, каж­дый со сво­им набо­ром серверов.

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

Может быть необ­хо­ди­мым делать обра­ще­ние к внут­рен­не­му ресур­су по дру­го­му hostname, неже­ли чем будет обра­ще­ние к внеш­не­му. Для это­го в заго­лов­ках прок­си­ро­ва­ния мы долж­ны ука­зать опцию Host.

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

* в дан­ном при­ме­ре мы будем прок­си­ро­вать запро­сы на бэкен­ды, пере­да­вая им имя хоста internal.domain.com.

4. TCP-запрос

Рас­смот­рим, в каче­стве исклю­че­ния, TCP-запрос на порт 5432 — под­клю­че­ние к базе PostgreSQL.

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

* в дан­ном при­ме­ре мы слу­ша­ем TCP-порт 5432 и прок­си­ру­ем все запро­сы на апст­рим tcp_postgresql.

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

* запро­сы будут слу­чай­ным обра­зом пере­да­вать­ся на сер­ве­ры 192.168.10.14 и 192.168.10.15.

5. UDP-запрос

Рас­смот­рим так­же и воз­мож­ность балан­си­ров­ки UDP-запро­сов — под­клю­че­ние к DNS по пор­ту 53.

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

* в дан­ном при­ме­ре мы слу­ша­ем UDP-порт 53 и прок­си­ру­ем все запро­сы на апст­рим udp_dns. Опция proxy_responses гово­рит о том, что на один запрос нуж­но давать один ответ.

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

* запро­сы будут слу­чай­ным обра­зом пере­да­вать­ся на сер­ве­ры 192.168.10.16 и 192.168.10.17.