[MSA알아보기] API 게이트웨이 (Spring Cloud Gateway)

차차의 개발일기·2024년 8월 7일
0

msa

목록 보기
6/7

이번 블로그에서는 API Gateway에 대해 알아보고 해당 Spring Cloud Gatewayfmf 구현해보겠습니다. 자세한 내용은 GIT-API-gateway 브랜치에서 알아보실 수 있습니다.

1. API Gateway

1.1 API Gateway란?

API 게이트웨이(패턴) 는 마이크로서비스 관리 / 운영을 위한 플랫폼 패턴이며 해당 패턴에 필요한 기능들을 제공하는 서버를 일컫습니다.
API 게이트웨이는 개별 서비스의 앞 단에서 모든 서비스들의 엔드포인트를 단일화하고 다음과 같은 필수 기능 요소들을 제공합니다.

1.2 주요기능

  • 인증과 인가 : 모든 서비스들에 대한 접근에 있어서 단일 진입점에서 인증과 인가 처리를 진행
  • API 요청 로드밸런싱 및 라우팅 : API 요청을 식별하여 적절한 마이크로서비스로 전달
  • QoS(Quality of Service) : 안정적인 서비스 제공 및 네트워크 품질을 관리하며 사용자 / 클라이언트 / API 단위로 접속 제어
  • 로깅 및 모니터링 : API 요청에 대한 로깅 / 모니터링 기능 지원
  • 입력 유효성 검사 : API 요청의 적절한 형식과 필수 데이터 포함 여부를 식별 및 관리
  • 요청 및 응답 변환: 요청과 응답을 변환하거나 필터링

1.3 장단점

API 게이트웨이를 사용함으로서 다음과 같은 장점을 얻을 수 있습니다.

1.3.1 장점

  • 애플리케이션의 내부 구조를 캡슐화 : 클라이언트는 특정 서비스를 호출하지 않고 단순히 게이트웨이와 통신하며, API 게이트웨이는 각 종류의 클라이언트에 특정 API를 제공
  • 클라이언트와 애플리케이션 간의 왕복 횟수가 감소하며, 클라이언트 코드 단순화

1.3.2 단점

  • 개발, 배포 및 관리해야 하는 지점이 증가
  • 각 마이크로서비스의 Endpoint를 노출하기 위해 API 게이트웨이를 업데이트해야 하는데 이로 인해 개발 병목 현상이 발생할 수 있음

그러나 단점이 있음에도 장점이 뚜렷하기 때문에 오늘날 마이크로서비스 아키텍처에서 API 게이트웨이는 반드시 필요한 요소로 존재하고 있습니다.

2. Spring Cloud Gateway

2.1 Spring Cloud Gateway란?

Spring Cloud Gateway는 Spring Framework에서 제공하는 오픈 소스 기반의 Gateway 서비스입니다.
Spring Cloud Gateway 이전에는 Spring Cloud Zuul이 있었으며 Zuul의 패치가 중단됨에 따라 Spring Cloud Gateway로의 이전이 이뤄졌다고 합니다.

  • Spring Cloud Gateway는 Spring 프로젝트의 일환으로 개발된 API 게이트웨이로, 클라이언트 요청을 적절한 서비스로 라우팅하고 다양한 필터링 기능을 제공합니다.
  • Spring Cloud Netflix 패키지의 일부로, 마이크로서비스 아키텍처에서 널리 사용됩니다.

2.2 주요 특징

  • 동적 라우팅: 요청의 URL 패턴에 따라 동적으로 라우팅
  • 필터링: 요청 전후에 다양한 작업을 수행할 수 있는 필터 체인 제공
  • 모니터링: 요청 로그 및 메트릭을 통해 서비스 상태 모니터링
  • 보안: 요청의 인증 및 권한 검증

2.3 Spring Cloud Gateway 필터링

2.3.1 종류

  • Global Filter: 모든 요청에 대해 작동하는 필터
  • Gateway Filter: 특정 라우트에만 적용되는 필터

2.3.2 구현 방법

필터를 구현하려면 GlobalFilter 또는 GatewayFilter 인터페이스를 구현하고, filter 메서드를 오버라이드해야 합니다.

2.3.3 주요 객체

  • Mono
    • Mono는 리액티브 프로그래밍에서 0 또는 1개의 데이터를 비동기적으로 처리합니다.
    • Mono<Void>는 아무 데이터도 반환하지 않음을 의미합니다.
  • ServerWebExchange
    • ServerWebExchange는 HTTP 요청과 응답을 캡슐화한 객체입니다.
    • exchange.getRequest()로 HTTP 요청을 가져옵니다.
    • exchange.getResponse()로 HTTP 응답을 가져옵니다.
  • GatewayFilterChain
    • GatewayFilterChain은 여러 필터를 체인처럼 연결합니다.
    • chain.filter(exchange)는 다음 필터로 요청을 전달합니다.

2.3.4 필터 시점

2.3.4.1 Pre 필터

Pre 필터는 요청이 처리되기 전에 실행됩니다. 따라서 Pre 필터에서는 요청을 가로채고 필요한 작업을 수행한 다음, 체인의 다음 필터로 요청을 전달합니다. 이때, 추가적인 비동기 작업을 수행할 필요가 없기 때문에 then 메서드를 사용할 필요가 없습니다.

```java

@Component
public class PreFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 요청 로깅
        System.out.println("Request: " + exchange.getRequest().getPath());
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {  // 필터의 순서를 지정합니다.
        return -1;  // 필터 순서를 가장 높은 우선 순위로 설정합니다.
    }
}

```
2.3.4.1 Post 필터

Post 필터는 요청이 처리된 후, 응답이 반환되기 전에 실행됩니다. Post 필터에서는 체인의 다음 필터가 완료된 후에 실행되어야 하는 추가적인 작업을 수행해야 합니다. 따라서 chain.filter(exchange)를 호출하여 다음 필터를 실행한 후, then 메서드를 사용하여 응답이 완료된 후에 실행할 작업을 정의합니다.

    
    @Component
    public class PostFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // 응답 로깅
                System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
            }));
        }
    
        @Override
        public int getOrder() {
            return -1;
        }
    }
    

3. 구현

3.1 order project

3.1.1 build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

3.1.2 controller

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping
    public String getOrder() {
        return "Order details";
    }
}

3.1.3. yml

server:
  port: 19092

spring:
  application:
    name: order-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

3.2 product

3.2.1 build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

3.1.2 controller

@RestController
public class ProductController {

    @Value("${server.port}") // 애플리케이션이 실행 중인 포트를 주입받습니다.
    private String serverPort;

    @GetMapping("/product")
    public String getProduct() {
        return "Product info!!!!! From port : " + serverPort;
    }
}

3.1.3. yml

server:
  port: 19093

spring:
  application:
    name: product-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka

여기서 프로덕트는 intellij의 editConfiguration - vmOption을 사용하여 19093, 19094로 설정하여 로드밸런싱까지 확인해보겠습니다.

3.3 gateway

3.3.1 프로젝트 생성


위 디펜던시를 가지고 이쓴ㄴ 프로젝트를 생성합니다.

3.3.2 preFilter

@Component
public class CustomPreFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest response = exchange.getRequest();
        logger.info("Pre Filter: Request URI is " + response.getURI());
        // Add any custom logic here

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

3.3.3 postFilter

@Component
public class CustomPostFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPostFilter.class.getName());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            logger.info("Post Filter: Response status code is " + response.getStatusCode());
            // Add any custom logic here
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

3.3.4 yml

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을 지정

4. 실행

4.1 순서

eureka server ⇒ gateway ⇒ order ⇒ product 순으로 어플리케이션을 실행합니다.

4.2 인스턴스 확인

http://localhost:{서버 설정 포트}에 접속하여 각 인스턴스를 확인합니다.

4.3 order 서비스 호출

http://localhost:{gateway 설정 포트}/order 로 접속하여 게이트웨이에서 order 서비스를 호출하는 것을 확인 할 수 있습니다.

4.4 product 서비스 호출

http://localhost:{gateway 설정 포트}/product 를 여러번 호출 하면서 포트가 달라지는 것을 확인합니다. 이를통해 로드밸런싱이 동작함을 확인합니다.

4.5 gateway 로그 확인

게이트웨이의 로그를 보면 호출 할때마다 필터가 동작하는것을 확인 할 수 있습니다.


이상으로 Spring cloud gateway 구현을 마무리 하겠습니다.


🔗Github Repository 링크

https://github.com/junghojune/MSA-Practice

profile
1년차 개발자 차차입니다.

0개의 댓글