토스는 Gateway 이렇게 씁니다
안녕하세요. 토스에서 Gateway를 개발하고 있는 서버플랫폼팀 최준우입니다.
토스에서는 목적에 맞는 다양한 Gateway를 사용하고 있는데요. 저는 이번 글에서 이러한 Gateway 아키텍처를 통해 토스가 누리고 있는 장점들과 이를 위해 어떠한 노력을 하고 있는지에 대해 간단히 소개하려고 합니다.
Gateway 란?
우선 Gateway에 대해 간단히 알아보겠습니다. Gateway는 라우팅 및 프로토콜 변환을 담당하며 마이크로 서비스의 중개자 역할을 하는 서버입니다. 이를 통해 서비스는 클라이언트와 독립적으로 확장할 수 있으며 보안, 모니터링을 위한 단일 제어 지점을 제공합니다. Netflix Zuul을 통해 잘 알려졌으며 현재는 Saas나 플랫폼으로도 사용할 수 있게 대중화되었습니다.
그림을 통해 예를 들어 보겠습니다. 서비스가 적고 트래픽이 적다면 클라이언트에서 서비스를 직접 호출하고 각각의 서비스에서 모든 로직을 처리해도 큰 부담이 되지는 않습니다. 그러나 스케일이 커지면 공통의 로직을 모든 서버에 적용하고 배포하는 것도 큰일이 됩니다.
이러한 필요성을 위해 개발된 게 Gateway 패턴입니다. Gateway는 서버들에서 필요한 공통 로직을 통합하여 처리합니다. 모든 서비스에서 필요한 유저 정보, 보안 정책 등을 Gateway에서 처리하고 이를 업스트림 서버로 넘겨줍니다.
Gateway는 요청이 오면 정의된 설정에 따라 요청을 라우팅하고 사전에 설정된 필터들을 작동시킵니다. 설정은 Route 단위로 구성이 되며 Route는 다시 Predicate와 Filter로 구성됩니다. Predicate는 요청을 구분할 때 사용하는 값인데, Path, Method, Host 등으로 요청을 매칭하고 Filter는 매칭된 요청에 대한 전처리나 서비스의 응답에 대한 후처리를 구현합니다.
저희는 이러한 Gateway 들을 Spring Cloud Gateway를 사용하여 구성하고 개발하고 있습니다. Spring Cloud Gateway는 스프링 Webflux를 통해 구현되어 있으며 내부적으로 Reactor-Netty를 사용하여 비동기 처리를 지원하고 있습니다. 또한 필터 개발에 Kotlin Coroutine을 적극 활용하고 있으며 Istio의 Ingress / Egress Gateway 및 Envoy 필터와 함께 유기적으로 개발하고 있습니다.
공통 로직 처리
이제 앞에서 소개 드렸던 공통 로직들을 저희가 어떻게 개발하고 사용하고 있는지에 대해 간단히 소개해 드리겠습니다.
Gateway에서 공통 로직을 처리하는 부분은 크게
- Request에 대한 전처리, 후처리
- 유저 정보를 이용한 로직 수행
- 보안 그리고 서비스 안정화를 위한 설정
등이 있는데, 몇 가지 사례와 그림을 토대로 설명드리도록 하겠습니다.
Sanitize
우선 Request 처리입니다. Gateway에서 우선적으로 처리해 줘야 하는 것은 Request를 Sanitize 하는 것입니다. Sanitize는 Client로부터 올바르지 않은 요청이 올 경우 이를 지우거나 올바른 값으로 바꿔주는 것을 의미합니다. 그림처럼 사용자가 악의적으로 값을 주고 요청하더라도 Gateway에서 이를 올바른 값으로 바꿔서 서비스에 넘겨줍니다.
유저 Passport
토스 내부 서비스들도 기존에는 모든 서비스에서 유저 정보가 필요할 때 유저 API를 호출하는 방식으로 유저 정보를 가져오고 있었는데요 이는 트랜잭션 내에 불필요한 중복 요청을 유발하고 서버 리소스의 낭비로 이어졌습니다. 이를 개선하기 위해 저희는 Netflix의 Passport 구조를 참고하였습니다.
Netflix는 유저 인증 시에 Passport 라는 id 토큰을 트랜잭션 내로 전파하는 방법을 사용하고 있는데요. 저희는 Netflix의 Passport 구조를 저희 팀에 맞게 변경하여 토스 Passport를 구현했습니다.
Passport는 사용자 기기 정보와 유저 정보를 담은 하나의 토큰인데요. 앱에서 유저 식별키와 함께 API를 요청하게 되면 Gateway에서 이 키를 토대로 인증 서버에 Passport를 요청합니다. Passport에는 디바이스 정보와 유저 정보가 담겨 있으며 Gateway는 이를 serialize 하여 서비스에 전파합니다. 유저 정보가 필요한 서비스는 유저 정보 호출 없이 Passport 정보를 통하여 유저에 대한 정보를 사용할 수 있습니다.
보안과 안정성
토스는 금융 앱인 만큼 높은 수준의 보안 요구사항이 존재합니다. 저희는 Gateway에서 이러한 요구사항을 만족하기 위하여 다양한 보안 로직을 수행하고 있는데요. 아래에서는 Gateway가 핵심적으로 수행하고 있는 부분을 몇 가지 소개해 드리겠습니다.
종단간 암호화
토스 앱에서 사용하고 있는 대부분의 API는 종단간 암호화를 통해 패킷 분석의 허들을 높여 안전하게 정보를 전달하고 있습니다. 앱에서 암호화 키를 사용하여 요청 바디를 암호화하고 Gateway에서 이를 복호화 하여 서비스에 전달합니다. 복호화 과정에서 인증 / 인가 로직이 처리되고 복호화 된 데이터와 유저 정보를 서비스로 넘겨주게 됩니다. Gateway에서 이 과정을 전부 처리하기 때문에 서비스에서는 편하고 안전하게 사용자의 요청을 처리할 수 있게 됩니다.
Dynamic Security
토스는 위에서 말한 인증 / 인가를 넘어서 각 요청이 실제로 위변조 되지 않은 토스 앱에서 만들어진 요청인 지도 검증을 합니다. 토스 앱은 내부적으로 매 요청을 서명할 아주 짧은 유효기간을 가진 안전한 키 값과 변조되지 않은 토스 앱에서만 알 수 있는 정보를 활용하여 각 요청을 서명하고 이를 Gateway로 보내게 됩니다.
Gateway에서는 각 요청에 들어있는 서명 값을 통해서 토스 앱에서 만들어진 요청인지, 중복해서 사용되지 않았는지, 그리고 유효기간이 만료된 키로 만들어지진 않았는지 검증하여 앱 위변조, delayed request, replaying attack을 방지하고 의심스러운 요청이 발견되면 FDS(fraud detection system)를 통해 계정을 비활성화하여 사용자를 안전하게 보호하고 있습니다.
인증서를 이용한 인증 / 인가
Gateway에서는 토스 앱 뿐만 아니라 외부 회사나 내부 개발자의 서비스 호출을 위해 클라이언트 인증서를 이용한 mTLS API 호출도 지원하고 있습니다. 기본적으로 Istio에서 제공하는 mTLS flow 위에 Gateway 애플리케이션을 두어 인증 / 인가 처리를 하고 있습니다. Istio 만 이용하여 인증/인가를 처리할 수도 있지만 코드 베이스의 애플리케이션이 Istio의 matching rule보다 자유도도 높고, Auditing 등의 로직을 처리할 수 있으며 카나리 배포의 이점을 누릴 수 있기 때문에 Gateway에서 인증 / 인가 처리를 담당하게 되었습니다.
Edge에서 Istio는 Client 인증서의 CA 유효성을 확인한 후, 해당 인증서 정보를 헤더에 실어서 모든 트래픽을 Gateway에 전달해 줍니다. 이렇게 받은 인증서를 Decode 하여 X.509 extensions 중 Subject Alternative Name을 활용하여 인증서로부터 사용자 정보를 얻게 됩니다. 이렇게 얻은 사용자 정보와 도착지 호스트 및 요청 경로를 활용하여 각 요청에 대한 인증 / 인가 및 Auditing 처리를 하고 있습니다.
Circuit breaker
사용하는 마이크로 서비스 아키텍처 패턴은 많은 서비스들이 거미줄처럼 서로 상호작용을 하고 있습니다. 따라서 그중 하나의 서비스에서 응답 지연이 발생하면, 해당 서비스에 의존하는 수많은 서비스들에게 응답 지연이 전파됩니다. 이렇게 퍼져 나간 응답 지연이 시스템의 자원을 점유하여 모든 시스템이 먹통이 되는 상황이 발생합니다. 이를 방지하기 위해서는 응답 지연을 유발하는 서비스에게 요청을 더 이상 보내지 않고 빠르게 실패하게 하여 부하를 겪고 있는 서비스가 회복할 수 있게 돕고, 이러한 응답 지연이 전체 서비스로 확산되지 않게 하는 것이 중요합니다. 이를 서킷 브레이크 라고 부릅니다.
또한 내부 서비스 간의 서킷 브레이킹도 중요하지만 근원적인 트래픽을 발생시키는 Client에게 백프레셔를 빠르게 주기 위해서는 Gateway에서 서킷 브레이킹을 거는 것이 중요합니다. 서킷 브레이크를 적용하는 방법에는 Istio를 활용한 인프라 레이어의 서킷 브레이킹 혹은 Resilience4J나 Hystrix와 같은 라이브러리를 이용한 애플리케이션 레이어의 서킷 브레이킹이 있습니다.
각 방법에는 장단점이 존재하는데요, Istio를 활용한다면 호스트 단위로 쉽고 빠르게 전체 적용이 가능하며 애플리케이션의 개발 주기와 독립적으로 관리될 수 있다는 장점이 있지만 Istio는 호스트 단위로만 서킷 브레이킹 설정이 가능하며, 설정할 수 있는 룰에도 한계가 있습니다.
따라서 토스에서는 보이는 것 같이 각 애플리케이션이나 Gateway에 서킷 브레이킹을 적용함으로써 호스트나 Route 단위 혹은 기능단위로 정교하게 서킷 브레이킹을 걸고 있습니다.
모니터링
Gateway는 토스의 모든 서비스가 거치는 컴포넌트인 만큼 보다 꼼꼼한 모니터링이 필요합니다. 모니터링에 중요한 요소로는 로깅, 메트릭, 트레이싱이 있는데요, 저희 팀에서 각각 어떻게 기록하고 모니터링하는지 소개하겠습니다.
로깅
저희는 Gateway를 지나는 모든 요청, 응답의 Route id와 method, URI, 상태 코드 등을 Elasticsearch에 남기고 있습니다.
덕분에 요청이 어떤 Route로 들어왔는지, 업스트림으로 어떤 URI를 호출했는지에 대한 정보를 바로 확인할 수 있습니다.
메트릭
다음은 메트릭입니다. 메트릭에는 크게 시스템에서 수집하는 메트릭과 애플리케이션에서 수집하는 메트릭이 있습니다. 두 메트릭 모두 Prometheus가 수집하는데요, Node Exporter를 통해 수집된 시스템 메트릭과 Spring의 actuator를 통해 수집된 애플리케이션 메트릭을 Grafana를 이용해 시각화하고 슬랙으로 알림을 보내고 있습니다.
시스템 메트릭에는 CPU, memory, 네트워크 RX, TX 트래픽 등이 포함되어 애플리케이션 수정 사항이 시스템에 주는 영향을 1차로 파악할 수 있고, 문제가 생기는 경우 현상과 원인 파악에 활용할 수 있습니다.
애플리케이션 메트릭에서는 JVM thread block 상황이나 세대별 메모리 할당을 파악하고 full GC 발생 여부 등을 확인하고 있습니다.
Spring Cloud Gateway에서는 Route 별 메트릭도 제공하는데요, 저희는 여기에 Path 값을 더해 API Path 별 Route 메트릭을 확인하기도 합니다.
마무리
토스팀에서 Gateway를 사용하여 해결하고 있는 몇 가지 사례에 대해 소개해 드렸습니다. 사례들처럼, Gateway는 외부에 노출되는 엔드포인트에 대해 중앙 집중식으로 관리할 수 있도록 도와줍니다. 이를 통해 트래픽을 모니터링하고 속도를 제한하거나 요청 및 응답을 요구사항에 맞게 수정하는 등 거대한 마이크로 서비스 아키텍처 클러스터를 보다 쉽게 확장, 관리 및 모니터링 할 수 있도록 합니다.
위 글을 통해 Gateway 사용에 많은 인사이트를 얻으셨길 바라면서 소개 드린 사례들 외에도 더 많은 사례들을 보고 싶으시다면 SLASH23 영상도 같이 보시는 것도 좋을 것 같습니다.
감사합니다!