모놀리식 아키텍처(Monolithic Architecture)의 거대한 성벽이 무너지고, 작고 독립적인 서비스들로 구성된 마이크로서비스 아키텍처(MSA)의 시대가 열렸습니다. MSA는 분명 개발팀에 놀라운 유연성과 확장성을 선물했지만, 그 이면에는 수많은 서비스들의 파편화로 인한 복잡성이라는 그림자가 짙게 드리워져 있습니다. 클라이언트는 도대체 어떤 서비스와 통신해야 할까요? 인증, 로깅, 모니터링과 같은 공통 기능들은 모든 서비스에 개별적으로 구현해야 할까요? 이러한 혼돈 속에서 시스템 전체의 안정성과 개발 생산성은 위협받기 시작합니다.
바로 이 지점에서 API 게이트웨이(API Gateway)라는 설계 패턴이 구원투수처럼 등장합니다. API 게이트웨이는 모든 클라이언트의 요청을 받아 적절한 마이크로서비스로 전달하는 단일 진입점(Single Point of Entry) 역할을 수행합니다. 마치 잘 설계된 건물의 로비처럼, 외부의 모든 방문객을 맞이하여 보안 검색을 거치고, 정확한 목적지로 안내하는 역할을 하는 것이죠. 이 글에서는 풀스택 개발자의 시각에서 API 게이트웨이가 어떻게 MSA의 복잡성을 길들이고, 안정적이며 확장 가능한 시스템 설계의 핵심 요소로 자리 잡았는지 심도 있게 파헤쳐 보겠습니다.
마이크로서비스, 왜 API 게이트웨이가 필요한가?
마이크로서비스 아키텍처의 이상적인 그림은 각 서비스가 독립적으로 개발, 배포, 확장되는 것입니다. 하지만 클라이언트가 이 수많은 서비스들과 직접 통신해야 한다고 상상해 봅시다. 문제는 생각보다 훨씬 심각해집니다.
문제 1: 클라이언트와 서비스 간의 강한 결합(Tight Coupling)
API 게이트웨이가 없다면, 클라이언트 애플리케이션(웹, 모바일 앱 등)은 모든 개별 마이크로서비스의 엔드포인트 주소를 직접 알고 있어야 합니다. 예를 들어 사용자 정보를 가져오려면 user-service의 주소로, 상품 정보를 가져오려면 product-service의 주소로 직접 요청해야 합니다. 이는 다음과 같은 문제를 야기합니다.
- 리팩토링의 어려움:
product-service를 두 개의 작은 서비스(product-info-service,product-inventory-service)로 분리했다고 가정해 봅시다. 이 변경 사항을 모든 클라이언트 코드에 반영하고 재배포해야 합니다. 이는 MSA가 추구하는 서비스의 독립성을 정면으로 위배하는 것입니다. - 서비스 디스커버리의 복잡성: 서비스 인스턴스는 오토스케일링(Auto-scaling)에 의해 동적으로 생성되고 사라집니다. 클라이언트가 이 변화무쌍한 서비스들의 IP 주소와 포트를 어떻게 추적할 수 있을까요? 클라이언트 내부에 복잡한 서비스 디스커버리 로직을 구현해야만 합니다.
문제 2: 공통 기능의 중복 구현 (Cross-Cutting Concerns)
현대적인 애플리케이션은 수많은 공통 기능을 필요로 합니다. 대표적인 예는 다음과 같습니다.
- 인증 및 인가 (Authentication & Authorization): 사용자가 누구인지 확인하고, 특정 리소스에 접근할 권한이 있는지 검사합니다.
- 로깅 및 모니터링: 모든 요청과 응답을 기록하고, 시스템의 상태를 파악하기 위한 지표(metrics)를 수집합니다.
- API 속도 제한 (Rate Limiting): 악의적인 공격이나 특정 사용자의 과도한 요청으로부터 시스템을 보호합니다.
- SSL/TLS 암호화 종료 (SSL Termination): 외부의 HTTPS 요청을 내부의 HTTP 통신으로 전환하여 서비스 내부의 부하를 줄입니다.
API 게이트웨이 없이는 이러한 공통 기능들을 모든 마이크로서비스에 각각 구현해야 합니다. 이는 엄청난 코드 중복을 발생시키고, 개발자들은 비즈니스 로직이 아닌 기반 기술 구현에 많은 시간을 허비하게 됩니다. 보안 정책이 변경될 때마다 모든 서비스를 수정하고 재배포하는 것은 상상만 해도 끔찍한 일입니다.
문제 3: 다양한 통신 프로토콜 처리의 어려움
MSA 환경에서는 각 서비스의 특성에 맞는 최적의 통신 프로토콜을 선택할 수 있습니다. 예를 들어, 외부에 공개되는 API는 주로 RESTful API(HTTP/JSON)를 사용하지만, 서비스 간 내부 통신은 성능이 뛰어난 gRPC나 메시지 큐(RabbitMQ, Kafka)를 사용할 수 있습니다. 클라이언트가 이 모든 프로토콜을 이해하고 통신하는 것은 거의 불가능에 가깝습니다. 웹 브라우저는 gRPC를 직접 호출할 수 없습니다.
API 게이트웨이는 이 모든 혼돈의 중심에서 질서를 부여하는 역할을 합니다. 클라이언트에게는 일관되고 통합된 단일 API를 제공하고, 내부적으로는 복잡한 마이크로서비스들의 상호작용을 중개하는 '교통 경찰'이자 '보안 요원'인 셈입니다. 이것이 바로 우리가 MSA 환경에서 API Gateway 패턴을 반드시 도입해야 하는 이유입니다.
API 게이트웨이의 핵심 역할과 막강한 장점
API 게이트웨이는 단순히 요청을 전달하는 프록시(Proxy) 서버 이상의 역할을 수행합니다. MSA의 안정성과 효율성을 극대화하는 다양한 핵심 기능들을 내장하고 있으며, 이것이 바로 API 게이트웨이를 도입함으로써 얻게 되는 강력한 장점들입니다.
1. 요청 라우팅 (Request Routing)
가장 기본적인 기능입니다. 게이트웨이는 들어온 요청의 경로(Path), 헤더(Header), 쿼리 파라미터(Query Parameter) 등을 분석하여 어떤 마이크로서비스로 전달할지 결정합니다. 예를 들어, /api/v1/users/{userId} 요청은 user-service로, /api/v1/products/{productId} 요청은 product-service로 라우팅하는 규칙을 설정할 수 있습니다. 이를 통해 클라이언트는 내부 서비스들의 복잡한 주소 체계를 전혀 알 필요가 없으며, 오직 게이트웨이의 주소만 바라보면 됩니다. 이는 서비스의 위치 투명성(Location Transparency)을 보장하는 핵심 요소입니다.
2. 인증 및 인가 (Authentication & Authorization)
API 게이트웨이의 가장 중요한 역할 중 하나는 보안을 중앙에서 처리하는 것입니다. 클라이언트가 보낸 요청에 포함된 인증 정보(예: JWT 토큰, API 키)를 게이트웨이 단에서 검증합니다. 유효한 토큰인 경우에만 내부 서비스로 요청을 전달하고, 그렇지 않으면 즉시 401 Unauthorized 또는 403 Forbidden 오류를 반환합니다. 이로써 개별 마이크로서비스들은 인증/인가라는 무거운 짐을 벗고 오롯이 자신의 비즈니스 로직에만 집중할 수 있게 됩니다.
- JWT(JSON Web Token) 검증: 요청 헤더의
Authorization: Bearer <token>에서 토큰을 추출하여 서명을 확인하고 만료 시간을 검사합니다. - API Key 관리: 외부 파트너나 서드파티 개발자에게 발급된 API 키를 검증하여 허가된 요청만 처리합니다.
- OAuth 2.0 / OIDC 통합: 외부 인증 서버(Identity Provider)와 연동하여 복잡한 인증 플로우를 처리합니다.
3. 집계 (Aggregation) 패턴
때로는 클라이언트가 원하는 화면을 구성하기 위해 여러 마이크로서비스를 호출해야 하는 경우가 있습니다. 예를 들어 '상품 상세 페이지'는 상품 정보(product-service), 재고 정보(inventory-service), 그리고 사용자 리뷰(review-service)가 모두 필요합니다. 클라이언트가 이 세 개의 API를 각각 호출하는 것은 비효율적이며 네트워크 오버헤드를 증가시킵니다. API 게이트웨이는 이러한 요청들을 하나로 묶어주는 집계(Aggregation) 역할을 수행할 수 있습니다. 클라이언트가 /api/v1/product-details/{productId} 라는 단일 API를 호출하면, 게이트웨이가 내부적으로 세 개의 서비스를 모두 호출하여 데이터를 조합한 후 최종 결과를 클라이언트에게 한 번에 전달해 줍니다. 이를 통해 클라이언트-서버 간의 통신 횟수(chattiness)를 획기적으로 줄일 수 있습니다.
4. 프로토콜 변환 (Protocol Translation)
앞서 언급했듯이, MSA 내부에서는 다양한 프로토콜을 사용할 수 있습니다. API 게이트웨이는 외부의 표준적인 요청(예: RESTful API)을 내부 서비스가 사용하는 다른 프로토콜(예: gRPC, AMQP)로 변환해주는 역할을 합니다. 웹 클라이언트는 친숙한 HTTP/JSON으로 요청하지만, 게이트웨이가 이를 gRPC 메시지로 변환하여 내부의 고성능 서비스와 통신할 수 있게 해주는 것입니다. 이는 기술 스택의 다형성을 보장하면서도 외부 인터페이스의 일관성을 유지하는 강력한 기능입니다.
5. 공통 기능 오프로딩 (Offloading Cross-Cutting Concerns)
인증/인가 외에도 다양한 공통 기능들을 게이트웨이로 오프로딩(offloading)하여 중복을 제거하고 개발 효율성을 높일 수 있습니다.
- 로깅 및 추적: 게이트웨이는 모든 요청/응답의 중앙 로깅 지점이 됩니다. 또한, 분산 추적(Distributed Tracing)을 위해 요청 헤더에 고유한 트랜잭션 ID(Correlation ID)를 주입하여, 하나의 요청이 여러 마이크로서비스를 거치는 전체 과정을 추적할 수 있게 도와줍니다.
- 응답 캐싱 (Response Caching): 자주 요청되지만 내용이 잘 변하지 않는 응답(예: 상품 카테고리 목록)을 게이트웨이에 캐싱하여 응답 속도를 높이고 백엔드 서비스의 부하를 줄입니다.
- 요청/응답 변환: 클라이언트가 요구하는 데이터 형식과 내부 서비스가 제공하는 형식이 다를 때 중간에서 데이터를 변환해주거나, 불필요한 필드를 제거하여 페이로드 크기를 최적화할 수 있습니다.
6. API 속도 제한 및 서킷 브레이커 (Rate Limiting & Circuit Breaker)
시스템 안정성을 확보하기 위한 필수 기능입니다.
- API 속도 제한 (Rate Limiting): 특정 클라이언트나 IP 주소에서 분당/시간당 보낼 수 있는 요청의 수를 제한합니다. 이를 통해 DoS(Denial of Service) 공격을 방어하고, 특정 사용자가 시스템 리소스를 독점하는 것을 방지하여 모든 사용자에게 공정한 서비스를 제공할 수 있습니다.
- 서킷 브레이커 (Circuit Breaker): 특정 마이크로서비스에 장애가 발생하여 응답이 지연되거나 실패할 경우, 해당 서비스로의 요청을 일시적으로 차단하는 패턴입니다. 이는 장애가 발생한 서비스가 복구될 시간을 벌어주고, 해당 장애가 시스템 전체로 전파되는 '연쇄 실패(Cascading Failures)'를 막아줍니다. 게이트웨이는 차단된 요청에 대해 즉시 에러를 반환하거나, 미리 준비된 기본 응답(Fallback Response)을 대신 전달할 수 있습니다.
이처럼 API 게이트웨이는 단순히 요청을 전달하는 문지기를 넘어, 마이크로서비스 아키텍처의 보안, 안정성, 효율성을 책임지는 핵심 컨트롤 타워 역할을 수행합니다.
대표적인 API 게이트웨이 구현체 비교 분석
API 게이트웨이의 개념과 역할을 이해했다면, 이제 실제 프로젝트에 어떤 구현체를 선택할지 고민해야 합니다. 세상에는 다양한 오픈소스 및 상용 API 게이트웨이 솔루션이 존재하며, 각기 다른 특징과 장단점을 가지고 있습니다. 여기서는 가장 널리 사용되는 몇 가지 솔루션을 비교 분석하여 여러분의 선택을 돕고자 합니다.
| 항목 | Spring Cloud Gateway | Kong | Amazon API Gateway | Apigee (Google Cloud) |
|---|---|---|---|---|
| 주요 언어/기술 스택 | Java (Spring Framework, Project Reactor) | Lua (on NGINX) | 관리형 서비스 (내부 구현 비공개) | 관리형 서비스 (내부 구현 비공개) |
| 운영 방식 | 자체 호스팅 (Self-hosted) | 자체 호스팅 (Self-hosted), 클라우드 제공 | 완전 관리형 서비스 (Fully-managed) | 완전 관리형 서비스 (Fully-managed) |
| 성능 | 우수 (비동기 논블로킹 I/O 기반) | 최상급 (NGINX 기반으로 매우 빠름) | 우수 (AWS가 관리, 자동 확장) | 우수 (Google이 관리, 자동 확장) |
| 확장성 및 커스터마이징 | 매우 높음 (Java 코드로 필터 직접 구현) | 높음 (Lua로 작성된 플러그인 생태계 활발) | 제한적 (Lambda Authorizer 등 연동 가능) | 높음 (JavaScript, Java 등으로 정책 구현) |
| 설정 방식 | 코드 기반 (Java or Kotlin), YAML 설정 | 선언적 (Admin API, DB, CRD) | GUI 콘솔, CLI, IaC (CloudFormation) | GUI 콘솔, API |
| 생태계 및 통합 | Spring 생태계(Eureka, Config 등)와 완벽 통합 | 플러그인 마켓플레이스, Kubernetes Ingress Controller | AWS 서비스(Lambda, IAM, Cognito 등)와 완벽 통합 | Google Cloud 서비스와 통합 |
| 주요 사용 사례 | Java/Spring 기반 마이크로서비스 환경 | 고성능, 다중 언어 환경, 플러그인 기반 확장 필요 시 | AWS를 메인 클라우드로 사용하는 환경, 서버리스 아키텍처 | 엔터프라이즈급 API 관리, 분석, 수익화 모델 필요 시 |
| 학습 곡선 | 낮음 (Spring 개발자에게 매우 친숙) | 중간 (NGINX, Lua, 플러그인 개념 이해 필요) | 낮음 (GUI 기반으로 시작하기 쉬움) | 중간 (기능이 많아 전체 파악에 시간 소요) |
Spring Cloud Gateway: Spring 개발자들의 선택
만약 여러분의 마이크로서비스가 대부분 Spring Boot로 개발되었다면, Spring Cloud Gateway는 거의 완벽한 선택지입니다. Spring 생태계에 완벽하게 통합되어 있으며, Spring 개발자에게 익숙한 방식으로 게이트웨이를 설정하고 확장할 수 있습니다. 내부적으로 Netty와 Project Reactor를 사용하여 비동기 논블로킹 방식으로 동작하므로 뛰어난 성능을 보장합니다. 가장 큰 장점은 Java 코드를 사용하여 필터(Filter)를 직접 구현함으로써 매우 정교하고 복잡한 로직을 자유자재로 추가할 수 있다는 점입니다.
Kong: 성능과 확장성을 모두 잡은 오픈소스 강자
Kong은 고성능 웹 서버인 NGINX를 기반으로 만들어져 매우 빠른 처리 속도를 자랑합니다. Kong의 핵심 철학은 '플러그인(Plugin)'입니다. 인증, 보안, 로깅, 속도 제한 등 거의 모든 기능을 플러그인 형태로 제공하며, 필요에 따라 쉽게 붙였다 뗄 수 있습니다. Lua 언어로 커스텀 플러그인을 만들 수도 있습니다. 이러한 구조 덕분에 다양한 언어로 구성된 폴리글랏(Polyglot) 마이크로서비스 환경에서 특히 빛을 발합니다. Kubernetes 환경에서는 Ingress Controller로도 널리 사용되어 클라우드 네이티브 환경과의 궁합도 매우 좋습니다.
Amazon API Gateway: 클라우드 네이티브의 정석
AWS를 주력 클라우드로 사용하고 있다면 Amazon API Gateway는 강력한 선택지입니다. 서버를 직접 관리할 필요가 없는 완전 관리형 서비스이므로 인프라 운영 부담이 전혀 없습니다. AWS Lambda, IAM, Cognito 등 다른 AWS 서비스와 매우 긴밀하게 통합되어 있어 서버리스(Serverless) 아키텍처를 구성하거나 AWS의 강력한 보안 기능을 활용하기에 최적화되어 있습니다. 다만, 자체 호스팅 솔루션에 비해 커스터마이징의 유연성은 다소 떨어질 수 있으며, 트래픽이 많아질수록 비용이 증가하는 종량제 모델이라는 점을 고려해야 합니다.
Apigee (Google Cloud): 엔터프라이즈를 위한 종합 API 관리 플랫폼
Apigee는 단순한 게이트웨이를 넘어 API의 전체 생명주기를 관리하는 종합 플랫폼(Full Lifecycle API Management)에 가깝습니다. 강력한 분석 및 모니터링 기능, API 수익화(Monetization) 모델 지원, 개발자 포털 제공 등 엔터프라이즈 환경에서 요구하는 고급 기능들을 다수 포함하고 있습니다. 대규모 조직에서 수많은 내부 및 외부 API를 체계적으로 관리하고 비즈니스 자산으로 활용하고자 할 때 적합한 솔루션입니다.
- Spring 개발팀이라면: 고민 없이 Spring Cloud Gateway로 시작하세요.
- 최고의 성능과 유연한 플러그인이 필요하다면: Kong이 훌륭한 선택입니다.
- 인프라 관리보다 개발에 집중하고 싶고 AWS를 쓴다면: Amazon API Gateway가 정답입니다.
- API를 제품처럼 관리하고 분석, 수익화하고 싶다면: Apigee를 고려해 보세요.
실전! Spring Cloud Gateway로 API 게이트웨이 구축하기
백문이 불여일견입니다. 이제 직접 Spring Cloud Gateway를 사용하여 간단한 API 게이트웨이를 만들어 보겠습니다. 이 예제에서는 /users/** 로 들어오는 요청을 user-service로, /orders/** 로 들어오는 요청을 order-service로 라우팅하는 기본적인 기능을 구현해 봅니다.
1. 프로젝트 설정 (build.gradle)
먼저 Spring Boot 프로젝트를 생성하고, `build.gradle` (또는 `pom.xml`) 파일에 필요한 의존성을 추가합니다. 게이트웨이 기능과 서비스 디스커버리(Eureka Client)를 위한 의존성이 필요합니다.
dependencies {
// Spring Cloud Gateway
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
// Eureka Client for Service Discovery
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
// ... 기타 Spring Boot 의존성
}
2. 기본 라우팅 설정 (application.yml)
Spring Cloud Gateway의 가장 큰 장점 중 하나는 application.yml 파일에 선언적으로 라우팅 규칙을 정의할 수 있다는 점입니다. 매우 직관적이고 관리가 용이합니다.
server:
port: 8000
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # Eureka와 같은 서비스 디스커버리 연동 활성화
lower-case-service-id: true
routes:
- id: user-service-route # 라우트의 고유 식별자
uri: lb://USER-SERVICE # 로드 밸런서를 통해 user-service를 찾음 (lb = load balancer)
predicates:
- Path=/api/users/** # 이 경로 패턴에 매칭될 때
filters:
- RewritePath=/api/users/(?<segment>.*), /$\{segment} # /api/users/1 -> /1 로 경로 재작성
- id: order-service-route
uri: lb://ORDER-SERVICE
predicates:
- Path=/api/orders/**
filters:
- RewritePath=/api/orders/(?<segment>.*), /$\{segment}
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
위 설정의 핵심은 `routes` 입니다.
- `id`: 각 라우팅 규칙을 구분하는 고유한 ID입니다.
- `uri`: 요청을 전달할 목적지 주소입니다. `lb://USER-SERVICE`는 서비스 디스커버리(Eureka)에 등록된 'USER-SERVICE'라는 이름의 서비스를 찾아 로드 밸런싱하여 요청을 보내라는 의미입니다. 이렇게 하면 서비스의 IP나 포트가 변경되어도 게이트웨이 설정을 바꿀 필요가 없습니다.
- `predicates`: 어떤 조건의 요청을 이 라우팅 규칙에 적용할지 정의합니다. `Path=/api/users/**`는 URL 경로가 `/api/users/`로 시작하는 모든 요청을 의미합니다. 경로 외에도 헤더, 쿠키, HTTP 메소드 등 다양한 조건으로 설정할 수 있습니다.
- `filters`: 요청이 목적지로 전달되기 전이나 후에 적용할 필터들을 정의합니다. `RewritePath` 필터는 클라이언트가 요청한 경로를 백엔드 서비스가 이해하는 경로로 변경해주는 역할을 합니다.
3. 커스텀 필터로 공통 기능 구현하기 (Custom Filter)
이제 Java 코드를 이용해 모든 요청에 대해 특정 헤더를 추가하고 요청 처리 시간을 로깅하는 커스텀 필터를 만들어 보겠습니다. 이것이 Spring Cloud Gateway의 진정한 강력함이 드러나는 부분입니다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(CustomGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Pre-Filter: 요청이 다운스트림 서비스로 가기 전에 실행
long startTime = System.currentTimeMillis();
// 요청에 커스텀 헤더 추가
exchange.getRequest().mutate().header("X-Request-ID", java.util.UUID.randomUUID().toString()).build();
log.info("Request Path: {}", exchange.getRequest().getPath());
// Post-Filter: 다운스트림 서비스의 응답이 온 후 실행
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.info("Response Status Code: {}", exchange.getResponse().getStatusCode());
log.info("Request processed in {} ms", duration);
}));
}
@Override
public int getOrder() {
// 필터의 실행 순서를 지정. 낮을수록 먼저 실행
return -1;
}
}
@Component 어노테이션을 붙여주기만 하면 이 필터는 자동으로 등록되어 모든 요청에 적용됩니다. filter 메소드 내부에서 chain.filter(exchange)를 기준으로 이전은 Pre-Filter, 이후(.then(...) 내부)는 Post-Filter로 동작합니다. 이처럼 간단한 코드로 강력한 공통 기능을 중앙에서 관리할 수 있습니다.
오픈소스 강자, Kong으로 API 게이트웨이 설정하기
이번에는 다른 관점에서 Kong을 이용해 API 게이트웨이를 구성하는 방법을 알아보겠습니다. Kong은 설정 파일을 직접 수정하는 대신, Admin API라는 RESTful API를 통해 동적으로 설정을 변경하는 것이 특징입니다.
1. Kong 설치 및 실행 (Docker Compose)
로컬 환경에서 가장 쉽게 Kong을 실행하는 방법은 Docker Compose를 사용하는 것입니다. Kong의 설정 데이터를 저장할 데이터베이스(PostgreSQL)와 Kong을 함께 실행합니다.
# docker-compose.yml
version: '3.8'
services:
kong-database:
image: postgres:9.6
container_name: kong-database
environment:
- POSTGRES_USER=kong
- POSTGRES_DB=kong
- POSTGRES_PASSWORD=kong
networks:
- kong-net
kong-migration:
image: kong:2.8
container_name: kong-migration
command: "kong migrations bootstrap"
depends_on:
- kong-database
environment:
- KONG_DATABASE=postgres
- KONG_PG_HOST=kong-database
- KONG_PG_PASSWORD=kong
networks:
- kong-net
restart: on-failure
kong:
image: kong:2.8
container_name: kong-gateway
depends_on:
- kong-migration
environment:
- KONG_DATABASE=postgres
- KONG_PG_HOST=kong-database
- KONG_PG_PASSWORD=kong
- KONG_PROXY_ACCESS_LOG=/dev/stdout
- KONG_ADMIN_ACCESS_LOG=/dev/stdout
- KONG_PROXY_ERROR_LOG=/dev/stderr
- KONG_ADMIN_ERROR_LOG=/dev/stderr
- KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl
ports:
- "8000:8000" # Proxy Port
- "8443:8443" # Proxy SSL Port
- "8001:8001" # Admin API Port
- "8444:8444" # Admin API SSL Port
networks:
- kong-net
networks:
kong-net:
driver: bridge
위 파일을 작성한 후 터미널에서 `docker-compose up -d` 명령을 실행하면 Kong이 구동됩니다.
2. Admin API를 이용한 서비스 및 라우트 등록
Kong의 설정은 'Service', 'Route', 'Consumer', 'Plugin' 등의 객체로 관리됩니다. `curl`과 같은 HTTP 클라이언트를 사용하여 Admin API(8001 포트)로 이 객체들을 등록/관리할 수 있습니다.
1) 백엔드 서비스(Service) 등록: 먼저 요청을 전달할 대상인 마이크로서비스를 등록합니다. 이름은 'user-service'이고, 실제 주소는 `http://mock-user-api:4000` 이라고 가정합니다.
curl -i -X POST \
--url http://localhost:8001/services/ \
--data 'name=user-service' \
--data 'url=http://mock-user-api:4000'
2) 라우트(Route) 등록: 어떤 경로로 들어온 요청을 위에서 등록한 서비스로 보낼지 규칙을 정합니다. /users 경로로 오는 요청을 'user-service'로 연결합니다.
curl -i -X POST \
--url http://localhost:8001/services/user-service/routes \
--data 'paths[]=/users'
이제 `http://localhost:8000/users` 로 요청을 보내면 Kong이 받아서 `http://mock-user-api:4000` 으로 전달해줍니다.
3. 플러그인을 이용한 기능 추가
Kong의 진정한 힘은 플러그인에 있습니다. API 키 인증과 속도 제한 플러그인을 적용해 보겠습니다.
1) API 키 인증(key-auth) 플러그인 활성화: 'user-service'에 `key-auth` 플러그인을 적용합니다.
curl -i -X POST \
--url http://localhost:8001/services/user-service/plugins \
--data 'name=key-auth'
2) 소비자(Consumer) 및 API 키 생성: API를 사용할 소비자를 생성하고, 해당 소비자에게 API 키를 발급합니다.
# 'my-app' 이라는 소비자 생성
curl -i -X POST --url http://localhost:8001/consumers/ --data "username=my-app"
# 'my-app' 소비자에게 API 키 발급
curl -i -X POST --url http://localhost:8001/consumers/my-app/key-auth/ --data 'key=super-secret-key'
이제 `apikey` 헤더에 `super-secret-key` 값을 담아서 보내야만 `/users` 경로에 접근할 수 있습니다.
3) 속도 제한(rate-limiting) 플러그인 활성화: 'my-app' 소비자가 분당 5번만 요청할 수 있도록 제한을 겁니다.
curl -i -X POST \
--url http://localhost:8001/consumers/my-app/plugins \
--data "name=rate-limiting" \
--data "config.minute=5" \
--data "config.policy=local"
이처럼 Kong은 코드를 한 줄도 작성하지 않고, 단지 Admin API 호출만으로 강력한 기능들을 동적으로 추가하고 관리할 수 있다는 점에서 Spring Cloud Gateway와는 다른 매력을 가집니다.
API 게이트웨이 도입 시 고려해야 할 함정들
API 게이트웨이는 수많은 장점을 제공하지만, '만병통치약'은 아닙니다. 잘못 도입하거나 관리하면 오히려 새로운 문제를 낳을 수 있습니다. 성공적인 시스템 설계를 위해 반드시 고려해야 할 잠재적인 함정들은 다음과 같습니다.
1. 단일 장애점 (Single Point of Failure - SPOF)
가장 치명적인 위험입니다. 모든 요청이 API 게이트웨이를 거치기 때문에, 게이트웨이가 다운되면 전체 시스템이 마비됩니다. 이는 MSA가 추구하는 장애 격리(Fault Isolation) 원칙에 위배될 수 있습니다. 따라서 API 게이트웨이를 프로덕션 환경에 배포할 때는 반드시 이중화, 삼중화하여 고가용성(High Availability)을 확보해야 합니다. 로드 밸런서를 게이트웨이 앞에 두어 여러 인스턴스로 트래픽을 분산하고, 특정 인스턴스에 장애가 발생해도 서비스가 중단되지 않도록 설계해야 합니다.
2. 개발 병목 현상 (Development Bottleneck)
모든 서비스의 API가 게이트웨이를 통해 노출되다 보니, 새로운 API를 추가하거나 기존 API를 변경할 때마다 게이트웨이 설정을 수정해야 합니다. 만약 게이트웨이를 관리하는 팀이 따로 있다면, 모든 개발팀이 이 팀에 의존하게 되어 개발 프로세스의 병목이 될 수 있습니다. 이를 해결하기 위해서는 게이트웨이 설정을 자동화하고, 각 개발팀이 자신의 서비스와 관련된 라우팅 규칙을 직접 관리할 수 있는 셀프 서비스(Self-service) 환경을 구축하는 것이 중요합니다.
3. 성능 저하 및 지연 시간 증가 (Increased Latency)
클라이언트와 마이크로서비스 사이에 게이트웨이라는 추가적인 네트워크 홉(hop)이 생기는 것이므로, 필연적으로 약간의 지연 시간(latency)이 추가됩니다. 게이트웨이에서 복잡한 로직(요청 변환, 데이터 집계 등)을 많이 수행할수록 이 지연 시간은 더 길어질 수 있습니다. 따라서 고성능의 게이트웨이 솔루션을 선택하고, 불필요한 필터나 로직은 최소화하는 등 성능 튜닝에 신경 써야 합니다. 특히 실시간성이 매우 중요한 서비스의 경우, 게이트웨이를 거치지 않는 별도의 통신 경로를 고려할 수도 있습니다.
4. 게이트웨이 자체의 복잡성 증가
모든 공통 기능과 라우팅 로직이 게이트웨이에 집중되다 보면, 게이트웨이 자체가 또 다른 거대한 모놀리식 애플리케이션처럼 비대해질 수 있습니다. 이는 유지보수를 어렵게 만들고 변화에 대한 대응을 느리게 만듭니다. 게이트웨이의 역할을 명확히 정의하고, 비즈니스 로직이 게이트웨이로 새어 들어오지 않도록 주의해야 합니다. 게이트웨이는 인프라의 영역에 가깝게 유지하고, 도메인별로 별도의 게이트웨이(BFF - Backend for Frontend 패턴 등)를 두는 것도 복잡성을 관리하는 좋은 방법입니다.
Chris Richardson, "Microservices Patterns" API 게이트웨이는 마이크로서비스 아키텍처의 필수적인 부분이지만, 동시에 잠재적인 병목 지점이자 단일 장애점이 될 수 있다. 따라서 높은 가용성과 확장성을 갖도록 설계하는 것이 무엇보다 중요하다.
결론: 게이트웨이를 넘어, 서비스 메시의 시대로
지금까지 우리는 API 게이트웨이가 어떻게 마이크로서비스 아키텍처의 혼돈 속에서 질서를 부여하고, 시스템 전체의 안정성과 개발 생산성을 높이는 핵심적인 설계 패턴인지 깊이 있게 살펴보았습니다. 단일 진입점을 제공하여 클라이언트를 단순화하고, 인증, 로깅, 라우팅과 같은 공통 기능을 중앙에서 처리함으로써 각 서비스가 자신의 핵심 비즈니스 로D직에만 집중할 수 있도록 돕는 API 게이트웨이는 이제 현대적인 MSA 환경에서 선택이 아닌 필수가 되었습니다.
하지만 기술의 발전은 멈추지 않습니다. API 게이트웨이가 주로 외부 클라이언트와 내부 서비스 간의 통신(North-South Traffic)을 관리하는 데 중점을 둔다면, 최근에는 서비스와 서비스 간의 내부 통신(East-West Traffic)을 관리하는 '서비스 메시(Service Mesh)'라는 개념이 주목받고 있습니다. Istio, Linkerd와 같은 서비스 메시 솔루션은 각 마이크로서비스에 '사이드카 프록시(Sidecar Proxy)'를 배치하여 서비스 간의 통신을 가로채고, 서비스 디스커버리, 로드 밸런싱, 서킷 브레이커, 상호 TLS(mTLS) 암호화와 같은 복잡한 기능들을 애플리케이션 코드 변경 없이 인프라 레벨에서 투명하게 제공합니다.
- API Gateway: 주로 외부에서 시스템 내부로 들어오는 트래픽(North-South)을 관리합니다. 클라이언트에게 통합된 API를 제공하고, 인증/인가, 속도 제한 등 경계(Edge)에서의 보안 및 정책을 담당합니다.
- Service Mesh: 주로 시스템 내부의 서비스 간 통신(East-West)을 관리합니다. 서비스 간의 안정적인 통신, 보안, 관찰 가능성(Observability)을 확보하는 데 중점을 둡니다.
API 게이트웨이와 서비스 메시는 서로를 대체하는 관계가 아니라, 각자의 역할과 책임 영역을 가지고 상호 보완하는 관계에 있습니다. 성공적인 마이크로서비스 시스템을 구축하기 위해서는 이 두 가지 패턴을 모두 이해하고, 우리 시스템의 요구사항에 맞게 적절히 조합하여 사용하는 지혜가 필요합니다. API 게이트웨이로 견고한 첫 번째 관문을 세우고, 서비스 메시로 서비스 간의 통신을 촘촘하고 안전하게 엮어 나갈 때, 우리는 비로소 MSA가 약속했던 진정한 가치를 온전히 누릴 수 있을 것입니다.
Post a Comment