반응형

본 내용은 인프런의 이도원 님의 강의 "Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)" 내용을 바탕으로 정리한 내용입니다.

API Gateway Service란?

  • API Gateway는 규모와 관계없이 REST 및 WebSocket API를 생성, 게시, 유지, 모니터링 및 보호하기 위한 AWS 서비스이다.
  • 이 서비스를 사용하면 API 개발자가 AWS 또는 다른 웹 서비스를 비롯해 AWS 클라우드에 저장된 데이터에 액세스하는 API를 생성할 수 있다.

API Gateway Service의 역활

  • 인증 및 권한 부여
  • 서비스 검색 통합
  • 응답 캐싱
  • 정책, 회로 차단기 및 QoS 다시 시도
  • 속도 제한
  • 부하 분산
  • 로깅, 추적, 상관 관계
  • 헤더, 쿼리 문자열 및 청구 변환
  • IP 허용 목록에 추가

Custom Filter

Custom Pre filter

  • 요청이 들어올 때 요청 id 를 로깅한다.

Custom Post filter

  • 요청이 처리된 후 응답 코드를 로깅한다.

@ GateWayService-Project

Custom Filter

package com.example.gateway_service.filter;  

import lombok.extern.slf4j.Slf4j;  
import org.springframework.cloud.gateway.filter.GatewayFilter;  
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;  
import org.springframework.http.server.reactive.ServerHttpRequest;  
import org.springframework.http.server.reactive.ServerHttpResponse;  
import org.springframework.stereotype.Component;  
import reactor.core.publisher.Mono;  

@Component  
@Slf4j  
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {  
    public CustomFilter(){  
        super(Config.class);  
    }  

    /**  
     * @param config  
     * @return  
     */  
    @Override  
    public GatewayFilter apply(Config config){  
        // Custom Pre Filter  
        return ((exchange, chain) -> {  
            // ServletHttp 를 비동기 방식에서는 ServerHttp 로 사용한다.  
            ServerHttpRequest request = exchange.getRequest();  
            ServerHttpResponse response = exchange.getResponse();  

            log.info("Custom PRE filter: request id -> {}", request.getId());  
            // Custom Post Filter.Suppose we can call error response handler based on error code.  
            return chain.filter(exchange).then(Mono.fromRunnable(()->{  
                log.info("Custom POST filter: response code -> {}", response.getStatusCode());  
            }));  
        });  
    };  

    // Mono : 이 객체는 웹 플럭스라고 해서 spring 5에서 추가된 기능, 비동기 방식의 서버 지원 (단일값으로)  
    public static class Config{  
        // Put the configuration properties  
    }  
}
  • apply 메서드를 오버라이드하여 요청과 응답을 처리하는 커스텀 로직을 정의
    • Pre Filter: 요청이 다운스트림 서비스로 전달되기 전에 가로채서 요청 ID를 로그로 남긴다. log.info("Custom PRE filter: request id -> {}", request.getId());를 사용하여 요청 ID를 출력한다.
    • Post Filter: 요청이 처리되고 응답이 생성된 후, 응답 상태 코드를 로그로 남긴다. log.info("Custom POST filter: response code -> {}", response.getStatusCode());를 사용하여 응답 상태 코드를 출력한다.
  • Config 클래스는 빈 상태로 정의되었지만, 필터의 설정을 추가하고자 할 때 확장할 수 있다.

application.yml

  • 기존 필터값 주석 처리 후 CutomFilter 추가
server:  
  port: 8080  

eureka:  
  client:  
    register-with-eureka: false  
    fetch-registry: false  
    service-url:  
      defaultZone: http://localhost:8761/eureka  

spring:  
  application:  
    name: gateway-service  
  cloud:  
    gateway:  
      routes:  
        - id: first-service  
          uri: http://localhost:8081/  
          predicates:  
            - Path=/first-service/**  
          filters:  
#            - AddRequestHeader=first-request, first-request-header2  
#            - AddResponseHeader=first-response, first-response-header2  
             - CustomFilter  
        - id: second-service  
          uri: http://localhost:8082/  
          predicates:  
            - Path=/second-service/**  
          filters:  
#            - AddRequestHeader=second-request, second-request-header2  
#            - AddResponseHeader=second-response, second-response-header2  
             - CustomFilter
  • spring.cloud.gateway.routes 섹션에서는 두 개의 서비스(first-servicesecond-service)에 대한 라우팅을 정의한다.
  • 각 서비스에 대해 CustomFilter를 필터로 추가하며, 기존의 기본 필터들(AddRequestHeader, AddResponseHeader)은 주석 처리되어 있다.
  • 각 라우트의 uri는 각각 http://localhost:8081/http://localhost:8082/로 설정되어 있다.
  • CustomFilter가 요청을 처리하기 전후로 로그를 남기며, 요청 ID와 응답 상태 코드를 출력한다.

@ FirstService-Project / SecondService-Project 수정

FirstServiceController

@GetMapping("/check")  
public String check() {  
    return "Hi, there. This is a message from First Service.";  
}
  • check 메소드는 HTTP GET 요청이 /check 경로로 들어올 때, "Hi, there. This is a message from First Service."라는 메시지를 반환한다.

SecondServiceController

@GetMapping("/check")  
public String check() {  
    return "Hi, there. This is a message from Second Service.";  
}
  • check 메소드는 HTTP GET 요청이 /check 경로로 들어올 때, "Hi, there. This is a message from Second Service."라는 메시지를 반환한다.

결과

그림 1) Postman Test

  • http://localhost:8080/first-service/check 경로를 호출하면 FirstServiceControllercheck 메소드가 호출되어 "Hi, there. This is a message from First Service."라는 메시지가 응답된다.
  • http://localhost:8080/second-service/check 경로를 호출하면 SecondServiceControllercheck 메소드가 호출되어 "Hi, there. This is a message from Second Service."라는 메시지가 응답된다.

Gateway-service log

Custom PRE filter: request id -> 37fc1887-1
Custom POST filter: response code -> 200 OK
  • Spring Cloud Gateway는 first-servicesecond-service의 요청을 처리한다.
  • 각 서비스는 /check 경로에서 특정 메시지를 반환한다.
  • 요청이 처리되는 동안 CustomFilter는 요청 ID와 응답 상태 코드를 로그로 남긴다.
    • PRE 필터: 요청 ID 출력
    • POST 필터: 응답 코드 출력

Global Filter

  • 공통적인 필터를 설정한다.
  • application.yml의 preLogger, postLogger 값을 통해 baseMessage를 화면에 출력한다.

@ Gateway-service Project

application.yml

server:  
  port: 8080  

eureka:  
  client:  
    register-with-eureka: false  
    fetch-registry: false  
    service-url:  
      defaultZone: http://localhost:8761/eureka  

spring:  
  application:  
    name: gateway-service  
  cloud:  
    gateway:  
      default-filters:  
        - name: GlobalFilter  
          args:  
            baseMessage: Spring Cloud Gateway GlobalFilter  
            preLogger: true  
            postLogger: true  
      routes:  
        - id: first-service  
          uri: http://localhost:8081/  
          predicates:  
            - Path=/first-service/**  
          filters:  
#            - AddRequestHeader=first-request, first-request-header2  
#            - AddResponseHeader=first-response, first-response-header2  
             - CustomFilter  
        - id: second-service  
          uri: http://localhost:8082/  
          predicates:  
            - Path=/second-service/**  
          filters:  
#            - AddRequestHeader=second-request, second-request-header2  
#            - AddResponseHeader=second-response, second-response-header2  
             - CustomFilter
  • default-filters: 공통 필터인 GlobalFilter를 추가하여 모든 요청에 대해 기본적인 필터 작업을 수행하도록 설정한다. 이를 통해 GlobalFilter는 모든 경로에서 공통적으로 동작하며, CustomFilter는 각 서비스별로 추가적으로 설정된다.

GlobalFilter 클래스

package com.example.gateway_service.filter;  

import lombok.Data;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.cloud.gateway.filter.GatewayFilter;  
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;  
import org.springframework.http.server.reactive.ServerHttpRequest;  
import org.springframework.http.server.reactive.ServerHttpResponse;  
import org.springframework.stereotype.Component;  
import reactor.core.publisher.Mono;  

@Component  
@Slf4j  
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {  

    public GlobalFilter() {super(Config.class);}  

    @Override  
    public GatewayFilter apply(Config config){  
        return  ((exchange, chain) -> {  
            ServerHttpRequest request = exchange.getRequest();  
            ServerHttpResponse response = exchange.getResponse();  

            log.info("Global Filter baseMessage: {}", config.getBaseMessage());  
            // Pre Filter  
            if(config.isPreLogger()) {  
                log.info("Global Filter Start: request id -> {}", request.getId());  
            }  

            return chain.filter(exchange).then(Mono.fromRunnable(()->{  
                // Post Filter  
                if(config.isPostLogger()){  
                    log.info("Global Filter End: reponse code -> {}", response.getStatusCode());  
                }  
            }));  
        });  
    }  

    @Data  
    public static class Config {  
        private String baseMessage;  
        private boolean preLogger;  
        private boolean postLogger;  
    }  
}
  • GlobalFilterSpring Cloud Gateway전역 필터로, 모든 요청에 대해 공통적으로 필터 작업을 수행한다. baseMessage, preLogger, postLogger와 같은 설정을 받아서 로그를 기록한다.
  • preLoggerpostLogger 옵션을 통해 각각 요청 처리 전후에 로그를 남긴다.
  • http://localhost:8080/first-service/check 경로를 호출하면, CustomFilter와 함께 GlobalFilter가 동작하고, 그에 따른 로그가 남는다.
  • GlobalFilter는 모든 요청에 대해 기본 메시지와 함께 Pre 로그Post 로그를 기록한다.
  • CustomFilter는 서비스별 필터로 요청 ID와 응답 상태 코드를 출력한다.

결과

Global Filter baseMessage: Spring Cloud Gateway GlobalFilter
Global Filter Start: request id -> 87025ee0-1
Custom PRE filter: request id -> 87025ee0-1
Custom POST filter: response code -> 200 OK
Global Filter End: reponse code -> 200 OK

Logging Filter

@GateWayService-Project

application.yml

server:  
  port: 8080  

eureka:  
  client:  
    register-with-eureka: false  
    fetch-registry: false  
    service-url:  
      defaultZone: http://localhost:8761/eureka  

spring:  
  application:  
    name: gateway-service  
  cloud:  
    gateway:  
      default-filters:  
        - name: GlobalFilter  
          args:  
            baseMessage: Spring Cloud Gateway GlobalFilter  
            preLogger: true  
            postLogger: true  
      routes:  
        - id: first-service  
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**  
          filters:  
#            - AddRequestHeader=first-request, first-request-header2  
#            - AddResponseHeader=first-response, first-response-header2  
             - CustomFilter  
        - id: second-service  
          uri: http://localhost:8082/  
          predicates:  
            - Path=/second-service/**  
          filters:  
#            - AddRequestHeader=second-request, second-request-header2  
#            - AddResponseHeader=second-response, second-response-header2  
             - name: CustomFilter  
             - name: LoggingFilter  
               args:  
                 baseMessage: Hi, there.  
                 preLogger: true  
                 postLogger: true
  • Eureka 클라이언트 설정
    • register-with-eureka: false로 설정하여 이 서비스가 Eureka 서버에 자신의 정보를 등록하지 않도록 한다.
    • fetch-registry: false로 설정하여 이 서비스가 Eureka 서버로부터 다른 서비스의 레지스트리를 가져오지 않도록 한다.
    • defaultZone: http://localhost:8761/eureka로 설정하여 Eureka 서버의 기본 URL을 지정한다.
  • Spring Cloud Gateway 설정
    • default-filters: 모든 요청에 대해 전역 필터를 적용한다.
      • GlobalFilter: 요청과 응답에 대해 로깅 기능을 제공한다.
        • baseMessage: 로깅에 사용될 기본 메시지를 설정한다.
        • preLogger: true로 설정하여 요청 전(pre)에 로그를 남긴다.
        • postLogger: true로 설정하여 응답 후(post)에 로그를 남긴다.
    • Route 설정
      • id: first-service로 설정하여 라우트의 ID를 정의한다.
      • uri: http://localhost:8081/로 설정하여 /first-service/** 경로의 요청을 해당 서비스로 전달한다.
      • predicates: Path 조건을 /first-service/**로 설정하여 이 경로에 들어오는 요청만 이 라우트를 선택하도록 한다.
      • filters: CustomFilter를 적용하여 이 라우트에 추가적인 필터를 설정한다.
      • id: second-service로 설정하여 두 번째 라우트의 ID를 정의한다.
      • uri: http://localhost:8082/로 설정하여 /second-service/** 경로의 요청을 해당 서비스로 전달한다.
      • predicates: Path 조건을 /second-service/**로 설정하여 이 경로에 들어오는 요청만 처리하도록 한다.
      • filters:
        • CustomFilter를 적용하여 사용자 정의 필터를 추가한다.
        • LoggingFilter를 적용하여 로깅 필터를 추가한다.
          • baseMessage: 로깅 메시지를 Hi, there.로 설정한다.
          • preLogger: true로 설정하여 요청 전(pre)에 로그를 기록한다.
          • postLogger: true로 설정하여 응답 후(post)에 로그를 기록한다.

LoggingFilter 클래스

package com.example.gateway_service.filter;  

import lombok.Data;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.cloud.gateway.filter.GatewayFilter;  
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;  
import org.springframework.http.server.reactive.ServerHttpRequest;  
import org.springframework.http.server.reactive.ServerHttpResponse;  
import org.springframework.stereotype.Component;  
import reactor.core.publisher.Mono;  

@Component  
@Slf4j  
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {  
    public LoggingFilter() { super(Config.class); }  

    public GatewayFilter apply(Config config){  
        return (exchange, chain) -> {  
            ServerHttpRequest request = exchange.getRequest();  
            ServerHttpResponse response = exchange.getResponse();  

            log.info("Logging filter baseMessage: " + config.getBaseMessage());  
            if(config.isPreLogger()){  
                log.info("Logging PRE filter: request uri -> {}", request.getURI());  
            }  
            return chain.filter(exchange).then(Mono.fromRunnable(()->{  
                if(config.isPreLogger()){  
                    log.info("Logging POST filter: response code -> {}", response.getStatusCode());  
                }  
            }));  
        };  
    }  

    @Data  
    public static class Config {  
        private String baseMessage;  
        private boolean preLogger;  
        private boolean postLogger;  
    }  
}

결과

Gateway-Service 로그 흐름

  • http://localhost:8080/second-service/check로 요청을 보낼 때, 아래와 같은 로그 순서가 발생한다.
  1. Global Filter:
    • Spring Cloud Gateway GlobalFilter라는 기본 메시지를 로깅한다.
    • 요청 ID와 함께 요청이 시작된 시점에서 로그를 기록한다 (pre-로그).
  2. Custom Filter:
    • 요청 ID를 기록하는 사용자 정의 필터가 요청 전(pre-filter)에서 로그를 남긴다.
  3. Logging Filter:
    • Hi, there라는 커스텀 메시지를 로그에 기록한다.
    • 요청 URI를 기록한다 (pre-로그).
  4. Proxied Service (second-service: http://localhost:8082):
    • 요청이 두 번째 서비스로 전달되어 처리된다.
  5. Logging Filter (Post-filter):
    • 응답 상태 코드를 로그에 기록한다 (post-로그).
  6. Custom Filter (Post-filter):
    • 응답 상태 코드를 기록하는 사용자 정의 필터가 응답 후(post-filter)에서 로그를 남긴다.
  7. Global Filter (Post-filter):
    • 응답 상태 코드와 함께 요청 처리가 완료된 시점을 로그에 기록한다 (post-로그).

Gateway-Service 로그

Global Filter baseMessage: Spring Cloud Gateway GlobalFilter
Global Filter Start: request id -> 4181e877-3
Custom PRE filter: request id -> 4181e877-3
Logging filter baseMessage: Hi, there.
Logging PRE filter: request uri -> http://localhost:8080/second-service/check
Logging POST filter: response code -> 200 OK
Custom POST filter: response code -> 200 OK
Global Filter End: reponse code -> 200 OK
  • 요청 전 로깅:
    • GlobalFilter, CustomFilter, LoggingFilter가 순차적으로 요청 정보를 로깅한다.
  • 요청 처리:
    • 요청이 실제 서비스(second-service로 설정된 localhost:8082)로 전달되어 처리된다.
  • 응답 후 로깅:
    • LoggingFilter, CustomFilter, GlobalFilter가 순차적으로 응답 정보를 로깅하며, 응답 상태 코드를 기록한다.

로드 밸런서란?

  • 클라이언트와 서버 그룹 사이에 위치하여 서버에 가해지는 트래픽을 여러 대의 서버에 고르게 분배하는 역할을 한다.

pom.xml

<dependency>  
<groupId>org.springframework.cloud</groupId>  
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>  
</dependency>
  • 로드 밸런서 기능을 구현하기 위해 각 서비스에 필요한 라이브러리를 pom.xml 파일에 추가한다.

application.yml

eureka:  
  client:  
    register-with-eureka: true  
    fetch-registry: true  
    service-url:  
      defaultZone: http://localhost:8761/eureka
  • 게이트웨이 서비스와 각 프로젝트 별로 eureka 값을 추가한다
  • 게이트웨이 서비스는 uri 값을 변경한다.
routes:  
  - id: first-service  
    uri: lb://MY-FIRST-SERVICE  
    predicates:  
      - Path=/first-service/**  
    filters:  
       - CustomFilter  
  - id: second-service  
    uri:  lb://MY-SECOND-SERVICE  
    predicates:  
      - Path=/second-service/**
  • 서비스의 라우팅 설정을 추가하여 Gateway가 로드 밸런서를 통해 트래픽을 분배하도록 한다.

기동순서

  1. Eureka Server: localhost:8761에서 실행하여 서비스 레지스트리를 관리한다.
  2. Gateway Server: Gateway 서버를 기동하여 클라이언트 요청을 처리하고, 로드 밸런서를 통해 서비스를 라우팅한다.
  3. First Service, Second Service: 각 서비스가 기동되고, Eureka에 등록된다.First Service와 Second Service를 2개씩 기동

1. Run Application

  • VM Option -> -Dserver.port=9002
-Dserver.port=9002

2. Terminal

mvn spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9003'
mvn clean compile package
java -jar -Dserver.port=9004 ./target/user-service-0.0.1-SNAPSHOT.jar
  • Second Service 인스턴스를 9003 포트로 기동한다. 이를 위해 mvn spring-boot:run 명령어를 사용하여 JVM 인자에 포트를 설정한다.
  • Maven 빌드를 수행하여 프로젝트를 컴파일하고 패키징한다
  • First Service의 또 다른 인스턴스를 9004 포트로 실행하려면, java -jar 명령어를 사용하여 실행한다.
  • 이와 같은 방식으로 여러 인스턴스를 실행하고, 로드 밸런서를 통해 균등하게 트래픽을 분배한다.
반응형

'Cloud > MSA' 카테고리의 다른 글

[MSA] MicroService 구현(회원가입 구현)  (0) 2024.11.26
[MSA] MicroService 구현(사용자 서비스)  (0) 2024.06.29
[MSA] Spring Cloud Gateway  (0) 2024.06.29
[MSA] 스프링 클라우드란?  (0) 2023.07.24
[MSA] SOA와 MSA  (0) 2023.07.24

+ Recent posts