마이크로서비스 아키텍처(MSA)는 여러 독립적인 서비스가 상호작용하여 복잡한 시스템을 형성하는 현대의 소프트웨어 설계 패턴입니다.
MSA 아키텍처는 서비스의 독립성과 확장성이라는 큰 장점을 제공하지만, 그에 따라 발생하는 여러 가지 복잡성 문제를 해결해야 합니다.
특히, 다양한 서비스가 상호작용하면서 발생할 수 있는 문제 중 하나가 클라이언트와 서비스 간의 요청 및 응답 관리입니다. 여기서 API 게이트웨이가 중요한 역할을 합니다.
이번 포스팅에서는 제가 공부한 내용을 바탕으로 API 게이트웨이의 역할과 필요성에 대해 정리하고, Spring Cloud API 게이트웨이의 주요 기능과 동작확인을 해보겠습니다.
API 게이트웨이 (Spring Cloud Gateway)란❓
Spring Cloud Gateway를 알아보기에 앞서 먼저 API 게이트웨이에 대해서 먼저 알아야 합니다.
API 게이트웨이는 클라이언트의 요청을 받아 백엔드 서비스로 라우팅 하고, 다양한 부가 기능을 제공하는 중간 서버이며, MSA에서 클라이언트와 다양한 서비스 간의 요청을 관리하고 조정하는 중요한 컴포넌트입니다.
API 게이트웨이는 다음과 같은 주요 역할을 수행합니다.
- 라우팅
- 클라이언트의 요청을 적절한 백엔드 서비스로 전달
- 인증 및 권한 부여
- 요청의 인증 및 권한을 검증
- 로드 밸런싱
- 여러 서비스 인스턴스 간의 부하 분산
- 모니터링 및 로깅
- 요청과 응답을 기록하고 모니터링
- 요청 및 응답 변환
- 요청과 응답의 포맷을 변환하거나 필터링
Spring Cloud Gateway란❓
Spring Cloud Gateway는 마이크로서비스 아키텍처(MSA)에서 API 게이트웨이의 역할을 수행하는 오픈 소스 프레임워크입니다.
Spring Framework와 Spring Boot의 강력한 기능을 기반으로 하며, 클라우드 환경에서의 API 관리와 서비스 간의 요청 및 응답 처리에 최적화되어 있습니다.
주요 목적은 요청 라우팅, 필터링, 모니터링, 보안 및 다양한 API 관리 기능을 제공하여 마이크로서비스 아키텍처의 복잡성을 줄이고, 시스템의 신뢰성을 높이는 것입니다.
Spring Cloud Gateway의 주요 특징은 다음과 같습니다.
1. 라우팅
- 요청을 다양한 백엔드 서비스로 라우팅 할 수 있습니다.
- 라우팅 규칙은 URL 패턴, HTTP 메서드, 헤더, 쿼리 파라미터 등 다양한 조건에 따라 정의할 수 있습니다.
2. 필터링
- 요청과 응답을 처리할 수 있는 필터를 적용하여, 인증, 권한 부여, 로깅, 요청 변환 등 다양한 기능을 수행할 수 있습니다.
- Spring Cloud Gateway는 기본 제공 필터 외에도 사용자 정의 필터를 지원합니다.
3. 로드 밸런싱
- 내부적으로 Spring Cloud LoadBalancer를 사용하여 여러 서비스 인스턴스 간의 부하 분산을 지원합니다.
- 이를 통해 서비스의 가용성을 높이고 성능을 최적화할 수 있습니다.
4. 보안
- 인증 및 권한 부여를 지원하여, 클라이언트 요청의 보안을 강화할 수 있습니다.
- Spring Security와 통합되어 보안 설정을 중앙에서 관리할 수 있습니다.
5. 모니터링 및 로깅
- 요청과 응답에 대한 로그를 기록하고, 다양한 메트릭을 수집하여 시스템의 상태를 모니터링할 수 있습니다.
- Micrometer와 Prometheus와 같은 모니터링 도구와 통합하여 실시간 모니터링이 가능합니다.
6. 서킷 브레이커 및 재시도
- 서킷 브레이커 패턴과 Retry 메커니즘을 지원하여, 실패한 요청을 자동으로 재시도하거나 장애가 발생한 경우 대체 응답을 제공할 수 있습니다.
- Resilience4j와 통합하여 이러한 기능을 활용할 수 있습니다.
7. 다양한 프로토콜 지원
- HTTP, HTTPS뿐만 아니라 WebSocket과 같은 다양한 프로토콜을 지원하여, 다양한 형태의 클라이언트와 서버 간의 통신을 처리할 수 있습니다.
Spring Cloud Gateway 설정
의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
Spring Cloud Gateway를 사용하려면 Spring Boot 애플리케이션에 의존성을 추가해야 합니다.
라우팅 설정
server:
port: 19091 # 게이트웨이 서비스가 실행될 포트 번호
spring:
main:
web-application-type: reactive # Spring 애플리케이션이 리액티브 웹 애플리케이션으로 설정됨
application:
name: gateway-service # 애플리케이션 이름을 'gateway-service'로 설정
cloud:
gateway:
routes: # Spring Cloud Gateway의 라우팅 설정
- id: order-service # 라우트 식별자
uri: lb://order-service # 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/order/** # /order/** 경로로 들어오는 요청을 이 라우트로 처리
- id: product-service # 라우트 식별자
uri: lb://product-service # 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/product/** # /product/** 경로로 들어오는 요청을 이 라우트로 처리
discovery:
locator:
enabled: true # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka 서버의 URL을 지정
위와 같이 application.yml 또는 application.properties 파일에서 기본적인 라우팅 및 필터 설정을 추가합니다.
1. 서버 포트 설정
- server.port 설정은 Spring Cloud Gateway가 실행될 포트 번호를 지정합니다
2. 애플리케이션 설정
- spring.main.web-application-type:
- 애플리케이션의 웹 환경 유형을 설정하는 부분입니다.
- reactive : Spring 애플리케이션이 리액티브 웹 애플리케이션으로 동작하도록 설정합니다.
- spring.application.name:
- gateway-service : 애플리케이션의 이름을 설정합니다.
- 이 이름은 Eureka 서버와 같은 서비스 디스커버리 시스템에서 사용됩니다.
3. Spring Cloud Gateway의 라우팅 설정
- routes: 게이트웨이의 라우팅 설정을 정의합니다.
- id
- 라우트의 고유 식별자입니다. 각 라우트는 고유한 ID를 가지며, 이를 통해 라우트를 식별합니다.
- uri
- 요청을 전달할 백엔드 서비스의 URI입니다.
- lb:// 접두사 : 로드 밸런싱을 의미하며, 서비스 이름(order-service 또는 product-service)은 서비스 디스커버리 시스템에서 관리됩니다.
- predicates
- 요청이 이 라우트로 전달되기 위한 조건입니다.
- Path=/order/** ➡️ /order/로 시작하는 모든 경로에 대한 요청을 order-service로 라우팅 합니다.
- Path=/product/** ➡️ /product/로 시작하는 모든 경로를 product-service로 라우팅 합니다.
- 요청이 이 라우트로 전달되기 위한 조건입니다.
- id
- discovery.locator.enabled
- 서비스 디스커버리를 통해 동적으로 라우트를 생성할지 여부를 설정합니다.
- true로 설정하면, Eureka와 같은 서비스 디스커버리 서버에 등록된 서비스를 자동으로 탐지하고 라우팅 할 수 있습니다.
4. Eureka 클라이언트 설정
- Eureka 서버의 URL을 지정하여 Spring Cloud Gateway가 Eureka에서 서비스 정보를 조회할 수 있게 합니다.
Gateway가 Eureka Client로 설정된 이유는 라우트 식별자를 서비스 이름으로 사용하기 때문입니다.
서비스 이름을 기반으로 라우팅 하려면 해당 서비스의 호스트를 알아야 합니다. 이를 위해 Eureka 서버에 요청을 보내어 등록된 서비스 목록을 확인하고, 서비스의 위치를 파악할 수 있습니다. 따라서 Gateway도 Eureka Client로 등록되어야 합니다.
Spring Cloud Gateway 필터링 _ 필터 종류
- Global Filter
- 모든 요청에 대해 작동하는 필터로, 전역적으로 적용됩니다.
- 예를 들어, 모든 요청의 로깅이나 공통적인 인증 작업을 수행할 때 사용됩니다.
- Gateway Filter
- 특정 라우트에만 적용되는 필터로, 라우트에 지정된 필터로서, 요청과 응답의 처리를 세밀하게 조정할 수 있습니다.
- 예를 들어, 특정 서비스에만 적용할 요청 변환이나 헤더 추가 작업을 수행할 때 사용됩니다.
Spring Cloud Gateway 필터링 _ 필터 구현
Spring Cloud Gateway에서 필터를 구현하려면 GlobalFilter 또는 GatewayFilter 인터페이스를 구현하고, filter 메서드를 오버라이드하여 필터링 로직을 정의해야 합니다.
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Mono;
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 필터 로직
System.out.println("Global filter applied");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1; // 필터의 우선순위 설정
}
}
- implements GlobalFilter, Ordered
- GlobalFilter와 Ordered 인터페이스를 구현합니다.
- GlobalFilter는 필터 인터페이스를, Ordered는 우선순위 설정을 위해 구현합니다.
Ordered란❓
Ordered는 Spring Framework에서 사용되는 인터페이스로, 객체의 우선순위를 지정할 때 활용됩니다.
- 우선순위 설정
- Ordered 인터페이스를 구현하면, getOrder() 메서드를 통해 객체의 우선순위를 설정할 수 있습니다.
- 이 메서드는 int 값을 반환하며, 값이 낮을수록 우선순위가 높습니다.
- 우선순위 값: 기본적으로, Spring의 Ordered 인터페이스는 몇 가지 상수를 제공합니다.
- Ordered.HIGHEST_PRECEDENCE: 가장 높은 우선순위 (Integer.MIN_VALUE).
- Ordered.LOWEST_PRECEDENCE: 가장 낮은 우선순위 (Integer.MAX_VALUE).
필터 시점별 종류
Spring Cloud Gateway에서 필터는 요청 처리 과정에서 특정 시점에 동작합니다. 필터는 크게 두 가지 시점에서 작동합니다.
1. 프리 필터(Pre-filter)
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class PreFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Pre-filter logic
System.out.println("Pre-filter applied");
return chain.filter(exchange);
}
}
요청이 백엔드 서비스로 전달되기 전에 실행됩니다. 주로 요청을 변환하거나, 인증 및 권한 부여를 처리하는 데 사용됩니다.
즉, pre 시점 로직은 GatewayFilter 또는 GlobalFilter에서 chain.filter(exchange) 호출 전에 수행됩니다.
용도
- 요청의 헤더, 파라미터를 수정하거나 추가
- 인증 및 권한 검증
- 요청 로깅
- 요청 변환
2. 포스트 필터(Post-filter)
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class PostFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Post-filter logic
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
System.out.println("Post-filter applied");
}));
}
}
백엔드 서비스로부터 응답이 돌아온 후 클라이언트에게 전달되기 전에 실행됩니다. 주로 응답을 수정하거나 응답에 대한 로깅을 처리하는 데 사용됩니다.
post 시점 로직은 chain.filter(exchange) 호출 후, 응답이 클라이언트로 반환되기 전에 수행됩니다.
용도
- 응답 헤더, 바디를 수정하거나 추가
- 응답 로깅
- 응답 변환
SpringCloud와 Gateway
Spring Cloud Gateway는 Spring Cloud Netflix 패키지의 일부로, 마이크로서비스 아키텍처(MSA)에서의 API 게이트웨이 역할을 수행합니다.
이는 클라이언트의 요청을 다양한 백엔드 서비스로 라우팅 하고, 필터링, 인증, 모니터링 등 다양한 기능을 제공합니다.
Eureka는 Netflix에서 제공하는 서비스 디스커버리 서버로, 서비스 인스턴스를 등록하고 조회할 수 있는 기능을 제공합니다.
Spring Cloud Gateway는 Eureka와 쉽게 통합되어, Eureka를 통해 동적으로 서비스 인스턴스를 조회하고 이를 기반으로 로드 밸런싱과 라우팅을 수행할 수 있습니다.
SpringCloud와 Gateway 동작 확인
Gateway를 사용하여 로드 밸런싱이 어떻게 진행되는지 동작을 확인해 보겠습니다.
Order Application
Product Application
Spring Cloud Gateway
위와 같이 Eureka서버를 확인해 보면 정상적으로 Eureka서버에 각 서비스 정보가 있습니다.
- Gateway → 19091
- Order → 19092
- Product → 19093, 19094
application.yml에서 설정한 대로 다음과 같이 동작할 것을 예상할 수 있습니다.
- /order/ 아래로 들어오는 모든 경로는 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
- /product/ 아래로 들어오는 모든 경로는 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
먼저 사용자가 Gateway로 접속하여 Gateway에서 Order 서비스를 호출하는 것을 확인해 보겠습니다.
Gateway를 통해 요청을 보내면, http://localhost:19091/order URL을 호출했을 때, 'Order detail'이라는 응답을 받을 수 있습니다.
이는 요청이 Spring Cloud Gateway를 통해 order-service로 라우팅 되어, 최종적으로 OrderApplication에서 데이터를 가져오는 과정을 통해 응답이 전달된다는 것을 의미합니다."
결과를 확인해 보면, 요청이 Gateway 쪽으로 http://localhost:19091/order요청이 들어왔고, OrderApplication에서 데이터를 정상적으로 가져와 HTTP 응답 코드: 200 : OK, 에러 없이 전송 성공되었다는 것을 확인할 수 있습니다.
/product/ 아래로 들어오는 모든 경로는 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅 될 것입니다.
즉, product를 호출하면 각 port가 번갈아가며 호출되는 것을 예상할 수 있습니다.
사용자가 Product를 호출할 때 Gateway의 로드밸런싱 동작을 확인해 보겠습니다.
- "localhost:19091/product"호출 → Gateway가 "19094"으로 로드 밸런싱
- "localhost:19091/product"호출 → Gateway가 "19093"으로 로드 밸런싱
위와 같이 Gateway를 통해서 product를 호출할 때 라운드 로빈 방식으로 로드밸런서가 동작하는 모습을 확인할 수 있습니다.