고객사 인프라 구성이 바뀌면서 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.jar

  • redisson-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 설정 두 줄만 기억하면 된다. 시행착오는 많았는데, 남은 설정은 결국 이 정도다.

참고자료