Porównanie
HAProxy vs Nginx jako reverse proxy
Obu tych narzędzi używałem produkcyjnie w kilku firmach, przy zupełnie różnej skali — od hostingu z tysiącami wirtualnych domen na wspólnych serwerach, przez brokera finansowego, gdzie liczyła się przewidywalność opóźnień, po obecne środowisko korporacyjne z wewnętrznymi usługami LDAP, Wildfly i bazami danych za jednym punktem wejścia. Nie ma tu jednego zwycięzcy — jest para narzędzi zbudowanych z innym priorytetem w głowie, i większość sporów „HAProxy czy Nginx” wynika z ignorowania tej różnicy.
Skąd się wzięła różnica
Nginx narodził się jako serwer WWW i dopiero później dorobił się proxy_pass. Do dziś widać to w konfiguracji — bloki location, obsługa plików statycznych, cache HTTP na poziomie serwera to jego naturalny język. HAProxy od pierwszego dnia jest load balancerem — nie serwuje plików, nie ma pojęcia o systemie plików serwowanej treści, za to model połączeń, kolejkowania i health checków jest w nim dopracowany głębiej niż w czymkolwiek, co pierwotnie miało być serwerem WWW.
Health checki
To miejsce, w którym różnica jest najbardziej odczuwalna w praktyce. HAProxy ma health checki jako obywatela pierwszej klasy — TCP, HTTP z dowolnym kodem oczekiwanym, a nawet natywne sprawdzanie protokołów takich jak MySQL czy LDAP bez pisania własnego skryptu:
backend wildfly_app
balance roundrobin
option httpchk GET /health
http-check expect status 200
server app1 10.0.1.11:8080 check inter 3s fall 3 rise 2
server app2 10.0.1.12:8080 check inter 3s fall 3 rise 2
Węzeł, który trzy razy z rzędu nie odpowie 200, wypada z rotacji automatycznie, bez restartu procesu i bez przerwy w obsłudze pozostałych. Wersja Nginx open-source nie ma aktywnych health checków wbudowanych — trzeba albo dokupić Nginx Plus, albo dopisać coś zewnętrznego (moduł nginx_upstream_check_module, zewnętrzny skrypt przez upstream z max_fails). max_fails/fail_timeout działa, ale reaguje dopiero na realny ruch — jeśli backend padnie w ciszy, pierwsi użytkownicy i tak dostaną błąd, zanim Nginx go wyłączy.
Sticky sessions
Przy aplikacjach trzymających sesję w pamięci procesu (klasyczne Wildfly bez klastrowanej sesji) sticky sessions nie są opcją, tylko wymogiem. HAProxy robi to przez ciasteczko wstrzykiwane na poziomie proxy, więc backend nie musi nic wiedzieć o mechanizmie:
backend wildfly_app
cookie SRV insert indirect nocache
server app1 10.0.1.11:8080 check cookie app1
server app2 10.0.1.12:8080 check cookie app2
Nginx open-source nie ma tego wbudowanego wprost — jest ip_hash, który przypina klienta po adresie IP, co psuje się natychmiast za NAT-em, gdzie wielu użytkowników wychodzi z tego samego adresu. Prawdziwe sticky sessions oparte o ciasteczko wymagają w Nginx zewnętrznego modułu (nginx-sticky-module) albo znowu wersji Plus.
SSL termination
Oba radzą sobie dobrze, różnią się filozofią. Nginx od lat jest wybierany do terminacji TLS przy dużym ruchu HTTP, bo jego model event-driven i szeroka konfiguracja (HTTP/2, HTTP/3 od nowszych wersji, cache OCSP stapling) są dopieszczone pod kątem ruchu webowego. HAProxy też terminuje TLS sprawnie i od wersji 1.5 robi to natywnie, ale jego mocną stroną jest raczej to, co dzieje się po terminacji — precyzyjne routingowanie ruchu po SNI, ACL-e budowane z dowolnych nagłówków, płynne przełączanie backendów bez przerywania połączeń.
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/bundle.pem
acl is_ldap req.ssl_sni -i ldap.internal.example.com
use_backend ldap_backend if is_ldap
default_backend wildfly_app
Kiedy wybieram które
- HAProxy — kiedy priorytetem jest przewidywalne rozłożenie ruchu na wiele backendów tego samego typu: baza danych, aplikacja Java, wewnętrzny LDAP. Tam, gdzie liczy się precyzyjny health check i minimalna latencja przy dużej liczbie równoczesnych połączeń.
- Nginx — kiedy proxy stoi przed treścią webową mieszaną ze statykami, cache'owaniem odpowiedzi i regułami przepisywania URL-i. Jego
locationi modułproxy_cacherobią to naturalnie, bez sztucznego dopasowywania narzędzia do zadania. - Caddy — do prostych wdrożeń z automatycznym TLS, gdzie i tak nie potrzebuję ani zaawansowanego load balancingu, ani cache'owania. Pisałem o tym osobno przy okazji porównania Caddy z Nginx + Certbot — tam chodziło o co innego niż w tym wpisie: o wygodę wystawienia HTTPS, nie o load balancing produkcyjnego ruchu.
Skala robi różnicę
Na hostingu z tysiącami domen wirtualnych Nginx wygrywał łatwością zarządzania masą konfiguracji per-domena i niskim zużyciem pamięci na tysiące bezczynnych połączeń. W środowisku brokera finansowego, gdzie każda milisekunda opóźnienia się liczyła i backendy padały bez ostrzeżenia, HAProxy wygrywał health checkami i przewidywalnością pod obciążeniem. W obecnej roli, gdzie HAProxy stoi przed parą LDAP i bazami danych, liczy się dokładnie to samo co u brokera: szybkie wykrycie padniętego węzła, zanim zauważy je użytkownik.
Pytanie „HAProxy czy Nginx” jest źle postawione. Właściwe pytanie brzmi: co stoi za proxy i czego to coś potrzebuje.
Ten sam LDAP master-master, o którym pisałem osobno, stoi u mnie właśnie za HAProxy z health checkiem na ldaps:// — opisałem to przy okazji wdrożenia replikacji z TLS i ppolicy.