Rozdelenie záťaže aplikácie na viacero nodov (load manažment) sa dá spraviť jednoducho využitím NGINX web serveru. NGINX je opensource a jeho konfigurácia nezaberie veľa času. NGINX je vynikajúcim, vysoko výkonným webovým serverom, ktorým obslúžime veľké množstvo dotazov na server pri minimálnych nárokoch na operačnú pamäť. Požiadavky na server sú spracovávané asynchrónne, čo je obrovskou výhodou oproti napríklad serveru APACHE. V článku ukážem, ako rozdeliť a prioritizovať záťaž jednotlivých nodov. Ako definovať záložný server a, ako nastaviť limit počtu pripojených IP adries..



Konfigurácia load balancera

Ako príklad si uvedieme Java Spring Boot aplikáciu, ktorej každá pustená inštancia bude počúvať na samostatnom porte (8081, 8082, 8083). Aplikácia bude obsahovať endpoint / ,ktorý zobrazí číslo aktuálne nasadenej verzie aplikácie. Na porte 8080 bude počúvať NGINX server, ktorý bude plniť funkciu load balancera a rozdeľovať záťaž podľa vopred definovaných pravidiel konfigurácie.



                            events {}
                            http {
                              upstream backend {
                                server node1 weight=3;
                                server node2;
                                server node3;

                                # konfigurácia môže obsahovať názov domeny alebo IP, port
                                # server 127.0.0.1:8084
                              }

                              server {
                                # nginx bude počúvať na porte 8080
                                listen 8080;
                                location / {
                                  proxy_pass http://backend;
                                }
                              }
                            }
                            

Parameter weight zvyšuje trojnásobne váhu priority nodu. To znamená, že pri 6 požiadavkách na Nginx by load balancer rozdelil záťaž nasledovne :

  1. požiadavka -> node1
  2. požiadavka -> node1
  3. požiadavka -> node1
  4. požiadavka -> node2
  5. požiadavka -> node3
  6. požiadavka -> node1


Záložný server

V našom príklade budú požiadavky na server rozdelené na nody node1 a node2. Node node3 bude záložný a nebude príjmať požiavky. Zmení sa to v okamihu ak sa primárny node app1 dostane do stavu nedostupný. V tomto prípade bude príjmať požiadavky node app3 pokiaľ node app1 nezmený stav na dostupný.


Konfigurácia NGINX súboru nginx.conf


                                events {}
                                http {
                                  upstream backend {
                                    server node1 max_fails=3 fail_timeout=30s;
                                    server node2;
                                    server node3 backup;
                                  }

                                  server {
                                    listen 8080;
                                    location / {
                                      proxy_pass http://backend;
                                    }
                                  }
                                }
                                

Atribút max_fails hovorí o tom, koľko krát musí byť server nedostupný za obdobie fail_timeout aby bol vyhodnotený ako nedostupný. Ak sa dostane server do takého stavu, budú jeho požiadavky presmerované na záložný server node3

Canary deployment (A/B Testing)

Pomocou techniky Canary deployment (prípadne A/B Testing), vieme znížiť riziko nasadenia novej verzie do produkcie pomalým zavedením zmeny na malú množinu používateľov. Máme dve verzie aplikácie 1a, ktorá bude v našom prípade bežať na portoch 8081, 8082 a novšiu verziu 1b bežiacu na porte 8083. Päť percent používateľov bude pristupovať na novú verziu aplikácie. Zvyšok bude presmerovaný na starú verziu. Postupne sa tento pomer môže zmeniť.

Ilustračný príklad aplikácie v produkčnom prostredí

A/B Testing
Konfigurácia bude vyzerať nasledovne


                                events {}
                                http {
                                  
                                  # aplikácia s verziou 1.0
                                  upstream appversion_1a {
                                    server node1:8081;
                                    server node2:8082;
                                  }

                                  # aplikácia s verziou 1.1
                                  upstream appversion_1b {
                                     server node3:8083;
                                  }

                                  # požiadavky na server rozhadzované v pomere 5:95
                                  split_clients "${date_gmt}" $appversion {
                                      5%   appversion_1b;
                                      *    appversion_1a;
                                  }

                                  # tiez je možné použiť NGINX premennú remote_addr
                                  # split_clients "${remote_addr}" $appversion {
                                  #     5%   appversion_1b;
                                  #     *    appversion_1a;
                                  # }

                                  server {
                                      listen 8080;
                                      location / {
                                        proxy_set_header Host $host;
                                        proxy_pass http://$appversion;
                                      }
                                  }
                                }
                                

Funkcia split_clients preberie ako vstupný parameter reťazec (v našom prípade aktuálny dátum a čas) a vygeneruje z neho hash. Tento hash potom použije ako parameter generovaného náhodného čísla od 0 do max(INTEGER). Ak je náš rozptyl 95 percent na appversion_1a, tak je to číslo medzi 0 a 4,080,218,930. Ak sa vygeneruje náhodné číslo nad túto hodnotu, spadá to pod appversion_1b. Ak použijeme remote_addr ako parameter funkcie split_clients treba si uvedomiť, že pri testovaní sa pripájame len z jednej IP adresy. Tým pádom sa vždy vygeneruje rovnaký hash na generovanie náhodného čísla. To spôsobí, že node na spracovanie požiadavky bude stále rovnaký.


Spustenie demo aplikácie simulujúcej posledný príklad


                                 git clone git@bitbucket.org:Morione/nginx-blog.git
                                 cd nginx-blog
                                 ./bootstrap.sh
                                

Záver

Ako vidieť na príkladoch, práca s NGINX je pomerne jednoduchá. Za žiadne peniaze, veľa muziky. V blogu sme si ukázali, ako nastaviť NGINX, aby fungoval ako load balancer. Ukázali sme si, ako zvládnuť "canary deployment" a, ako mať pripravený záložný server, ak príde výpadok.
Zdrojové kódy príkladu je možné stiahnúť na https://bitbucket.org/Morione/nginx-blog/src/master
Príklady používajú kontajnerizáciu, čiže je potrebné mať nainštalovaný docker a docker-compose.


Michal Kalman
Michal Kalman
Softvérový vývojár

Softvérový vývojár a podnikateľ zameriavajúci sa na platformu Java, Python a Javascript. Mám dlhoročné profesionálne skúsenosti s vývojom Java/JavaEE aplikácií. Som zakladateľ firmy Morione, ktorá sa venuje návrhom a realizáciou efektívnych webových aplikácií. Podporujem a vyvíjam viaceré startupy. Vo voľnom čase cestujem po svete, behám po horách, píšem blogy a tvorím kreatívne videá.