Spis treści
Tomcat - Klaster. Środowisko rozproszone oraz HA (wysoka dostępność)
Autor: Marcin Kasiński
02.11.2013 19:23:49 +0200
W niniejszym artykule postaram się opisać środowisko pozwalające na rozłożenie obciążenia aplikacji internetowych pomiędzy wiele instancji serwera WWW oraz Tomcat. Jako środowisko pokazowe wykorzystane zostaną w sumie 4 serwery z zainstalowanymi komponentami HAProxy, keepalived, httpd oraz tomcat.
Dla środowiska postawione zostały 2 główne cele. Pierwszym jest ciągłość pracy środowiska w przypadku awarii serwera poprzez zdublowanie serwerów. Każdy z komponentów środowiska występuje w dwóch instancjach i w normalnych warunkach oba serwery obsługują przychodzący ruch. Celem zdublowania jest to aby awaria jednego ze zdublowanych komponentów nie wpłynęła na ciągłość pracy. W przypadku awarii cały ruch będzie obsłużony przez drugi działający serwer do momentu, aż awaria nie zostanie rozwiązana. Drugim celem jest replikacja sesji aplikacji pomiędzy nodami. W przypadku prostego zdublowania serwera tomcat każdy z nodów ma własną sesję. W przypadku awarii jednego z nodów i ruch zostanie przekierowany na drugi powstanie wtedy nowa sesja. Oznacza to, że zostaną utracone wszelkie dane przechowywane w sesji danego użytkownika. Aby temu zapobiec należy sesje aplikacji przechowywać równocześnie na wszystkich nodach. W takiej sytuacji w przypadku awarii po przekierowaniu ruchu na drugi nod nie powstanie nowa sesja , tylko zostanie odnaleziona konkretna sesja związana z danym użytkownikiem.
Architektura rozwiązania
Celem opisu środowiska klastrowego Tomcat wykorzystam 4 maszyny z zainstalowanym systemem operacyjnym Fedora Core 19.
- 192.168.146.133 Maszyna HA1 (HAProxy Load Balancer pierwszy nod)
- 192.168.146.134 Maszyna HA2 (HAProxy Load Balancer drugi nod)
- 192.168.146.100 Wirtulny adres HA0 (Wirtulne IP dla maszyn HA1 i HA2)
- 192.168.146.130 Maszyna WWW1 (Serwer Apache oraz Tomcat pierwszy nod)
- 192.168.146.132 Maszyna WWW2 (Serwer Apache oraz Tomcat drugi nod)
W naszej konfiguracji na zewnątrz wystawione jest IP wirtualne HA0. Wszelki ruch powinien odbywać się poprzez to wirtualne IP, które jest skonfigurowane na maszynach HA1 oraz HA2 za pomocą modułu keepalived. Oznacza to, że wywołując to wirtualne IP ruch będzie przekierowany na fizyczną maszynę HA1 lub HA2. Awaria jednej z tych maszyn spowoduje , że kolejne wywołania będą przekazywane do pozostałego działającego serwera.
Kiedy ruch trafi do serwera HA1 lub HA2 zostanie on obsłużony przez moduł HAProxy Load Balancer. Moduł ten ma za zadanie przekazanie ruchu do jednego z działających serwerów WWW1 lub WWW2.
Dalej na serwerze WWW1 lub WWW2 ruch trafi do serwera Apache httpd ze skonfigurowanym modułem proxy.
Statyczna treść aplikacji webowej jest serwowany bezpośrednio przez serwer httpd. Treść dynamiczna za pomocą modułu proxy przekierowywana jest do jednego z serwerów tomcat.
Serwery tomcat są skonfigurowane w klastrze, którego celem będzie replikacja sesji.
W takiej konfiguracji przy awarii jednego serwera Tomcat kolejne zapytanie użytkowników będą kierowane do drugiego działającego serwera Tomcat, który to serwer
będzie miał aktualny stan sesji. Oznacza to, że dla użytkownika końcowego awaria jednego Tomcata będzie niezauważona i nie wpłynie na działanie aplikacji.
Konfiguracja wszystkich serwerów
Poniżej znajdują się kroki jakie należy wykonać podczas instalacji serwera.
Poniższe kroki należy wykonać na wszystkich maszynach składających się na nasze środowisko.
Podczas instalacji wybieramy standardowe opcje. Jako konfiguracje instalatora podczas instalacji wybieramy "serwer WWW".
Po zakończeniu instalacji i restarcie serwera logujemy się na konto root.
W pierwszym kroku aktualizujemy wszystkie pakiety na serwerze.
yum -y update
Następnie, jako że mamy do czynienia ze środowiskiem developerskim nie zajmujemy się tu zupełnie bezpieczeństwem, pozwalamy sobie na wyłączenie serwisu firewalld . Od tej pory środowisko mamy niezabezpieczone. Zwracam na to uwagę jeśli ktoś chciałby operację to wykonać na serwerach, na których zdecydowanie nie należy powyższej operacji wykonywać ze względów bezpieczeństwa.
systemctl stop firewalld.service systemctl disable firewalld.service
Konfiguracja HAProxy Load Balancer
Na obu maszynach HA1 oraz HA2 powinno się zainstalować pakiety keepalived oraz haproxy. Keepalived odpowiada za wystawienie dla obu maszyn jednego wirtualnego IP dla komunikacji. Haproxy jest modułem Load Balancer pozwalającym na rozłożenie obciążenia pomiędzy istniejące serwery WWW1 i WWW2. Poniższe komendy instalują pakiety oraz włączają modułu co spowoduje, że będą one uruchamiane przy starcie serwera
yum install -y keepalived haproxy systemctl enable haproxy.service chkconfig --level 2345 keepalived on
Konfiguracja modułu haproxy.service
Plik konfiguracyjny haproxy znajduje się w lokalizacji /etc/haproxy/haproxy.cfg W pliku tym najważniejsze parametry to :
- frontend : Parametry usługi HAProxy na jakiej następuje nasłuchiwanie na połączenia
- backend : lista serwerów do których będzie następowało przekierowanie wraz z parametrami
W naszym przypadku na maszynach HA1 oraz HA2 na porcie 80 będzie wystawiona usługa HAProxy, która będzie przekierowywała komunikację do 2 serwerów 192.168.146.131 oraz 192.168.146.132, gdzie znajdują się uruchomione usługi httpd Apache nasłuchujące również na standardowym porcie 80.
Aby zrealizować komunikacje, o której jest mowa powyżej, na obu maszynach na koniec pliku konfiguracyjnego HAProxy należy dodać poniższy wpis.
# HAProxy's stats listen stats 0.0.0.0:9090 stats enable stats hide-version stats uri / stats realm HAProxy\ Statistics stats auth admin:admin frontend httpFrontEnd 0.0.0.0:80 maxconn 100000 option forwardfor header x-forwarded-for default_backend httpBackEnd backend httpBackEnd server web1 192.168.146.131:80 cookie A check server web2 192.168.146.132:80 cookie B check
Dalej możemy uruchomić na obu maszynach serwis HA Proxy
systemctl enable haproxy.service
Single Point Of Failure - Konfiguracja Keepalived
W wielu instrukcjach dotyczących keepalived możemy przeczytać, że w pierwszym kroku konfiguracji keepalived należy na obu maszynach HA1 oraz HA2 włączyć możliwość współdzielenia jednego IP oraz przeładować konfiguracje.
echo 1 > /proc/sys/net/ipv4/ip_forward echo 1 > /proc/sys/net/ipv4/ip_nonlocal_bind sysctl -a | grep net.ipv4.ip_forward sysctl -a | grep net.ipv4.ip_nonlocal_bind
sysctl -p
Celem ustawienia tych wartości na stałe aby były aktywowane przy starcie serwera należy w pliku /etc/sysctl.conf należy dodać poniższe linie (lub wyedytować ustawiając wartość 1):
net.ipv4.ip_forward = 1 net.ipv4.ip_nonlocal_bind = 1
Po tym można wykonać poniższą komendę celem przeładowania konfiguracji lub zrestartować serwer.
sysctl -p /etc/sysctl.conf
Z mojego doświadczenia wynika, że w przypadku dystrybucji Fedora Core 19 nie ma takiej potrzeby, wiec powyższe umieściłem tylko informacyjnie. Plik konfiguracyjny keepalived znajduje się w lokalizacji /etc/keepalived/keepalived.conf
W pliku tym najważniejsze parametry to :
- virtual_ipaddress : Witrualny adres IP widoczny na zewnątrz
- interface : nazwa interfesu sieciowego na jakim następuje komunikacja pomiędzy nodami keepalived
W naszym przypadku ustanowimy dla maszyn H1 oraz H2 wirtualne IP 192.168.16.100.
Na maszynie HA1 plik ten powinien mieć postać:
ovrrp_script chk_haproxy { script "killall -0 haproxy" # verify the pid existance interval 2 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance VI_1 { interface eno16777736 # interface to monitor state MASTER virtual_router_id 51 # Assign one ID for this route priority 101 # 101 on master, 100 on backup authentication { auth_type PASS auth_pass 1111 # put in your own numeric password here (In this example it's 1111) } virtual_ipaddress { 192.168.146.100 # the virtual IP } track_script { chk_haproxy } }
Na maszynie HA2 plik ten powinien mieć postać:
ovrrp_script chk_haproxy { script "killall -0 haproxy" # verify the pid existance interval 2 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance VI_1 { interface eno16777736 # interface to monitor state BACKUP virtual_router_id 51 # Assign one ID for this route priority 101 # 101 on master, 100 on backup authentication { auth_type PASS auth_pass 1111 # put in your own numeric password here (In this example it's 1111) } virtual_ipaddress { 192.168.146.100 # the virtual IP } track_script { chk_haproxy } }
Dalej możemy uruchomić na obu maszynach serwis keepalived
systemctl enable haproxy.service
Po uruchomieniu możemy zweryfikować interfejsy sieciowe. Przypominam o wprowadzeniu odpowiedniej nazwy interfejsu sieciowego.
ip a | grep -e inet.*eno16777736
Na maszynie H1 pełniącej rolę MASTER powinniśmy dostać wynik
inet 192.168.146.133/24 brd 192.168.146.255 scope global eno16777736 inet 192.168.146.100/32 scope global eno16777736
Na maszynie H2 pełniącej rolę BACKUP powinniśmy dostać wynik
inet 192.168.146.134/24 brd 192.168.146.255 scope global eno16777736
Oznacza to, że obecnie na maszynie H1 mamy wystawione IP wirtualne 192.168.146.100
Po powyższych operacjach możemy wylistować porty na jakich maszyny H1 oraz H2 nasłuchują
netstat -tulpn
W wyniku powyższej komendy na maszynach H1 oarz H2 powinniśmy otrzymać wynik jak poniżej. Z wyniku usunąłem nieistotne w naszym przypadku rekordy.
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:9090 0.0.0.0:* LISTEN 448/haproxy tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN 448/haproxy tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 448/haproxy ...
Konfiguracja HTTP
Na obu maszynach WWW1 oraz WWW2 musimy zainstalować pakiet httpd. Jest to serwer Apache odpowiedzialny w naszym przypadku za serwowanie treści statycznej.
yum -y install httpd
Plik konfiguracyjny modułu proxy http znajduje się w lokalizacji /etc/httpd/conf.modules.d/00-proxy.conf
W pliku tym najważniejsze parametry to :
- Proxy : Sekcja opisująca usługę proxy
- BalancerMember : Parametry serwerów na które będzie przekierowywany ruch.
- ProxyPass : Określa jakie URLe przychodzące do serwera WWW1 lub WWW2 będą przekierowywane do serwera Tomcat.
Na obu maszynach WWW1 oraz WWW2 plik ten powinien mieć postać:
# This file configures all the proxy modules: LoadModule proxy_module modules/mod_proxy.so LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so LoadModule proxy_ajp_module modules/mod_proxy_ajp.so LoadModule proxy_balancer_module modules/mod_proxy_balancer.so LoadModule proxy_connect_module modules/mod_proxy_connect.so LoadModule proxy_express_module modules/mod_proxy_express.so LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so LoadModule proxy_ftp_module modules/mod_proxy_ftp.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_scgi_module modules/mod_proxy_scgi.so <Proxy balancer://testcluster stickysession=JSESSIONID> #<Proxy balancer://testcluster> BalancerMember ajp://192.168.146.131:8009 min=10 max=100 route=node1 loadfactor=1 BalancerMember ajp://192.168.146.132:8009 min=20 max=200 route=node2 loadfactor=1 </Proxy> ProxyPass /app balancer://testcluster/ #bez poniższego każdy request to nowa sesja ProxyPassReverseCookiePath / /app <Location /balancer-manager> SetHandler balancer-manager </Location>
Po aktualizacji konfiguracji możemy włączyć (aby uruchamiał się automatycznie przy starcie systemu) i uruchomić serwis httpd
systemctl enable httpd.service systemctl start httpd.service
Konfiguracja Tomcat
W naszym laboratoryjnym przypadku serwis httpd oraz tomcat znajdują się na jednej maszynie. W środowiskach produkcyjnych wskazane byłoby aby je rozdzielić na różne maszyny.
Na obu maszynach WWW1 oraz WWW2 należy zainstalować pakiet tomcat. Jest to serwer Tomcat odpowiedzialny w naszym przypadku za serwowanie treści dynamicznej i co ważniejsze za współdzielenie sesji pomiędzy nodami.
yum -y install tomcat tomcat-admin-webapps tomcat-webapps
Plik konfiguracyjny serwera tomcat znajduje się w lokalizacji /etc/tomcat/server.xml Na obu serwerach WWW1 oraz WWW2 modyfikujemy sekcje Engine ustawiając nazwę silnika.
Na serwerze WWW1 powinna ona mieć postać:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
Na serwerze WWW2 powinna ona mieć postać:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node2">
Celem rozłożenia sesji pomiędzy nody w konfiguracji powinna zostać dodana sekcja Cluster definiująca klaster tomcat odpowiedzialny za przechowywanie sesji na wielu nodach. Rozróżniamy dwa rodzaje klastrów Tomcat. Klaster dynamiczny jest najprostszym rozwiązaniem , gdzie następuje automatyczna komunikacji pomiędzy nodami klastra za pomocą komunikacji multicastowej. Jeśli z jakiś przyczyn taka komunikacja z różnych przyczyn nie może być włączona należy zastosować klaster statyczny, gdzie w konfiguracji każdego noda musimy z góry zdefiniować adresy IP pozostałych członków klastra.
Tomcat - Klaster dynamiczny
Jeśli chcemy zdefiniować klaster dynamiczny do konfiguracji tomcat na obu nodach dodajemy poniższy wpis:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8"> <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autoBind="100" selectorTimeout="5000" maxThreads="6"/> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/> <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/> <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/" deployDir="/tmp/war-deploy/" watchDir="/tmp/war-listen/" watchEnabled="false"/> <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster>
Tomcat - Klaster statyczny
Jeśli chcemy zdefiniować klaster statyczny do konfiguracji tomcat na poszczególnych nodach dodajemy odpowiednią sekcje Cluster . W sekcji najważniejsze parametry to :
- Receiver -> address : adres IP danego noda
- Member : sekcja opisująca parametry członka klastra. Występuje tyle razy ile jest nodów w klastrze.
Na serwerze WWW1 wpis ten powinien mieć postać:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8" channelStartOptions="3"> <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="192.168.146.131" port="4110" autoBind="9" selectorTimeout="5000" maxThreads="6"/> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor"> <!--Member className="org.apache.catalina.tribes.membership.StaticMember" port="4110" host="192.168.146.131" domain="delta-static" uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}" /--> <Member className="org.apache.catalina.tribes.membership.StaticMember" port="4210" host="192.168.146.132" domain="delta-static" uniqueId="{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0}" /> </Interceptor> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/> <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/> <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster>
Na serwerze WWW2 wpis ten powinien mieć postać:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8" channelStartOptions="3"> <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="192.168.146.132" port="4210" autoBind="9" selectorTimeout="5000" maxThreads="6"/> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor"> <Member className="org.apache.catalina.tribes.membership.StaticMember" port="4110" host="192.168.146.131" domain="delta-static" uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}" /> <!-- <Member className="org.apache.catalina.tribes.membership.StaticMember" port="4210" host="192.168.146.132" domain="delta-static" uniqueId="{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0}" /> --> </Interceptor> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/> <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/> <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster>
Aplikacja testowa
Każda aplikacja webowa, która ma wykorzystywać współdzielenie sesji w ramach klastra w pliku web.xml wewnątrz tagu web-app musi posiadać wpis:
<distributable />
Celem przetestowania rozwiązania przygotowałem prostą aplikację zawierającą tylko jeden plik JSP. Zadaniem aplikacji jest wyświetlenie identyfikatora sesji , informacji czy sesja jest nowa oraz wartości atrybutu sesyjnego count . Wartość ta będzie przy każdym wywołaniu testowej stronie zwiększana co pozwoli zwerfikować poprawność działania rozwiązania.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <% // int name = request.getParameter( "username" ); String countString= (String )session.getAttribute( "count" ) ; int countInt=0; if (countString!=null) countInt=Integer.parseInt(countString); countInt++; countString=String.valueOf(countInt); session.setAttribute( "count", countString ); %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> HostName=<%= request.getServerName() %><br> Sessionid=<%= session.getId() %><br> Sessionid is new=<%= session.isNew() %><br> Count= <%= countString %> </body> </html>
Po zainstalowaniu aplikacji na obu nodach WWW1 oraz WWW2 zakładając, że nazwa aplikacji to WebSessionTester, możemy przetestować aplikację wywołując stronę:
http://192.168.146.100/app/WebSessionTester/ powrót
Komentarze
2021-10-12 16:19:41
2021-10-07 10:31:49
Dodaj Komentarz
Newsletter
Jeżeli chcesz być na bieżąco informowany o aktualnościach i poradach IT zapisz się do naszego newslettera.