janol.pl_

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 location i moduł proxy_cache robią 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.
Czy da się ich używać razem Tak, i regularnie tak robię: HAProxy na brzegu jako load balancer i punkt terminacji TLS albo pass-through po SNI, a za nim kilka instancji Nginx serwujących konkretną aplikację ze swoim cache'em i regułami przepisywania. HAProxy rozkłada ruch i pilnuje zdrowia węzłów, Nginx zajmuje się tym, do czego faktycznie został zaprojektowany — obsługą treści. To nie jest kompromis, tylko użycie każdego narzędzia w roli, w której jest najmocniejsze.

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.

← wszystkie wpisy