안녕하세요, 자바파커입니다.
"서비스를 여러 개로 쪼갰는데, 인증 코드를 서비스마다 복붙하고 있습니다."
마이크로서비스로 넘어가면 거의 모두가 이 지점에서 한 번 막힙니다. 서비스는 깔끔하게 나눴는데, 인증·로깅·요청 제한 같은 "공통 관심사"가 서비스마다 중복되기 시작하죠. 서비스가 4개면 인증 코드도 4벌입니다.
결론부터 말씀드리면 — API 게이트웨이는 이 공통 관심사를 서비스 밖으로 꺼내 한 곳에 모으는 장치입니다. 클라이언트와 서비스 사이에 문을 하나 세우고, 모든 요청이 그 문을 지나가게 만드는 것이죠.
말로 설명하면 추상적이라, 요청 하나가 게이트웨이를 통과하는 과정을 2분짜리 영상으로 만들었습니다. 글보다 이쪽이 빠를 수 있습니다.
이 글에서는 영상에 담지 못한 것 — 구현체 선택 기준, 서킷 브레이커·카나리 배포 같은 확장 기능, 그리고 "게이트웨이를 두면 안 되는 경우" 까지 다룹니다.
API 게이트웨이란 무엇인가
API 게이트웨이는 클라이언트의 모든 요청을 받아 적절한 백엔드 서비스로 전달하는 단일 진입점(Single Entry Point) 입니다.
| 항목 | 내용 |
|---|---|
| 정체 | 클라이언트와 마이크로서비스 사이의 단일 진입점 |
| 핵심 목적 | 공통 관심사를 서비스 밖으로 분리 |
| 주요 기능 | 인증·인가, 레이트 리밋, 라우팅, 캐시, 응답 집계, 로깅 |
| 트래픽 방향 | North-South (외부 ↔ 내부) |
| 대표 구현 | Spring Cloud Gateway, Kong, Traefik, Envoy, AWS API Gateway |
| 없을 때 대안 | 서비스 2~3개면 nginx 리버스 프록시로 충분 |
비유하자면 — 게이트웨이는 회사 건물의 로비 안내데스크입니다. 방문객은 각 부서 위치를 몰라도 되고, 신분 확인은 로비에서 한 번만 하며, 방문 기록도 여기서 남습니다. 부서마다 경비원을 두는 대신 입구 하나를 지키는 방식이죠.
게이트웨이가 없으면 생기는 일
클라이언트가 서비스를 직접 호출하면 세 가지 문제가 생깁니다.
- 클라이언트가 모든 서비스 주소를 알아야 합니다. 서비스가 늘어날 때마다 클라이언트도 같이 바뀝니다.
- 인증·로깅·제한 로직이 서비스마다 중복됩니다. 정책이 바뀌면 전부 고쳐야 합니다.
- 서비스 하나만 바뀌어도 클라이언트가 깨집니다. 내부 구조 변경이 그대로 외부에 노출됩니다.
API 게이트웨이의 5가지 핵심 기능
요청 하나가 게이트웨이를 통과하는 순서대로 보겠습니다.
1. 인증 · 인가 — 잘못된 요청을 입구에서 끊는다
가장 앞단에서 토큰을 검증합니다. 서명이 깨졌거나 만료된 JWT는 뒤쪽 서비스까지 가지도 못하고 401로 반사됩니다.
효과는 두 가지입니다. 서비스가 인증 코드를 갖지 않아도 되고, 비정상 트래픽이 내부망에 아예 들어오지 않습니다.
# Spring Cloud Gateway - JWT 검증 필터
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: JwtAuthFilter2. 레이트 리밋 — 폭주를 내부로 흘려보내지 않는다
클라이언트별 요청 수를 세어 허용치를 넘으면 429 Too Many Requests로 차단합니다. 초당 5건이 허용치인데 14건이 들어오면 5건만 통과하고 9건은 게이트웨이에서 끊깁니다.
핵심은 뒤쪽 서비스가 폭주를 구경조차 하지 못한다는 점입니다. 서비스마다 방어 로직을 넣는 대신 입구에서 한 번에 막습니다.
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5 # 초당 보충량
redis-rate-limiter.burstCapacity: 10 # 순간 허용치
key-resolver: "#{@userKeyResolver}"분산 환경에서는 카운터를 Redis 같은 공유 저장소에 두어야 합니다. 인스턴스별 로컬 카운터를 쓰면 게이트웨이가 3대일 때 실제 허용치가 3배가 됩니다.
3. 라우팅 — 경로가 목적지를 정한다
GET /users는 User 서비스로, GET /orders는 Order 서비스로, POST /pay는 Payment 서비스로 보냅니다.
이 덕분에 서비스가 어디로 이사 가도 클라이언트 코드는 그대로입니다. 서비스를 쪼개거나 합쳐도, 인스턴스를 늘려도 클라이언트는 게이트웨이 주소 하나만 압니다. API 버전 관리(/v1, /v2)도 여기서 처리합니다.
4. 캐시 — 같은 요청은 서비스를 부르지 않는다
동일한 요청이 다시 오면 게이트웨이가 저장해 둔 응답을 바로 반환합니다. 서비스까지 왕복하던 요청이 게이트웨이에서 끝나므로 응답 시간과 서비스 부하가 동시에 줄어듭니다.
단, 캐시는 읽기 전용이고 사용자별로 다르지 않은 응답에만 걸어야 합니다. 인증된 사용자별 응답을 공유 캐시에 넣으면 다른 사용자에게 남의 데이터가 나갑니다. Cache-Control: private 처리와 캐시 키에 사용자 식별자 포함 여부를 반드시 확인하세요.
5. 응답 집계 (BFF) — 여러 서비스를 한 번에 묶는다
화면 하나를 그리는 데 서비스 셋이 필요할 때, 클라이언트가 세 번 호출하는 대신 게이트웨이가 대신 병렬로 호출하고(fan-out) 결과를 하나로 합쳐(fan-in) 돌려줍니다.
모바일처럼 네트워크 왕복 비용이 큰 환경에서 특히 효과적입니다. 이 패턴을 클라이언트 종류별로 분리하면 BFF(Backend For Frontend) 가 됩니다.
여기에 더해 모든 통과 기록에 같은 trace-id를 심으면, 요청 하나가 서비스 여러 개를 거친 경로를 한 줄로 추적할 수 있습니다.
영상에 없는 것 — 확장 기능 4가지
실무에서 게이트웨이에 함께 얹는 기능들입니다.
| 기능 | 하는 일 | 언제 필요한가 |
|---|---|---|
| 서킷 브레이커 | 실패가 누적된 서비스로의 호출을 일정 시간 차단 | 한 서비스 장애가 전체로 번지는 걸 막을 때 |
| 카나리 배포 | 트래픽의 N%만 새 버전으로 라우팅 | 무중단으로 새 버전을 점진 검증할 때 |
| 프로토콜 변환 | 외부 REST ↔ 내부 gRPC 변환 | 내부는 gRPC로 성능을 챙기고 외부엔 REST를 열 때 |
| 요청/응답 변환 | 헤더 주입, 필드 마스킹, 페이로드 재구성 | 레거시 API 스펙을 외부에 그대로 노출하기 싫을 때 |
특히 서킷 브레이커는 레이트 리밋과 짝입니다. 레이트 리밋이 "들어오는 폭주"를 막는다면, 서킷 브레이커는 "나가는 호출이 죽은 서비스에 매달리는 것"을 막습니다. 둘 다 없으면 장애가 연쇄됩니다.
filters:
- name: CircuitBreaker
args:
name: orderCircuit
fallbackUri: forward:/fallback/orders구현체 어떤 걸 고를까
| 구현체 | 기반 | 강점 | 이럴 때 |
|---|---|---|---|
| Spring Cloud Gateway | Java / Netty (리액티브) | Spring 생태계와 완전 통합, 필터를 자바로 작성 | 팀이 Spring 기반이고 커스텀 로직이 많을 때 |
| Kong | OpenResty (nginx + Lua) | 플러그인 생태계가 풍부, 관리 UI | 설정 위주로 빠르게 붙이고 싶을 때 |
| Traefik | Go | 동적 설정, 컨테이너 라벨 자동 인식 | 쿠버네티스·도커 환경에서 설정 부담을 줄이고 싶을 때 |
| Envoy | C++ | 고성능, 관측성이 강력, 서비스 메시 데이터 플레인 | 대규모 트래픽 + 메시까지 염두에 둘 때 |
| AWS API Gateway | 관리형 | 인프라 운영 불필요, Lambda 연동 | 서버리스 구성이거나 운영 인력이 없을 때 |
선택 기준은 단순합니다. 커스텀 로직을 코드로 많이 짜야 하면 Spring Cloud Gateway, 설정으로 끝내고 싶으면 Kong이나 Traefik, 운영을 맡기고 싶으면 관리형입니다.
쿠버네티스를 쓰신다면 nginx-ingress에서 Traefik으로 마이그레이션한 경험도 참고하실 만합니다.
게이트웨이를 두면 안 되는 경우
솔직히 말씀드리면, API 게이트웨이가 항상 정답은 아닙니다. 아래에 해당하면 도입을 미루는 편이 낫습니다.
1. 서비스가 2~3개이고 팀이 하나일 때 공통 관심사 중복이라고 해봐야 몇 곳 안 됩니다. 게이트웨이를 세우면 운영할 컴포넌트만 하나 늘어납니다. nginx 리버스 프록시로 충분합니다.
2. 이중화 여력이 없을 때 게이트웨이는 모든 트래픽이 지나는 단일 지점입니다. 이게 죽으면 서비스가 전부 죽습니다. 최소 2대 이상 + 헬스체크 + 오토스케일을 감당할 수 없다면, 단일 진입점은 편의가 아니라 위험입니다.
3. 비즈니스 로직을 넣고 싶어질 때 가장 흔한 실패 패턴입니다. "여기서 처리하면 편한데" 하면서 도메인 로직이 게이트웨이 설정에 쌓이기 시작하면, 결국 아무도 못 건드리는 설정 덩어리가 됩니다. 게이트웨이에는 인증·라우팅·제한 같은 횡단 관심사만 넣으세요.
4. 내부 서비스 간 호출까지 통과시키려 할 때 게이트웨이는 외부 → 내부(North-South) 트래픽용입니다. 서비스끼리의 호출(East-West)까지 게이트웨이로 보내면 불필요한 홉과 병목이 생깁니다. 그쪽은 서비스 메시나 직접 호출의 영역입니다.
자주 묻는 질문 (FAQ)
Q. API 게이트웨이와 로드밸런서, 리버스 프록시는 뭐가 다른가요?
계층이 다릅니다. 로드밸런서는 같은 서비스의 인스턴스들에 트래픽을 분배합니다(L4/L7). 리버스 프록시는 요청을 대신 받아 백엔드로 전달합니다. API 게이트웨이는 리버스 프록시 기능 위에 인증·레이트 리밋·응답 집계 같은 API 수준의 정책을 얹은 것입니다. 게이트웨이가 리버스 프록시의 상위 개념이라고 보시면 됩니다.
Q. 쿠버네티스 Ingress가 있는데 API 게이트웨이가 또 필요한가요?
역할이 겹치지만 같지는 않습니다. Ingress는 클러스터로 들어오는 HTTP 라우팅과 TLS 종료에 초점이 있습니다. JWT 검증, 사용자별 레이트 리밋, 응답 집계 같은 API 정책은 기본 Ingress로는 부족합니다. 그래서 Ingress Controller에 게이트웨이 기능을 더한 제품(Kong Ingress, Traefik 등)을 쓰거나, Ingress 뒤에 게이트웨이를 따로 두는 구성을 씁니다. 쿠버네티스 기초는 K8S에 대한 이해와 Ingress + cert-manager 가이드에 정리해 뒀습니다.
Q. 게이트웨이에서 인증하면 각 서비스는 인증을 안 해도 되나요?
아니요. 이게 가장 위험한 오해입니다. 게이트웨이 인증은 외부 트래픽만 막습니다. 내부망에 침투했거나 잘못 설정된 서비스가 게이트웨이를 우회해 직접 호출하면 그대로 뚫립니다. 게이트웨이에서 검증한 사용자 정보를 서명된 헤더로 내려보내고, 각 서비스는 그 헤더를 최소한 검증하는 구성이 안전합니다. 제로 트러스트의 기본입니다.
정리
API 게이트웨이는 결국 하나의 문장으로 요약됩니다.
공통 관심사를 서비스 밖으로 꺼내 한 곳에 모으는 장치.
- 단일 진입점 — 클라이언트는 주소 하나만 알면 됩니다
- 인증·인가를 한 곳에서, 레이트 리밋으로 폭주 차단
- 경로 기반 라우팅과 API 버전 관리
- 캐시·응답 집계로 왕복과 지연 단축
- 로깅·트레이싱 표준화
다만 서비스가 몇 개 안 되거나 이중화 여력이 없다면 서두를 이유가 없습니다. 게이트웨이는 서비스가 늘어나면서 중복이 실제로 아파질 때 도입해도 늦지 않습니다.
여러분은 어떤 게이트웨이를 쓰고 계신가요? Spring Cloud Gateway, Kong, 아니면 관리형 서비스인가요? 도입하면서 겪은 시행착오가 있다면 댓글로 공유해 주세요.