Spring Cloud API Gateway

shlee ⚡️·2023년 11월 16일
0

마이크로서비스가 수백 대 있어요 이런 마이크로서비스는 공통점이 많죠

인증, 승인, 로깅, 환율 제한 등등이요

이런 공통 기능은 어디서 구현할까요?

전형적인 솔루션은 API 게이트웨이죠

이전에는 Zuul이라는 게이트웨이가 인기가 많았고 많이들 사용했지만

Netflix가 더이상 지원하지 않으면서 Spring Cloud API Gateway를 사용하게 됐어요

포트 구성

목표

  • 인증, 승인, 로깅, 환율 제한같은 공통 기능 필터를 위한
  • Spring Cloud Api Gateway를 만들어보자

이미지

01. Spring Cloud Api Gateway 생성

  • Devtools
  • Actuator
  • Eureka Discovery Client
    ( Exchange, Conversion에 추가한 의존성 spring-cloud-starter-netflix-eureka-client가 이것임)
  • Gateway
    보안, 모니터링, 메트릭(측정) 및 복원력과 같은 교차 문제를 구현하는 데 도움이 됩니다.

application.properties

  • 어플리케이션 이름, 포트 지정
  • 디폴트 네이밍서버 url을 명시적으로 적어줌
spring.application.name=api-gateway
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

유레카 확인

02. Discovery Locator 활성화 (공통기능)

application.properties

  • spring.cloud.gateway.discovery.locator.enabled=true 추가
    기본 값은 false
    discovery client를 이용해 게이트 웨이도 마이크로 서비스 인스턴스들을 검색 가능하게 하려고 추가 함
spring.application.name=api-gateway
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

spring.cloud.gateway.discovery.locator.enabled=true // 추가

게이트 웨이 테스트

이렇게 공통 기능을 사용하게할 수 있습니다.

NAV에서 어떤 마이크로 서비스도 호출할 수 있죠

API 게이트웨이를 통해 유레카와 함께 등록돼 있어요

인증 같은 걸 구현하고 싶다면 API 게이트웨이에서 구현하면 돼요

마이크로 서비스에서 인증된 것만 허용하면 되죠

모든 인증 논리는 API 게이트웨이에 구현될 수 있죠

따라서 모든 요청은 게이트웨이를 통하여 들어가서 각각의 microservice로 리다이렉트 됨

application.properties

  • spring.cloud.gateway.discovery.locator.lower-case-service-id=true // 추가
  • spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true // 이거도 가능
    기본 값은 false
  • 기본이 대문자라 보기 안좋으니 소문자로 변경한것임
spring.application.name=api-gateway
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true // 추가

  • 이제 소문자로만 요청 가능

03. Gateway를 이용한 경로 탐색

공식문서 참조

ApiGateConfiguration - 생성

@Configuration
public class ApiGateConfiguration {
	
	@Bean
	public RouteLocator gatewayRoute(RouteLocatorBuilder builder) {
		Function<PredicateSpec, Buildable<Route>> routeFunction
			= p -> p.path("/get") // predicateSpec
					.uri("http://httpbin.org:80"); //redirect to
			
		return builder.routes()
				.route(routeFunction)
				.build();
	}
}

httpbin.org 확인

http://httpbin.org/

ApiGateConfiguration - 추가

  • 리다이렉트에 추가로 헤더나 경로 매개 변수를 추가하고 싶다고 해보죠
  • // 추가
  • .filters(f -> f
    .addRequestHeader("MyHeader", "MyURI")
    .addRequestParameter("Param", "MyValue"))
@Configuration
public class ApiGateConfiguration {
	
		@Bean
	public RouteLocator gatewayRoute(RouteLocatorBuilder builder) {
		Function<PredicateSpec, Buildable<Route>> routeFunction
			= p -> p.path("/get")
					.filters(f -> f // 추가
							.addRequestHeader("MyHeader", "MyURI") // 추가
							.addRequestParameter("Param", "MyValue"))// 추가
					.uri("http://httpbin.org:80"); //redirect to
			
		return builder.routes()
				.route(routeFunction)
				.build();
	}
}

ApiGateConfiguration - 수정 및 추가

  • http://localhost:8000/currency-exchange/currency-exchange/from/USD/to/INR
  • 이 url이 너무 기니까 커스텀 route 추가
  • .route(p -> p.path("/currency-exchange/**") // /currency-exchange/** 오는 모든 경로를
  • .uri("lb://currency-exchange")) // 로드밸런싱(부하분산) 하면서 유레카야 애플리케이션 이름 currency-exchange인거 찾아서 리다이렉트 시켜줘
  • uri: lb://{service-name} // 유레카를 통한 방식
@Configuration
public class ApiGateConfiguration {
	
	@Bean
	public RouteLocator gatewayRoute(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(p -> p.path("/get")
						.filters(f -> f
								.addRequestHeader("MyHeader", "MyURI")
								.addRequestParameter("Param", "MyValue"))
						.uri("http://httpbin.org:80"))  //redirect to
				.route(p -> p.path("/currency-exchange/**")  // 추가
							.uri("lb://currency-exchange"))  // 추가
				.build();
	}
}

application.properties

  • discovery.locator 옵션 주석 또는 삭제
  • spring.cloud.gateway.discovery.locator.enabled=true
  • spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.application.name=api-gateway
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
// spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
// spring.cloud.gateway.discovery.locator.enabled=true
// spring.cloud.gateway.discovery.locator.lower-case-service-id=true


이제 Exchange 마이크로서비스를 직접적으로 요청하는
http://localhost:8000/currency-exchange/from/USD/to/INR 포트번호 8000번이 아닌
8765번 게이트웨이 http://localhost:8765/currency-exchange/from/USD/to/INR로 요청해도 똑같이 동작

ApiGateConfiguration - 수정 및 추가

  • /currency-conversion 도 마찬가지로 리다이렉트 추가
  • /currency-conversion-feign 도 마찬가지로 리다이렉트 추가
@Configuration
public class ApiGateConfiguration {
	
	@Bean
	public RouteLocator gatewayRoute(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(p -> p.path("/get")
						.filters(f -> f
								.addRequestHeader("MyHeader", "MyURI")
								.addRequestParameter("Param", "MyValue"))
						.uri("http://httpbin.org:80"))  //redirect to
				.route(p -> p.path("/currency-exchange/**")
							.uri("lb://currency-exchange"))
				.route(p -> p.path("/currency-conversion/**") // 추가
						.uri("lb://currency-conversion")) // 추가
				.route(p -> p.path("/currency-conversion-feign/**") // 추가
						.uri("lb://currency-conversion")) // 추가
				.build();
	}
}

ApiGateConfiguration - 수정 및 추가

  • /abc 를 받으면 /currency-conversion-feign로 filter를 통해 rewritePath해서 요청하게 수정
  • "/abc/(?<segment>.*)"의 뜻은 /abc/**인데 **부분의 url을 segment 정규식 변수로 받은거고
  • "/currency-conversion-feign/${segment}" 에서 ${segment} 정규식 변수를 사용한 것
@Configuration
public class ApiGateConfiguration {
	
	@Bean
	public RouteLocator gatewayRoute(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(p -> p.path("/get")
						.filters(f -> f
								.addRequestHeader("MyHeader", "MyURI")
								.addRequestParameter("Param", "MyValue"))
						.uri("http://httpbin.org:80"))  //redirect to
				.route(p -> p.path("/currency-exchange/**")
							.uri("lb://currency-exchange"))
				.route(p -> p.path("/currency-conversion/**")
						.uri("lb://currency-conversion"))
				.route(p -> p.path("/currency-conversion-feign/**")
						.uri("lb://currency-conversion"))
				.route(p -> p.path("/abc/**") // 추가
						.filters(f -> f.rewritePath( // 추가
								"/abc/(?<segment>.*)", // 정규식 추가
								"/currency-conversion-feign/${segment}")) // 추가
						.uri("lb://currency-conversion")) // 추가
				.build();
	}
}

이제 /abc로 요청하도 /currency-conversion-feign로 rewrite해서 요청하게 된다.

04. Gateway 로깅 필터 구현 (전역 필터)

LoggingFilter

  • implements GlobalFilter
@Component
public class LoggingFilter implements GlobalFilter{

	private Logger logger = LoggerFactory.getLogger(LoggerFinder.class);
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		logger.info("Path of the request received -> {}", exchange.getRequest().getPath());
		return chain.filter(exchange);
	}

}

모든 요청에 대한 인증 같은 걸 구현하고 싶다면

여기가 그걸 구현하기에 알맞은 곳이죠


Spring Cloud Gateway의 중요한 특징은 요청의 속성을 맞출 수 있다는 점이에요.

앞서 경로에 근거해 매치하는 법을 봤죠 게다가 많은 다양한 것들을 기반으로 매치시킬 수 있어요

호스트에 따라 매치할 수 있고

요청 메서드에 따라 매치할 수 있어요

쿼리 매개 변수 기반으로 매치할 수도 있고

여러 매개 변수 기반으로 요청을 매치할 수도 있어요

어떤 요청 속성에서도 라우트를 매치할 수 있고 Predicate와 Filters도 정의할 수 있죠

profile
흔들리지 말고.. 묵묵히

0개의 댓글