haproxy - czyli z ruchem HTTP zabaw kilka

Wyobraźmy sobie sytuację, że mamy serwis z dość dużą oglądalnością, wszelkie optymalizacje już zawodzą, programiści przepisali 50% zapytań SQL, administratorzy baz danych wrzucili 75% jej zawartości do pamięci RAM, memcache robi się czerwony od ilości GET'ów a serwis i tak działa coraz wolniej... rozkładamy ręce i szukamy rozwiązania do balansowania ruchu na kilka serwerów.

Jak przystało na dobrego szperacza google'owego na pewno natrafiamy na informacje o LVSie, Big IP, Apache w trybie proxy, później pojawia się też jakiś Nginx, aż w końcu może trafimy na informacje o haproxy i to właśnie o tym narzędziu będzie ten wpis.

haproxy - jak możemy wyczytać z dokumentacji, jest to reverse proxy TCP/HTTP dla środowisk wysokiej dostępności, potrafiące m.in:

  • route'ować pakiety HTTP na podstawie zawartych Cookie
  • przełączać ruch na inny serwer w przypadku wykrycia awarii
  • blokować requesty HTTP na podstawie analizy nagłówków
  • generować statystyki ruchu/usług
  • modyfikować nagłówki HTTP w locie (coś dodać, coś usunąć, coś zmodyfikować)
  • zatrzymać przyjmowanie nowych połączeń bez zrywania nawiązanych
  • i wiele wiele więcej

Opiszę prosty przykład konfiguracji haproxy, który będzie można użyć do balansowania ruchu HTTP do dwóch serwerów.

Schemat:

  • xen2 - serwer z HTTP Apache - 192.168.1.241
  • xen3 - serwer z HTTP Lighttpd - 192.168.1.242
  • xen4 - serwer z HTTP Nginx - 192.168.1.124
  • xen7 - serwer z haproxy - 192.168.1.220

Nie chcę opisywać sposobu instalacji haproxy, bo każdy wybierze swoją drogę, apt-get install haproxy, emerge haproxy, wget haproxy-1.3.17.tar.gz; ./configure; make; make install, itd... ja używam Ubuntu server 8.04.2 i środowiska wirtualnego XEN, więc u mnie był apt-get ;-) mając zainstalowane haproxy, możemy zacząć konfigurację:

# vi /etc/haproxy.cfg

  • global - ustawienia typu logowania, ilości maksymalnej połączeń, uid'a, gid'a, itd...
  • default - ustawienia domyślne dla wszystkich usług frontend/backed.
  • listen - sekcja właściwa która nas będzie interesowała w tym momencie

Nasza przykładowa konfiguracja wygląda następująco:

root@xen7:~# cat /etc/haproxy.cfg

global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
maxconn 4096
user haproxy
group haproxy
defaults
log global
mode http
option httplog
option dontlognull
retries 3
redispatch
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
option httpclose

listen xen 192.168.1.220:80
balance roundrobin
server xen2 192.168.1.241:80
server xen3 192.168.1.242:80
server xen4 192.168.1.125:80

Czyli mamy sekcję listen (nazwa usługi xen) słuchająca na adresie IP load-balancer'a, na potrzeby tego opisu tak może być, ale najlepiej jest każdą usługę (klaster) bindować do innego adresu IP, balance roundrobin oznacza, że requesty będą rozdzielane kolejno do każdego node'a xen2, xen3, xen4 i niżej jest już sama definicja node'ów, server xen2, 3, 4, oraz IP do których należy przekierować requesty.

Uruchamiany daemona (dla testów w trybie foreground i debug):

root@xen7:~# haproxy -dV -f /etc/haproxy.cfg

Available polling systems :
select : pref=150, test result OK
sepoll : disabled, test result OK
epoll : disabled, test result OK
poll : disabled, test result OK
Total: 4 (1 usable), will use select.
Using select() as the polling mechanism.

Tak wiem polling przy wykorzystaniu select(), nie jest tym co nas zadowala, ale dla testów może być ;-) najwidoczniej paczka w Ubuntu była skompilowana z wyłączonym epollem ;-(

W praktyce 3 requesty wyglądają tak:

macbook:~ jamzed$ lwp-request -Sde xen7|grep Server
Server: lighttpd/1.4.19

macbook:~ jamzed$ lwp-request -Sde xen7|grep Server
Server: nginx/0.5.33

macbook:~ jamzed$ lwp-request -Sde xen7|grep Server
Server: Apache/2.2.8 (Ubuntu)

W konsoli widzimy debug:

00000002:xen.accept(0003)=0005 from [192.168.1.182:54865]
00000002:xen.clireq[0005:ffff]: GET / HTTP/1.1
00000002:xen.clihdr[0005:ffff]: TE: deflate,gzip;q=0.3
00000002:xen.clihdr[0005:ffff]: Connection: TE, close
00000002:xen.clihdr[0005:ffff]: Host: xen7
00000002:xen.clihdr[0005:ffff]: User-Agent: lwp-request/5.810
00000002:xen.srvrep[0005:0006]: HTTP/1.1 200 OK
00000002:xen.srvhdr[0005:0006]: Server: nginx/0.5.33
00000002:xen.srvhdr[0005:0006]: Date: Fri, 19 Jun 2009 17:32:57 GMT
00000002:xen.srvhdr[0005:0006]: Content-Type: text/html
00000002:xen.srvhdr[0005:0006]: Content-Length: 151
00000002:xen.srvhdr[0005:0006]: Last-Modified: Wed, 30 Aug 2006 10:39:17 GMT
00000002:xen.srvhdr[0005:0006]: Connection: close
00000002:xen.srvhdr[0005:0006]: Accept-Ranges: bytes

przychodzący request (GET / HTTP/1.1) - nagłówki od klienta xen.clihdr i pełną odpowiedź serwera xen.srvhdr.

Takim sposobem udało nam się szybko zrobić w prosty sposób load-balancer HTTP, jest to tylko ułamek tego co potrafi haproxy, w kolejnym wpisie pokażę kilka operacji na nagłówkach HTTP oraz jak "kierować" ruch HTTP do konkretnych node'ów na podstawie żądań GET (rozdzielanie ruchu w zależności od typu obiektu).

A jakie są Wasze preferencje co do balanserów? LVS? A może jakieś sprzętowe?