Haproxy: Load Balance webes megoldáshoz

A blogomat 2005 óta írogatom, fejlesztgetem, és azóta sok apró más weboldalnak is helyet adott az a webszerver, ami kiszolgálja a kéréseket. Kezdetekben egy fizikai vason futott minden féle alkalmazás. Majd a virtualizáció megjelenésével, a különböző alkalmazásoknak külön virtuális gépet szántam, és ezáltal külön operációs rendszert. Így már a levelezés, a file share, és a web server sem tudta megölni a másikat, mert külön lettek választva. Web server viszont egy volt. Így ha azzal valami történt, akkor a weboldal elérhetetlen volt.

Új struktúra

Evolúciós lépésnek tűnt, hogy a webszervert mint SPOF (Single Point Of Failure) mengszüntessem. Ugyanis, karbantartási műveleteknél, esetleges hibák esetén, a kiesést maguk az ügyfelek is megtapasztalták. Amennyiben nem egy web instance, hanem kettő (vagy több) szolgálja ki az ügyfelek kéréseit máris redundásá tudjuk tenni szolgáltatásnak ezt a szintjét.

Persze szükségünk van egy új dologra, ami kezeli a kapcsolatokat. Érzékeli, hogyha a két web instance közül bármelyikkel baj van, és intelligensen csak oda küld kéréseket ahol azokat ki is szolgálják. Természetesen ez az egység nem csak hibakezelésre lehet jó, hanem a terhelés elosztására is. Tehát ha mindkét web instance elérhető, akkor egyenlően (vagy valamilyen súlyozást, vagy más szabályozást figyelembe véve) osztja el a külső kéréseket a kiszolgálók között.

haproxy1

Az én példámban tehát két web instance lesz, de természetesen ez nem limitált, tetszőleges számban konfigurálhatjuk ezeket a kiszolgálókat. Gyorsan vezessünk is be új fogalmakat. Ahogy az ábrán is láthatjátok ezentúl a Load Balancer eszköz fogja közvetlenül kapni a külső kéréseket. Tehát ezen az egységen lesznek kívülről elérhető portok, így ez lesz a FRONTEND. BACKEND-nek fogjuk nevezni azokat az egységeket, amiknek a Load Balancer továbbíthatja a kérést. Ezek a külső ügyfél számára teljesen láthatatlanok lesznek.

Természetesen ahhoz, hogy a két backend minden körülmények között ugyan azt az információt adja vissza valahogy szinkronban kell őket tartani. Jómaga,, a két backend-en a WWW könyvtárat egy DRBD alapú dual primary módszerrel oldottam meg. Erről többet itt lehet olvasni. A weboldalak által használt adatbázist rakhattam volna külön gépre, vagy gépekre. Viszont számomra praktikusnak tűnt, hogy mindkét web instance mellett egy egy lokális MySQL adatbázis fusson. Mindkét web instance a saját lokális MySQL adatbázisát éri el. Viszont a két MySQL egymás master-master replikációja. Így, ami az egyik MySQL-ben megváltozik, az meg fog változni a másikéban is.

Tehát a dual primary DRBD megoldás a statikus file problémára, a MySQL master-master replica az adatbázishoz. Ezt tekintem a továbbiakban alap állapotnak, hogy két ilyen backend gép rendelkezésre áll.

Haproxy

A Haproxy egy nyílt forráskódú, régóta fejlesztett és nagyon komoly load balancer software, ami minden féle operációs rendszerre elérhető. A fent említett funkciók igazából bármilyen fizikai Load Balancer eszközzel, vagy software-el megvalósíthatóak. Számomra a Haproxy volt az ideális, méghozzá egy Ubuntu rendszerre telepítve.

A HAproxy-ból is az elérhető legújabb, de még fejlesztői változatot használtam (később meg lesz indokolva miért). A csomagokat bárki maga előállíthatja forrásból (cikk hozzá itt), vagy a már legyártott binárisokat is használhatjuk. Én learchiválom őket ide a cikkbe, ha valakinek szüksége lenne rá.

haproxy_1.5-dev17_amd64.deb
haproxy_1.5-dev17_i386.deb

Az én környezetemben a 10.0.0.40-es gép lesz a HAPROXY (Load Balancer). Ez az egység ami kezeli a kívülről kapott kapcsolatokat. A két backend a 10.0.0.58 és 10.0.0.59 lesz. Az utóbbi két gép, lesz amik között a DRBD be lesz állítva, illetve ahol a MYSQL-ek master-master alapú replikációt használnak.

A Haproxy beállításánál egyetlen egy konfigurációs állományt kell szerkesztenünk. Több más weboldal egy szinte üres konfigurációt épít fel, és ahogy belekerülnek új sorok, úgy magyarázza el, hogy ezáltal milyen plusz tudással ruházták fel a Haproxy-t. Én viszont egy teljesen megírt konfiguráción kívánok végigmenni, és bemutatni, hogy is épül fel, és melyik része miért felel.

# cat /etc/haproxy/haproxy

global
        log 127.0.0.1   local0
        log 10.0.0.15   local0 info
        stats socket /var/run/haproxy.sock mode 0600 level admin
        maxconn 4096
        user haproxy
        group haproxy
        daemon
        #debug

defaults
        log     global
        option  dontlognull
        retries 3
        option redispatch
        maxconn 2000
        contimeout      5000
        clitimeout      50000
        srvtimeout      50000
        # DDOS protection
        timeout http-request 5s
        timeout connect 5s
        timeout server 10s
        timeout client 30s

        errorfile       400     /etc/haproxy/errors/400.http
        errorfile       403     /etc/haproxy/errors/403.http
        errorfile       408     /etc/haproxy/errors/408.http
        errorfile       500     /etc/haproxy/errors/500.http
        errorfile       502     /etc/haproxy/errors/502.http
        errorfile       503     /etc/haproxy/errors/503.http
        errorfile       504     /etc/haproxy/errors/504.http

listen  http-stat
    bind 10.0.0.40:4545
    mode http
    option httplog

    stats enable
    stats uri     /xorp-hastatus
    stats realm   Haproxy\ Statistics
    stats auth    admin:admin
    stats refresh 5s


frontend http-in
   bind *:80
   mode http
   option httplog

   # Logging
   reqadd ^X-Real-IP:

   option httpclose
   option forwardfor except 10.0.0.0/8
   option forwardfor except 172.16.0.0/12
   option forwardfor except 127.0.0.0/8
   option forwardfor except 192.168.0.0/16

   # Compression
   compression algo gzip
   compression offload
   compression type text/html text/plain text/css 
application/x-javascript text/javascript

   # ACLs
   acl is_mail_unsec hdr_end(host) -i xorp-mail.xorp.hu
   redirect prefix https://xorp-mail.xorp.hu if is_mail_unsec

   acl is_webmail_unsec hdr_end(host) -i webmail.xorp.hu
   redirect prefix https://xorp-mail.xorp.hu if is_webmail_unsec

   default_backend xorp-web-http

frontend https-in
   bind *:443 ssl crt /etc/ssl/xorp.hu/host.pem ca-file 
/etc/ssl/xorp.hu/host.cert verify optional crt-ignore-err 10

   mode http
   option httplog

   # Logging
   reqadd ^X-Real-IP:

   option httpclose
   option forwardfor except 10.0.0.0/8
   option forwardfor except 172.16.0.0/12
   option forwardfor except 127.0.0.0/8
   option forwardfor except 192.168.0.0/16

   # Compression
   compression algo gzip
   compression offload
    compression type text/html text/plain text/css
application/x-javascript text/javascript

    # ACLs
    acl is_webmail_sec hdr_end(host) -i webmail.xorp.hu
    redirect prefix https://xorp-mail.xorp.hu if is_webmail_sec

    acl is_mail_sec hdr_end(host) -i xorp-mail.xorp.hu
    use_backend xorp-mail-https if is_mail_sec

    default_backend xorp-web-https

backend xorp-web-http
    mode http
    option forwardfor header X-Real-IP
    balance roundrobin
    cookie SERVERID insert nocache indirect
    option httpchk HEAD /check.txt HTTP/1.0
    option log-health-checks

    server xorp-web1 10.0.0.58:80 cookie xorp-web1 check
    server xorp-web2 10.0.0.59:80 cookie xorp-web2 check

backend xorp-web-https
    mode http
    balance roundrobin
    option forwardfor header X-Real-IP
    cookie SERVERID insert nocache indirect
    option httpchk HEAD /check.txt HTTP/1.0
    option log-health-checks

    server xorp-web1 10.0.0.58:80 cookie xorp-web1 check
    server xorp-web2 10.0.0.59:80 cookie xorp-web2 check

backend xorp-mail-https
    mode http
    option forwardfor header X-Real-IP
    option httpchk HEAD /check.txt HTTP/1.0
    option log-health-checks

    server xorp-mail 10.0.0.57:80 check

Akkor, kezdjük is végignézni mi is szerepel most ebben. Ahogy látható vannak főbb bekezdések a konfigurációs állományban. A global részen található maga a haproxy, mint a program számára szükséges beállítások. Fontos, hogy a haproxy file-ba nem szeret közvetlenül logolni, így helyi vagy távoli log szervereket érdemes megadni, illetve hogy milyen facility-be menjenek a logok. Erről majd kicsit később még fogunk szót ejteni.

A socket részben adhatjuk, meg hogy hova nyisson egy socket file-t a haproxy. Ez abban az esetben kell, ha a hatop nevű csomaggal terminálból akarjuk monitorozni a haproxy-t. Itt állíthatjuk még be, milyen felhasználóként fusson a haproxy, illetve, hogy daemon módban.

A következő nagy bekezdés a defaults. Az ide definiált értékek lesznek az alapértelmezettek minden ezután következő résznél. Tehát ha itt megadnunk valamit, azt külön már később nem kell definiálnunk. Amire itt fontos kitérni talán a DDOS protection alatti timeout részek. A http-request értékét én 5 másodpercre állítottam. Tehát ha valaki kommunikálni akar, akkor 5 másodperce van arra, hogy el is kezdje. Különben a haproxy bontja a kapcsolatot. Az errorfile bejegyzés után pedig azt definiálhatjuk, hogy a különböző hibakódokhoz megjelenítendő tartalmat melyik file-ból vegye a program.

Listen

Ezt követően jön a tényleges testreszabása a funkciónak. Két féle módon definiálhatunk terhelést elosztó szolgáltatásokat. Az egyszerűbb módszer, amikor a listen direktívát használjuk. Ilyenkor a frontend és backend információkat, ugyan úgy a listen alatt kell definiálnunk. Először is minden bejegyzésnek nevet kell adni. Én ennek a listen bejegyzésnek a http-stat nevet adtam. Következő sorban meg kell adni, milyen IP címen, és milyen porton legyen a frontend portja elérhető. Beállítottam, hogy HTTP típusú adatot fog kezelni, illetve, httplog típusú logolása lesz. Ezek után csak stats bejegyzéseket láthatunk. Ezzel definiáltam egy webes formában elérhető statisztika oldalt, amin elérhetjük majd, hogy a haporxy éppen hogyn működik. Milyen forgalom megy rajta keresztük, és milyen állapotban látja a backendeket.

haproxy2

Tehát most ez a listen bejegyzés egy belső porton elérhető statisztika elérhetőséget definiál csupán.

Frontend

Nézzük akkor a komolyabb és produktívabb bejegyzéseket. Itt már nem listen bejegyzéseket fogok használni. Külön bejegyzés lesz frontend és külön bejegyzés a backend-ekhez.

Ahogy a fenti konfigurációban látszik két frontend bejegyzés is van. Az egyik a http-in a másik a https-in névre hallgat. A http lesz felelős a normál http kapcsolatokért, ami a 80-as porton fog érkezni. Beállítjuk a http módot, illet httplog opciót.

Valós IP

A következő rész egy fontos rész lesz a konfigurációban. Ugyanis a Haproxy-t használva, a backendek access logjában a Haproxy fog megjelenni mint forrás cím. Azaz, ha használunk valami statisztikát generáló programot, az ezek után azt fogja mutatni, hogy egyedi látogatónk csak 1 lesz. A Haproxy. Ahhoz, hogy ne a Haproxy IP címe jelenjen meg a backendek logjában szükséges, hogy a X-Real-IP ben adjuk át a valós külső IP-t. Illetve, hogy bizonyos belső címtartományokat ne adjon át szükségesek forwardfor except sorok.

Ez persze csak a Haproxy-n szükséges változtatás volt. Ezen túl a backend logolását is meg kell változtatni. Az én esetemben ezek Apache 2-es instancek. A virtual hostoknál módosítottam a Custome logot erről:

CustomLog ‘|/usr/sbin/rotatelogs „/var/log/apache2/weboldal.log” 604800 60’ „%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\””

Erre:

CustomLog ‘|/usr/sbin/rotatelogs „/var/log/apache2/weboldal.log” 604800 60’ „%{X-Real-IP}i %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\””

Kiemeltem a fontos részt. Ezek után, már a backendek újra a valós külső IP-it fogja letárolni a látogatóinknak.

Tömörítés

A következő rész a tömörítés. Beállíthatjuk, hogy a Haproxy tömörítve adja tovább a böngészőknek a kért információt. Természetesen itt szükséges, hogy a böngészők is támogassák ezt a módot. Ennek ellenére, érdemes bekapcsolni. A legtöbben a gzip tömörítési megoldást ajánlják én is ezt állítottam be. Ezen túl külön definiálhatjuk, hogy milyen típusú elemek esetén használja. Itt én a tipikusan szöveges, és statikus állományokat soroltam fel.

Access Controll List

A következő rész lesz az ACL, azaz Access Control List-ekért felelős. A HTTP módnak hála, Layer 7-es szinten tudja a Haproxy ellenőrizni az adatfolyamot, és különböző feltételek szerint cselekedni. Hihetetlen komplex szabályokat lehet írni az ACL-nek hála. Én most egy nagyon alap dolgot használok csupán. Megvizsgálom, hogy a header-ben milyen host szerepel, milyen kiszolgálónak küldik azt a csomagot. Amennyiben illeszkedik arra a hostnévre amit megadtam, akkor az ACL igaznak fog bizonyulni.

Az http-in frontendben szereplő ACL-ek csak sima HTTP to HTTPS átirányítások. Ha valaki a http://webmail.xorp.hu-t kívánja megtekinteni, akkor egyből még a Haproxy átirányítja a https:// verzióra.

Amennyiben egyik ACL-re se illeszkedett az a csomag amit, a frontend kapott, akkor a default_backend bejegyzés lép életbe, és a megadott backend bejegyzés fogja megkapni a csomagot.

SSL és HTTPS

Mielőtt tovább lépnénk a backend-re, viszont nézzük meg a https-in frontendet is. Értelemszerűen, itt már a 443-mas porton fogunk figyelni. Viszont a bind sorban már saját hitelesítéssel kapcsolatos fileket fogunk megadni. Ez azért fontos, mert a Haproxy nem tud titkosított backendeket Layer 7-es módban kezelni. Persze nem kell megijedni. Erre találták ki a http mode helyett a tcp mode-t. A TCP mode egy butább, Layer 4-es ellenörzési szintet tesz lehetővé, és persze nem csak web szerverek kapcsolatait tudja ezáltal kezelni. Cserébe elveszítünk, minden Layer 7-es ACL lehetőséget. Emiatt a funkció miatt kellett a legfrisebb developer verziót használnom a haproxy-ból. A stabil 1.4-es verzióban ez nem implementált.

Ahhoz, hogy a HTTPS esetén ne legyen így, ahhoz szükséges, hogy a haproxy-t vértezzük fel ezzel a titkosítás kezelésével. Ez viszont meg fogja követelni, hogy a HTTPS frontend mögött sima titkosítatlan HTTP (általában port: 80) backendek álljanak.

Saját Titkosítási Tanusítvány generálása

Én a következő pár sor segítségével generáltam magamnak egy ingyenes, persze saját magam aláírt hitelesítéshez használható állomány csomagot.

# mkdir /etc/ssl/xorp.hu
# cd /etc/ssl/xorp.hu
# openssl genrsa 2048 > host.key
# openssl req -new -x509 -nodes -sha1 -days 3650 -key host.key > host.cert

Itt meg kell adnunk a hitelesítéshez szükséges adatokat. Amennyiben Wildcard SSL-t szeretnénk létrehozni arra figyeljünk, hogy a Common Name esetében *.domain.hu módon adjuk meg az értéket.

# openssl x509 -noout -fingerprint -text < host.cert > host.info
# cat host.cert host.key > host.pem
# chmod 400 host.key host.pem

Ezek után már a haproxy konfigurációs állományában található módon használhatóak is a hitelesítések. Amennyiben nem akarjuk, hogy a böngésző figyelmeztessen minket, hogy nem hivatalosan aláírt certificate-ről van szó, akkor a következő módon exportáljuk ki a Windows számára is használható tanusítványt.

# openssl crl2pkcs7 -nocrl -certfile host.cert -outform DER -out host.pkcs7

Ezek után juttassuk el a Windows gépünkre. Jobb katt, Install Certificate. Majd a megjelenő ablakban next, next finish.

haproxy3

Visszatérve a https-in frontend bejegyzéseihez, a bind sor után a már ismert valós logolás és tömörítés beállításait láthatjuk. Majd ezt követően kezdődnek az ACL-ek, amiket a bind sorban kitárgyalt dolgok miatt használhatunk HTTPS adatfolyam esetén is. Az első ACL itt is csak egy egyszerű átirányítást fog érvényesíteni. A második viszont már a megfelelő hostname illeszkedése esetén egy új backend-re fogja irányítani a forgalmat, nem pedig a defaultra. Itt is az összes olyan adat, ami nem illeszkedik egyik ACL-re sem, az mindg a default_backend-re lesz irányítva.

Tehát a frontend az a rész, ahol definiálni tudjuk, hogy kívülről milyen portra érkező forgalmat, milyen szabályok szerint, hova akarjuk kézbesíteni.

Backend

Most pedig nézzük a backend definíciókat. Hármat fogtok látni a config-ban. Az egyik a default a http frontend számára (xorp-web-http). A másik default külön a https forgalom számára (xorp-web-https). Egy harmadik pedig a email webes forgalom számára. A backendek esetén is definiálni kell a módot, és természetesen itt is a http mode-t kell használnunk. A frontendek esetében már volt szó a X-Real-IP fontosságáról. Ezt természetesen a backendeknél is definiálnunk kell.

Cookie

A cookie bejegyzés felhatalmazza a haproxy-t, hogy a kérést kiszolgáló backend-et általunk megadott azonosítóját belecsempéssze a válaszba. Ezek után amennyiben a kliens megint kérést intéz a haproxy-hoz, és előtte már valamelyik backend szolgálta ki, akkor amennyiben felfedezi a haproxy ezt az azonosítót, megint a már ismert szerverhez fogja kézbesíteni a kérést. Ezzel biztosítva, hogy beállítások, session információk ne vesszenek el.

Httpchk

Természetesen valamilyen módon tesztelnünk is tudni kell a backend-et, hogy képes-e a kéréseket kiszolgálni. Amennyiben nem adunk meg semmit, sima Layer 4-es check fog csak történni. Azaz megnézi, hogy a port elérhető-e, amire a kéréseket kell továbbítani. Viszont definiálhatunk Layer 7-es szintű ellenőrzést. A httpchk után egy GET lekéréssel a /check.txt-t kérjük le (természetesen ezt a file-t létre kell hozni a backend webes környezetében). Amennyiben ez a file elérhető, abban az esetben a gépünk működőnek lesz nyilvánítva. Amennyiben a gép nem elérhető a log-health-check hatására egy szép log bejegyzést fogunk kapni.

Végül, de nem utolsó sorban definiálnunk kell magukat a szervereket, amik ezalatt a backend alatt elérhetőek lesznek. Megadni milyen IP-n és milyen porton szolgáltatnak. Ezek mellett check-et és a cookie opciókat bekapcsolni rá. Természetesen súlyozhatjuk őket, illetve a terhelés eloszlását is befolyásolhatjuk a balance opció megadásával. Én egy egyenlő rangú egyszerű roundrobin eloszlást állítottam be.

Tehát a http és https kapcsolatokat is ugyan az a két backend szolgálja ki. Layer 7-es szinten ellenőrzi őket a haproxy. Amennyiben az egyik nem képes kiszolgálni a kéréseket, abban az esetben hibásnak fogja megjelölni az adott gépet a haproxy, és csak a másik fogja kiszolgálni a kéréseket. Amint megint elérhető a hibás szerver, automatikusan leteszteli a haproxy, és ha jónak íteli meg, már is részt vehet a kiszolgálásban. Kivétel a webmail forgalom. Ott csupán egyetlen szerverből álló külön (Zimbra) szerver szolgálja ki a kéréseket, és nem az alapértelmezett backendek.

Email értesítés

Jogos igény, hogy ha valamelyik backend szerver nem elérhető, akkor arról kapjunk értesítést. A haproxy alapból nem rendelkezik ilyen funkcióval. Ő minden log bejegyzést elküld az általunk megadott log szervernek. Ez lehet távoli és helyi egyaránt. Amennyiben rendelkezünk megfelelő monitorozó rendszerrel azzal is figyeltethetjük. Ha nincs, vagy valami független egyszerű megoldással akarjuk megoldani, akkor alkalmazhatjuk a következő módszert.

Először is oldjuk meg, hogy a haproxy-t futtató rendszer fogadja és külön log file-ba tárolja el a haproxy üzeneteit. Én, ezt most rsyslog-al fogom tenni.

# vi /etc/rsyslog.conf

# provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

Bizonyosodjunk meg, hogy az utolsó két sor ki van kommentezve. Ezek után szerkesszük meg az rsyslog szabályait, úgy, hogy a következő szabály elérhető legyen benne:

# vi /etc/rsyslog.d/50-default.conf
local0.* -/var/log/haproxy0.log

Majd indítsuk újra az rsyslogd-t.

# /etc/init.d/rsyslog restart

Ezt követően a /var/log/haproxy0.log file folyamatosan a haproxy üzeneteit kell, hogy tartalmazza. Már csak be kell állítanunk egy új alkalmazást, ami figyeli, hogy milyen üzenetek kerülnek bele, és hiba esetén emailt küldjön a kért személyeknek. Én erre a SEC nevezetű toolt fogom használni.

# apt-get install sec

Módosítsuk az alapértelmezett konfigurációt. Állítsuk be az input file-t és engedélyezzük a programot.

#vi /etc/default/sec

RUN_DAEMON=”yes”
DAEMON_ARGS=”-conf=/etc/sec.conf -input=/var/log/haproxy0.log -pid=/var/run/sec.pid -detach -syslog=daemon”

Majd hozzuk létre a következő konfigurációs állományt.

# vi /etc/sec.conf

# SEC – Simple Event Correlator Configuration File
#
# Author: Steve Moitozo
# Created: 20070304
# Description:
#
# match on a line like this:
# Server http_proxy/www0 is DOWN. 0 active and 2 backup servers left.
# Running on backup. 0 sessions active, 0 requeued, 0 remaining in queue.
# Take the name of the instance (http_proxy/www0) and put it in $1
# Take the server status (DOWN or UP) and put it in $2
# Take the rest of the line and put it in $3
#
type=Single
ptype=RegExp
pattern=Server\s+(\S+)\s+\S+\s+(\S+)(.*)
desc=$0
action=pipe ‘%t server $1 went $2 $3’ /usr/bin/mail -s ‘HAProxy: $1 went $2’ admin@xorp.hu

Ahhoz, hogy működjön már csak újra kell indítanunk.

# /etc/init.d/sec restart

Innentől minden Server Down vagy UP üzenetről levelet is fogunk kapni automatikusan a megadott admin[kukac]xorp.hu címre.

Összegzés

A haproxy egy hihetetlenül sokrétű program. Annak ellenére, hogy ingyenes, profi helyeken és eszközökben is használják. Folyamatosan fejlesztik, és az Layer 7 ACL-eknek hála nagyon komoly szűréseket is meg lehet vele valósítani. Ennek ellenére, aki csak olyan egyszerű szinten szeretné használni ahogy én teszem, annak is egy kivalló megoldást nyújt magasabb rendelkezésre állás elérésében.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük