고객사 인프라 구성이 바뀌면서 WAS가 여러 대가 됐고 세션을 WAS끼리 나눠 쓸 방법이 필요해졌다. 세션 클러스터링 자체는 어려운 작업이 아니다. 조건이 문제였다. Spring Framework 버전이 낮았고 세션을 담을 AWS ElastiCache for Redis에는 저장 중 암호화와 전송 중 암호화가 모두 켜져 있었다. 이 조합에서는 흔히 알려진 방법만으로는 세션 클러스터링이 동작하지 않았다.
시행착오는 많았지만 이 글에서는 최종적으로 동작한 설정만 정리했다.
작업 환경
AWS ElastiCache for Redis 포트:
6480저장 중 암호화, 전송 중 암호화: 모두 활성화
Spring Framework:
4.3.7(※ 스프링프레임워크 최신 버전에서는 더 쉬운 세션 클러스터링 방법이 있다..)
세션은 애플리케이션이 아니라 Tomcat에서 가로챈다
고른 방법은 Redisson의 Tomcat Session Manager다. Tomcat은 세션 저장을 Manager라는 컴포넌트에 맡기는데, 이 Manager를 Redisson 구현체로 바꾸면 세션이 WAS 메모리 대신 Redis에 저장된다. 세션 저장소를 컨테이너 수준에서 바꾸기 때문에 애플리케이션 코드와 Spring 버전은 건드리지 않는다. 낮은 Spring 버전을 그대로 둬야 하는 상황에서 이 점이 맞았다.
graph LR U[사용자] --> LB[Load Balancer] LB --> W1[WAS 1<br/>RedissonSessionManager] LB --> W2[WAS 2<br/>RedissonSessionManager] W1 -- "rediss:// (TLS)" --> R[(ElastiCache for Redis<br/>:6480)] W2 -- "rediss:// (TLS)" --> R
암호화 설정 중 클라이언트 설정에 영향을 주는 쪽은 전송 중 암호화다. 저장 중 암호화는 ElastiCache가 디스크에 쓸 때 처리하는 일이라 클라이언트는 신경 쓸 게 없다. 반면 전송 중 암호화가 켜지면 TLS로만 접속할 수 있어서 클라이언트가 평문 redis://로 붙으면 연결이 되지 않는다. 뒤에 나오는 redisson.yaml의 SSL 설정이 이 때문에 필요하다.
1. Redisson jar를 Tomcat lib에 넣는다
Redisson Tomcat README에서 두 파일을 받는다.
redisson-all-3.22.1.jarredisson-tomcat-8-3.22.1.jar
받은 파일을 설치된 Tomcat의 lib 폴더로 옮긴다. redisson-tomcat-8은 Tomcat 8용이므로 Tomcat 버전이 다르면 그에 맞는 jar를 받아야 한다.
2. context.xml에 Session Manager를 지정한다
{tomcat_base}/context.xml의 Context 태그 안에 넣는다. Redisson README는 Manager를 붙이는 방법을 두 가지로 안내한다. 하나는 Manager가 configPath로 설정 파일을 직접 읽는 방식이고 다른 하나는 JNDI에 등록한 RedissonClient를 Manager가 가져다 쓰는 방식이다.
<!-- 방법 A: Manager가 redisson.yaml을 직접 읽는다 -->
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/conf/redisson.yaml" readMode="REDIS"/>
<!-- 방법 B: server.xml에 등록한 JNDI 리소스(bean/redisson)를 가져다 쓴다 -->
<ResourceLink name="bean/redisson" global="bean/redisson"
type="org.redisson.api.RedissonClient" />
<Manager className="org.redisson.tomcat.JndiRedissonSessionManager"
readMode="REDIS"
jndiName="bean/redisson" />
두 방법은 하나만 고른다. Context 하나에 Manager가 둘 있으면 둘 중 하나만 적용된다. 아래 3단계의 server.xml 설정은 방법 B에 필요한 것이다.
readMode="REDIS"는 세션 속성을 요청마다 Redis에서 읽는다는 뜻이다. README에 따르면 다른 값인 MEMORY는 로컬 세션에도 속성을 두고 Redis와 동기화한다.
3. server.xml에 JNDI 리소스를 등록한다
{tomcat_base}/server.xml의 GlobalNamingResources 태그 안에 넣는다. 방법 B의 bean/redisson이 여기서 만들어진다.
<Resource name="bean/redisson"
auth="Container"
factory="org.redisson.JndiRedissonFactory"
configPath="${catalina.base}/conf/redisson.yaml"
closeMethod="shutdown"/>
4. redisson.yaml에서 TLS로 접속하게 한다
{tomcat_base}/conf/redisson.yaml을 새로 만든다. 핵심은 주석을 단 두 곳, 주소의 rediss:// 스키마와 SSL 설정이다.
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 10000
retryAttempts: 20
retryInterval: 2000
password: null
subscriptionsPerConnection: 5
clientName: null
# "rediss://" 스키마 사용 (s가 하나 더 붙으면 TLS 접속)
address: "rediss://{생성된 AWS ElastiCache for Redis의 Endpoint}:6480"
# SSL 설정 추가
sslEnableEndpointIdentification: false
sslProvider: "JDK"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 2
dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.Kryo5Codec> {}
transportMode: "NIO"
sslEnableEndpointIdentification: false는 인증서의 호스트 이름 검증을 끈다. 접속은 편해지지만 검증 하나를 포기하는 설정이라, 가능하면 켠 상태로 붙는지 한 번 확인해 볼 만하다.
각 WAS에 같은 설정을 마친 뒤 재시작하면 세션 클러스터링이 동작한다.
정리
Spring 버전을 올릴 수 없는 상황이라면 세션 문제는 애플리케이션 밖, Tomcat의 Manager에서 푸는 편이 빠르다. 암호화된 ElastiCache에 붙을 때는 rediss://와 SSL 설정 두 줄만 기억하면 된다. 시행착오는 많았는데, 남은 설정은 결국 이 정도다.
참고자료
Redisson Tomcat Session Manager README — jar 다운로드와 Manager 설정 두 가지 방법,
readMode설명ElastiCache in-transit encryption (TLS) — 전송 중 암호화가 켜진 클러스터에 TLS로 접속해야 하는 이유