쿠버네티스(Kubernetes) 환경에서 스프링부트(Spring Boot) 애플리케이션을 배포하고, Pod을 2개로 설정했을 때 로그인 문제가 발생했습니다. 로그인을 해도 다시 로그인을 하게 되는 상황이였는데요, 스프링 시큐리티(Spring Security)를 사용할 때, 세션 정보가 한 Pod에만 저장되어 있기 때문에 로드 밸런싱으로 인해 사용자 요청이 다른 Pod로 전송될 때 세션 정보를 찾을 수 없어 로그인 문제가 발생한것으로 보입니다.
스프링 세션 + Redis를 사용하여 문제를 해결해보도록 하겠습니다.
Redis를 사용하여 세션을 공유하는 과정을 단계별로 설명하겠습니다. 이 과정은 스프링 부트와 스프링 세션(Spring Session) 라이브러리를 사용하여, 세션 정보를 중앙 집중식으로 관리하는 Redis 저장소에 저장함으로써, 여러 스프링 부트 인스턴스(또는 쿠버네티스 상의 Pod)가 같은 세션 정보를 공유할 수 있게 해줍니다.
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.session:spring-session-data-redis'
implementation 'org.springframework.data:spring-data-redis'
runtimeOnly 'redis.clients:jedis'
}
application.properties 또는 application.yml 파일에 Redis 서버의 호스트 이름과 포트 번호를 설정합니다. 이 설정을 통해 스프링 애플리케이션은 Redis 서버에 연결할 수 있습니다.
(session-redis 관련 yaml 파일 내용은 밑에 올리겠습니다)
spring.redis.host=session-redis
spring.redis.port=6379
스프링 세션을 활성화하기 위해 @EnableRedisHttpSession 애노테이션을 추가합니다. 이 애노테이션은 스프링 세션의 구성을 활성화하고, 세션을 Redis에 저장하도록 스프링 부트 애플리케이션을 구성합니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession
public class RedisConfig {
}
스프링 시큐리티(Spring Security) 설정에서는 특별한 변경이 필요 없습니다.
이렇게 설정해주면 잘 동작할까요??
오류발생!!
org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sjs.jsvill.entity.Member
java.io.NotSerializableException: com.sjs.jsvill.entity.Member라는 예외가 발생했습니다. 이 오류는 com.sjs.jsvill.entity.Member 클래스의 인스턴스를 Redis에 저장하려고 할 때, 해당 객체가 Java 직렬화 프로토콜을 따르지 않기 때문에 발생합니다. Redis에 객체를 저장하기 위해서는 해당 객체가 Serializable 인터페이스를 구현해야 합니다.
**serialVersionUID**는 직렬화된 객체의 버전 관리에 사용됩니다. 클래스가 Serializable 인터페이스를 구현한다면, 이 필드를 선언하는 것이 좋습니다. 클래스의 구조가 변경되어도, 같은 **serialVersionUID**를 가진 클래스로의 역직렬화가 가능합니다.
spring.redis.host=session-redis 설정처럼 구현하기 위해서는
Redis를 쿠버네티스 클러스터 내부에서 실행하려면, Redis 서버를 별도의 Pod로 배포하고, 이 Pod에 대한 서비스를 생성해야 합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: session-redis
spec:
replicas: 1
selector:
matchLabels:
app: session-redis
template:
metadata:
labels:
app: session-redis
spec:
containers:
- name: session-redis
image: redis:alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: session-redis
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: session-redis
작동 원리
- 사용자가 스프링 부트 애플리케이션에 로그인하면, 스프링 세션은 세션 정보를 Redis에 저장합니다.
- 사용자의 브라우저는 쿠키를 통해 세션 ID를 저장하고 이를 사용하여 요청을 인증합니다.
- 사용자 요청이 로드 밸런서를 통해 어떤 인스턴스로든 전달될 때, 인스턴스는 세션 ID를 통해 Redis에서 해당 세션 정보를 조회합니다.
- 이 방식을 통해, 여러 인스턴스 간에 세션 정보가 공유되므로, 사용자는 어느 인스턴스로 요청을 보내도 로그인 상태를 유지할 수 있습니다.
쿠버네티스 환경에서 여러 Pod가 하나의 Redis 세션을 참조하는 구조를 완성했습니다!
잘 되는지 확인해보기 위해 간단하게 redis-session pod의 로그를 살펴보겠습니다.
'> kubectl logs pod/session-redis-765dd67477-vskcz'
Redis가 정상적으로 작동하고 있으며, 주기적으로 데이터를 디스크에 저장하고 있는 상태임을 보여줍니다.