Apache/Tomcat 세션 유지 및 세션 클러스터링 구성
안녕하세요. MSP 사업부 김병학 입니다.
이 글은 Apache/Tomcat 세션 유지 및 세션 클러스터링 구성, AJP 연동 구성을 테스트한 내용을 기반으로 작성한 매뉴얼입니다. 구성 과정에서 제가 실제로 겪었던 문제들과 그 해결방법까지 포함되어 있어, 비슷한 환경에서 클러스터링을 시도하시는 분들께 도움이 되실 겁니다.
☑️ 개요 및 구성 목표
JSP(Java Server Page) 기반의 웹 서버의 경우 클라이언트가 접속 시 사용자에게 쿠키 형태의 JSESSION을 발급합니다. 발급 이후 WAS에서는 JSESSION의 만료 시간까지 이를 보관하고 있다가, 동일한 JSESSION을 가진 사용자가 다시 접속하면 새로운 세션을 생성하지 않고 기존의 연결을 유지시켜주죠. 이 덕분에 클라이언트는 로그인이 유지되거나, 쇼핑몰 장바구니가 그대로 남아있는 등 서비스의 연속성을 자연스럽게 경험할 수 있습니다.
대부분의 인프라 관리자는 인프라 장애가 발생하지 않도록 끊임없이 고민합니다. 가장 먼저 떠올릴 수 있는 대응은 ‘이중화’를 통한 고가용성(HA) 구성입니다. 흔히들 WEB/WAS를 2대 이상 구성하고 로드밸런서(L4 or L7)의 알고리즘과 함께 CSP에서 제공하는 세션 유지 기능을 활용하려 하지만… 막상 실무에서는 그렇게 간단하게 되지 않습니다.
왜냐하면, 클라이언트가 하나의 노드에만 접속해 있을 때는 문제가 없지만, 서버 장애가 발생해 다른 노드로 연결되는 순간, 사용자의 세션 정보는 사라져버리기 때문입니다. 이때부터 문제가 시작되죠. 질문 하나 드려볼게요.
“사용자 세션 정보를 모든 WAS 노드가 공유하도록 구성할 수 있다면?”
이건 단순한 고가용성을 넘어, 사용자 경험의 일관성을 유지하는 중요한 아이디어가 됩니다.
그렇다면 어떻게 구성해야 할까요? 어떤 설정이 필요할까요? 대부분의 WAS 솔루션에서는 이런 기능을 이미 제공중입니다.
그중에서 오늘은, Tomcat에서 사용할 수 있는 가장 쉬운 방법인 DeltaManager, SimpleTcpCluster, 그리고 mod_jk를 활용한 AJP 기반의 통신 방식을 작성하고자 합니다.
☑️ 사전 필요 지식
모든 내용을 상세히 적고싶지만, 오늘은 주제가 정해져있으니 그러지는 않겠습니다.
아래 내용들을 이미 어느정도 편하게 다루시는 분들을 대상으로 작성하였으니 참고 부탁드립니다.
✅ Linux 서버 기초 지식
– 리눅스 인프라를 기준으로 설명드리니, 리눅스 지식은 선행되어야 합니다.
✅ 네트워크 기초 지식
– TCP/IP 통신 개념
– 로드밸런서의 기초적인 개념
✅ Apache / Tomcat 기초 지식
– Apache / Tomcat 설치 경험 有
( ※ Apache / Tomcat 구조의 인프라를 운영해보셨다면 매우 쉽게 읽으실 수 있습니다.)
☑️ 시스템 구성 개요
구성요소는 아래와 같습니다.
✅ Load Balancer(L4)
– WEB 서버 상단에서 클라언트의 접속을 분기시켜 균등하게 트래픽이 흐르도록 합니다.
✅ WEB(Apache) 2대
– 정적페이지 출력을 위해 필요로 합니다.
– mod_jk 모듈을 설치하여 WAS 와 AJP 통신이 가능하도록 설정합니다.
✅ WAS(Tomcat) 2대
– 동적페이지 출력을 위해 필요로 합니다.
– Tomcat 이 설치되며, 클러스터 구성할 예정입니다.
시스템의 구성은 간단합니다. 이제 본 글을 시작하겠습니다.
Apache/Tomcat 세션 유지 (Sticky Session)
서두에 설명하였듯이 Tomcat을 여러 대 두고 Apache나 Load Balancer를 통해 분산하는 구조에서 동일한 세션을 유지시키도록 설정하기위해, 세션 유지 (Sticky Session) 을 고려합니다.
해당 파트에서는 세션 유지 (Sticky Session) 구성에 필요한 설정을 단계별로 정리하고, 실제 테스트를 통해 세션이 유지되는지를 확인해보겠습니다.
(세션 복제, 즉 클러스터링은 다음 파트에서 다룹니다.)
1️⃣ Apache mod_jk 설치 및 Load
Apache HTTP Server와 Tomcat 사이에서 AJP 프로토콜을 통해 통신하기 위해 mod_jk 모듈이 필요합니다. 아래 내용대로 설치 및 LoadModule 을 통해서 Apache 에 적용 합니다.
📌 설치
mod_jk를 설치합니다. 예시는 CentOS 패키치 설치 기준입니다.
yum install mod_jk
📌 Apache에 모듈 로드 설정 추가
/etc/httpd/conf.modules.d/00-jk.conf 또는 메인 설정 파일(httpd.conf)에 다음과 같이 작성합니다.
LoadModule jk_module modules/mod_jk.so <IfModule mod_jk.c> JkWorkersFile conf/workers.properties JkLogFile logs/mod_jk.log JkLogLevel info JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " # JkMountFile conf/uriworkermap.properties </IfModule>
위 설정에서 가장 중요한것은 JkWorkersFile conf/workers.properties 설정입니다.
이는 workers.properties의 설정을 통해서 WEB/WAS 간의 트래픽 처리 방식을 규정하기 때문입니다.
uriworkermap.properties 파일도 필요에 따라 사용하기 유용합니다.
2️⃣ Apache workers.properties 작성
Apache와 Tomcat 간 연결할 대상 노드를 설정하는 구성 파일입니다. 일반적으로 /etc/httpd/conf/workers.properties 혹은 /etc/httpd/conf.d/workers.properties 경로에 작성합니다.
worker.list=loadbalancer worker.node1.type=ajp13 worker.node1.host=10.1.0.84 worker.node1.port=8009 worker.node1.lbfactor=1 worker.node2.type=ajp13 worker.node2.host=10.1.0.99 worker.node2.port=8009 worker.node2.lbfactor=1 worker.loadbalancer.type=lb worker.loadbalancer.balance_workers=node1,node2 worker.loadbalancer.sticky_session=true
🔸여기서 핵심은 sticky_session=true 설정입니다.
클라이언트가 최초 연결한 노드(node1=was1 / node2=was2) 와 이후에도 게속 통신하도록 고정해주는 역할을 합니다.
3️⃣ tomcat AJP/1.3 커넥터 활성화
tomcat server.xml 파일에서 AJP 커넥터를 활성화합니다. 대부분의 경우 기본적으로 주석 처리되어 있기 때문에 열어주고 필요한 속성을 명시해야 합니다.
대부분의 경우 tomcat 의 conf 디렉토리에 server.xml 이 위치합니다.($CATALINA_HOME/conf/server.xml)
<!-- Service 태그 안쪽에 작성 --> <Connector protocol="AJP/1.3" address="0.0.0.0" port="8009" redirectPort="8443" secretRequired="false" />
🔸주의할 점은 Tomcat 9부터 secretRequired=”true”가 기본값이므로 반드시 명시적으로 false로 설정해야 합니다.
💡 NOTE ※ 참고 링크 |
그리고 꼭 Engine 태그에 jvmRoute를 지정해줘야 sticky session이 동작하니 이부분도 수정 합니다.
<!-- WAS01 은 node1 로 설정 --> <Engine name="Catalina" defaultHost="localhost" jvmRoute="node1"> <!-- WAS02 은 node2 로 설정 --> <Engine name="Catalina" defaultHost="localhost" jvmRoute="node2">
🔸여기서 설정한 jvmRoute 는 웹서버에서 설정한 worker.loadbalancer.balance_workers=node1,node2 값과 동일해야합니다.
4️⃣ Apache vhost 설정
이부분에서는 크게 수정할 내용이 없습니다. JkMount 옵션 를 추가하면 되는데요, 아래 예시 파일을 공유 합니다.
<VirtualHost *:80> ServerName test.kimbh.kr ServerAlias test.kimbh.kr ProxyRequests Off ProxyPreserveHost On ProxyVia On ErrorDocument 503 /error/503.html ProxyPass /error/ ! JkMount /* loadbalancer </VirtualHost>
JkMount /* loadbalancer 옵션을 통해, 이제 위에서 작성했던 workers.properties 의 설정을 따라가게 됩니다.
5️⃣세션 유지 테스트
모든 설정을 마무리한 후 WEB/WAS 서비스를 재시작하면 설정은 마무리 되었습니다. 이제 설정이 정상적으로 적용되었는지, 그리고 세션이 유지가 되는지 확인해보겠습니다.
아래는 페이지 출력을 위해서 만든 예시 코드입니다.
<%@ page import="java.io.ByteArrayOutputStream, java.io.ObjectOutputStream" %> <%@ page contentType="text/html; charset=utf-8" %> <% Integer ival = (Integer)session.getAttribute("_session_counter"); if (ival == null) { ival = new Integer(1); } else { ival = new Integer(ival.intValue() + 1); } session.setAttribute("_session_counter", ival); StringBuilder resultLog = new StringBuilder(); resultLog.append("Session ID : ").append(session.getId()).append("<br>"); resultLog.append("Session Object: _session_counter = ").append(session.getAttribute("_session_counter")).append("<br>"); resultLog.append("Session isNew: ").append(session.isNew()).append("<br>"); try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(bos); objOut.writeObject(session.getAttribute("_session_counter")); objOut.flush(); objOut.close(); resultLog.append("✅ Serializable 테스트: 세션 객체 직렬화 성공<br>"); } catch (Exception e) { resultLog.append("❌ Serializable 실패: ").append(e.getMessage()).append("<br>"); } %> <html> <head><title>Session Test</title></head> <body> <h2>Tomcat Session Clustering Test</h2> <p><%= resultLog.toString() %></p> <p><a href="test.jsp">[새로고침]</a></p> </body> </html>
위 소스 코드를 tomcat/webapps/ROOT/test.jsp (기본 소스 디렉토리 위치) 에 생성후
http://<서버IP 또는 도메인>/test.jsp 를 열어보면 아래와 같이 출력됩니다.
Session ID가 변경되지 않고, Session Counter의 숫자가 증가하며 처음 접속 시에는 세션이 생성(true)되었지만, 이후 새로고침 시에는 생성되지 않았음(false)을 확인할 수 있습니다.
이후 서버에서 tcpdump를 확인해보면, LB를 라운드로빈 방식으로 설정했을 경우 클라이언트는 WEB01과 WEB02를 번갈아 가면서 접속하지만, WAS에서는 최초 연결된 WAS로만 트래픽이 흐르는 것을 확인할 수 있습니다.
※ tcpdump 추천 명령어 tcpdump -nni any port 80 or 443 |
여기까지 성공적으로 따라왔다면, 세션유지(Sticky Session) 는 성공적으로 구성되었습니다.
Tomcat 세션 공유(세션 클러스터링) 구성
세션 클러스터링을 활용하기 위해서는 DeltaManager 클래스를 사용합니다. 이 방식은 각 WAS 간 세션 정보를 복제하여 유지함으로써, 어느 노드에 클라이언트 요청이 도달하더라도 동일한 세션 상태를 유지할 수 있게 해줍니다.
💡 DeltaManager란? DeltaManager는 Tomcat의 세션 클러스터링 방식 중 하나로, 전체 세션 데이터를 복제하지 않고 변경된 부분만 전송하는 차등 복제 방식을 사용합니다. 이로 인해 성능에 부담을 덜 주면서도 실시간 세션 동기화가 가능하다는 장점이 있습니다. (단, 완벽한 일관성이 필요한 경우에는 Full replication 기반인 BackupManager를 고려해야 합니다.) |
1️⃣ server.xml 설정
<Engine> 태그 내부에 jvmRoute를 설정하고, <Cluster> 구성 내용을 작성합니다.
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1"> <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.StaticMembershipService"> <Member className="org.apache.catalina.tribes.membership.StaticMember" port="4000" host="10.1.0.84" domain="tomcat-cluster" uniqueId="{0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2}" /> <Member className="org.apache.catalina.tribes.membership.StaticMember" port="4001" host="10.1.0.99" domain="tomcat-cluster" uniqueId="{0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3}" /> </Membership> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" /> </Sender> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="10.1.0.84" port="4000" autoBind="true" maxThreads="6" selectorTimeout="5000" /> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter="" /> </Cluster> </Engine>
🔸주의할 점은 각 서버에서 Receiver의 address와 port가 자기 자신의 IP와 포트여야 한다는 것입니다. 상대 노드의 정보가 아닌, 본인이 수신할 설정을 작성해야 합니다. 또한 Receiver 의 port 는 각 WAS 마다 달라야 함으로, 참고 바랍니다.
🔸필자는 WAS01 은 4000, WAS02는 4001로 설정했습니다.
💡 NOTE 링크 : https://stackoverflow.com/questions/18835014/tomcats-clustering-session-replication-not-replicating-properly |
2️⃣ web.xml 설정
세션 복제를 허용하기 위해, web.xml에 <distributable/> 태그를 추가합니다. 이 태그는 해당 WAR 내에서 생성된 세션이 클러스터링을 통해 복제될 수 있음을 명시합니다.
<web-app> <distributable/> </web-app>
web.xml은 애플리케이션의 WEB-INF/web.xml 위치에 존재해야 하며, Tomcat 전역 설정 web.xml 로는 적용되지 않습니다.
📦 디렉토리 구성 예시 ./tomcat/webapps/ROOT/ |
이후 tomcat 을 재시작하면 적용은 모두 마무리되었습니다. 여기까지 모두 따라오셨다면, 아래 시나리오대로 테스트를 해보면서 문제가 있는지 확인해보면 됩니다.
🧪 테스트 방법
1. 브라우저로 http://<Apache IP>/test.jsp 접속
2. 새로고침으로 세션 증가 확인
3. 현재 접속 중인 WAS를 강제로 중단
4. 다시 새로고침 → 이전에 유지되던 세션 정보 그대로 유지되어야 정상
💡 NOTE 만일, 정상적으로 작동하지 않는다면 로깅 설정을 추가하여 디버깅하면 도움이 많이 됩니다. org.apache.catalina.tribes.level = FINE 테스트 및 구축 단계에서는 FINE 으로 문제점을 찾고, 이후로는 INFO 정도로 로깅 레벨을 올려서 운영한다면, 보다 효율적일겁니다. |
이로써 오늘 글은 마무리되었습니다. 모쪼록 구성하시는 분들에게 도움이 되었으면 좋겠습니다.
항상 글을 쓰면서, 많은 내용을 적고싶은데 필력이 부족하여 작성 못하는것들이 많아 아쉽습니다. 구성중 어려운 부분 있으시면, 댓글 남겨주세요.