프로젝트를 진행하면서 게이트웨이를 구현하는 역할을 담당하였습니다. 

저희 팀은 JWT 인증 및 엔드포인트 별 권한 검증을 게이트웨이에서 구현하기로 결정했습니다.

그러다가 제가 생각했던 문제점과 문제 해결을 위해 시도한 방법, 그리고 그 방법의 문제점을 소개하고자 합니다. 

문제 상황: Spring Cloud Gateway의 재시동 문제

엔드포인트 별 권한 검증을 Gateway에서 하기로 했다. 따라서 Reactive Spring Security를 사용해서 필터 체인에 구현했다.

하지만, 각 서비스별 인가 정책이 바뀐다면 이를 반영하기 위해 Gateway의 코드를 수정하고, 재시동해야 하는 문제가 있다.
이러한 문제는 모든 엔드포인트의 라우팅을 담당하는 Gateway에 있어 좋지 않다고 판단했다.
이에 무중단으로 정책 업데이트를 반영할 수 있는 방법을 모색하였다.

그래서 생각해 낸 것이 Redis에 정책을 저장하고, 주기적으로 정책을 업데이트하는 관리 서버를 운영하는 것이다. 특히 성능적 이슈를 해결하기 위해서 Redisson의 Reactive Redisson 을 사용했다. Reactive Redisson은 Reactive Streams의 규칙을 따르는 API를 통해, Redis와의 통신을 논블로킹 방식으로 처리한다.

이러한 방식의 장점?

  1. 대용량 트래픽 처리 효율성: 처음에 게이트웨이에서 모든 인가 책임을 부여한 이유는 대규모 트래픽이 발생할 경우 불필요한 요청이 서비스까지 도달하지 않도록 차단하는 것이었다. 특히 트래픽이 몰리는 이벤트가 발생할 경우, 서비스에 도달하지 않아도 처리 불가한 요청을 미리 필터링하려는 의도였다.
  2. 인가의 중앙화: 각 서비스에서 권한 검증 로직을 별도로 구현하지 않아도 된다는 점도 장점이라 생각했다. 게이트웨이에서 인가를 중앙화함으로써 코드 중복을 방지하고, 각 서비스는 비즈니스 로직에만 집중할 수 있었다.

문제 정의

문제 정의 1: 성능적 이슈?

  • 중앙 집중화에 따른 성능 부담: 모든 인가(Authorization) 요청이 게이트웨이로 집중되면, 게이트웨이는 단순히 라우팅 하는 역할을 넘어 각 요청에 대해 인가 정책을 검증해야 하므로, 게이트웨이에 큰 부하가 걸릴 수 있다. 특히, 서비스의 확장성과 독립성을 강조하는 마이크로서비스 아키텍처에서는 각 서비스가 개별적으로 권한 검사를 수행하는 것이 더 효율적일 수 있다. 인가 로직을 게이트웨이에 집중시키면, 게이트웨이의 처리량이 대폭 증가해 트래픽이 몰리는 상황에서 병목 현상이 발생할 가능성이 크다.
  • 추가적인 네트워크 레이턴시: 인가 정책을 매번 Redis에서 조회하고 검증하는 작업은 네트워크 지연율을 증가시킬 수 있습니다. 아무리 Redis가 빠르다고 하더라도, 매 요청마다 추가적인 네트워크 I/O가 발생하게 되어 전체 요청 처리 시간이 증가할 수 있다. 특히, 대규모 트래픽이 발생할 경우, Redis로의 과도한 접근이 발생해 오히려 Redis 자체가 병목이 될 가능성도 배제할 수 없다.

문제 정의 2: 관리 포인트의 증가

  • 추가적인 인프라 관리: Redis 및 정책 서버가 추가되면서, 이를 안정적으로 운영하기 위한 인프라 관리 비용이 발생한다.
  • 정책 업데이트 과정의 복잡성: 인가 정책이 변경될 때마다 정책 서버에 새로운 정책을 반영하고 이를 Redis에 업데이트해야 하는 추가적인 절차가 필요하다. 각 서비스가 자체적으로 인가 로직을 관리하는 방식에서는 서비스 업데이트만으로 해결할 수 있지만, 이 방식에서는 정책을 관리 서버에서 먼저 반영하고 Redis로 동기화하는 과정을 거쳐야 한다. 이로 인해 정책 변경 주기가 빨라질수록 운영상의 복잡성이 증가한다.
  • 정책 동기화 및 일관성 유지 문제: Redis에서 정책을 주기적으로 업데이트할 때, 게이트웨이에서 이를 실시간으로 반영할 수 있도록 해야 한다. 만약 동기화 주기가 길거나 동기화 과정에서 문제가 발생하면, 오래된 정책을 사용해 인가를 수행하게 되어 보안상의 문제가 발생할 수 다. 이를 해결하기 위해 동기화 간격을 적절히 설정하고, 실시간 모니터링을 강화해야 하지만, 이는 결국 운영 복잡성을 증가시키는 요인이 된다.

문제 정의 3: Redis의 장애 이슈

  • Redis 장애로 인한 전체 서비스 중단 위험: 게이트웨이가 Redis에 종속되면서, Redis 장애는 곧 인가 정책 조회 불가로 이어져 게이트웨이에서 모든 인가 요청이 실패하게 된다. 이는 결과적으로 전체 서비스의 요청 처리가 중단되는 심각한 상황을 초래할 수 있다. Redis 장애에 대한 대비책이 없다면, 게이트웨이가 오프라인 상태로 전환되거나 서비스 전체를 스톱시켜야 하는 상황에 놓일 수 있다. 특히, Redis가 외부 클라우드 제공업체에서 관리되는 경우(AWS, Azure 등) 사용자 측에서 장애를 완전히 통제하기 어려워 그 영향은 더욱 심각해질 수 있다.
  • Pub/Sub 구조로의 대안 제시: Redis의 Pub/Sub 기능을 활용하여 게이트웨이가 정책을 구독하고 이를 유지하는 구조를 도입하는 방법이 있다. 이 방식에서는 Redis가 인가 정책을 Pub/Sub 채널을 통해 전송하고, 게이트웨이가 이를 구독하여 정책을 실시간으로 업데이트받을 수 있다. 하지만 게이트웨이가 재시작되면 문제가 생긴다. 게이트웨이 자체는 휘발성 메모리를 사용하기 때문에, 재시작 시 구독하던 정책이 모두 유실되어 다시 Redis와 연결되기 전까지 정책이 없는 상태가 된다. 즉, Pub/Sub 구조만으로는 이 문제를 완전히 해결할 수 없다.

문제 해결 방안

Redis 고가용성 확보

  • Redis Sentinel: Redis Sentinel은 Redis 인스턴스를 모니터링하고, 장애가 발생하면 자동으로 마스터-슬레이브 전환을 수행하여 다운타임을 최소화할 수 있는 고가용성 설루션이다. Sentinel은 Redis 인스턴스의 상태를 지속적으로 감시하고, 마스터 노드에 장애가 발생할 경우 슬레이브 노드 중 하나를 마스터로 승격시켜 서비스를 유지한다. 이를 통해 Redis 장애 시에도 정책 조회 및 업데이트 작업을 중단 없이 수행할 수 있다.
  • Redis Cluster: Redis Cluster는 데이터가 여러 노드에 분산 저장되어 있으며, 노드 간 자동 페일오버 기능을 제공하여 노드 하나가 장애를 일으켜도 다른 노드들이 정상적으로 동작하도록 지원한다. 클러스터 구조에서는 데이터를 샤딩(Sharding)하여 분산 처리하기 때문에, 트래픽이 많은 상황에서도 성능을 안정적으로 유지할 수 있다. 

정책 서버를 두지 않고 Config 서버 또는 DB로 정책 관리

  • Config 서버를 활용한 정책 관리: Spring Cloud Config Server는 분산 시스템에서 설정 데이터를 중앙에서 관리할 수 있도록 도와주는 솔루션이다. 정책 서버 대신 Config 서버를 사용하면 인가 정책을 중앙화하여 관리할 수 있으며, 정책이 변경될 때마다 서비스들이 실시간으로 이를 반영할 수 있다. Config 서버는 Git, SVN 등 다양한 소스 저장소와 통합할 수 있으며, 이를 통해 정책을 소스 코드 관리 시스템에서 직접 관리할 수 있다. 정책 변경 시 Config 서버가 이를 모든 게이트웨이에 즉시 배포하고 반영할 수 있으므로, Redis 장애로 인한 문제를 해결할 수 있다.
  • 데이터베이스를 통한 정책 관리: 인가 정책을 데이터베이스(DB)에 저장하고 관리하는 방식도 대안이 될 수 있다. 관계형 데이터베이스나 NoSQL 데이터베이스를 사용하여 정책 데이터를 저장하고, 게이트웨이가 정책을 조회하여 사용하는 방식이다. 이 방식에서는 Redis처럼 메모리 기반 스토리지의 장애 위험을 피할 수 있으며, 데이터베이스는 복구성과 안정성이 뛰어나므로 고가용성을 보장할 수 있다.

그냥 서비스 별로 권한 체크 하기

게이트웨이에서 헤더에 `UserRole`과 같은 권한을 나타내는 값을 추가하고 각 서비스에서 이를 받아 검증하는 방식으로 한다면 위 고려사항들이 다 필요 없긴 하다.

Redis가 고장이 나도 서비스는 잘 되어야 한다.

Redis는 인메모리 데이터 저장소이기 때문에 휘발성이므로 시스템 장애나 Redis 인스턴스가 고장이 나면 그 메모리에 있던 데이터는 손실될 수 있다. 이 특성으로 인해 Redis는 데이터를 영구적으로 저장하는 용도로는 적합하지 않다.

그래서 Redis가 고장 나더라도 서비스는 데이터베이스나 다른 영구 저장소를 통해 계속 운영될 수 있도록 설계해야 한다.

 

Redis는 캐시 역할이나 성능 개선을 위한 보조적 도구로 사용하고, 핵심 비즈니스 로직이 Redis에 지나치게 의존하지 않도록 설계하는 것이 바람직하다.

결론

사실 게이트웨이에 인증/인가를 모두 수행하는 것은 아래 영상에서 영감을 받았다.

토스ㅣSLASH 23 - 토스는 Gateway 이렇게 씁니다 (youtube.com)

초반부에 게이트웨이를 하나의 모놀리스로 운영한다는 말에 영감을 받아, 나도 한 번 구현해 보았다. 

사실 정책 관리를 이렇게 하진 않겠지만, 나름 무중단을 위해 고민해보고, 또 튜터님들께 여러 의견을 들어서 Redis의 의존하는 기능은 지양하고, 장애 발생 시의 대처도 꼼꼼히 해야된다는 것을 느꼈다.