스프링 클라우드 게이트웨이

Sprout·2023년 10월 16일
0

MSA

목록 보기
5/8

API Gateway

클라이언트/백엔드 서비스 간의 통신을 관리하고 제어하는 역할을 하는 컴포넌트

→ 이를 통해 보안, 성능, 확장성 등 다양한 이점을 얻을 수 있음.

서비스 관리 및 모니터링

서비스 감지, 로드 밸런싱, 회로 차단 등을 통해 서비스 운영이 용이

  • 회로차단 : Health Check에서 접속안되는 서버 막기

효율적인 필터링 및 가공

요청과 응답을 가로채 필터링하고 수정하여 보안, 로깅 적용 가능

단일 진입점 ( 어딘가에 진입할 때 최초로 입장하는 문 )

클라이언트는 API 게이트웨이 하나만 알면 됨.


API Gateway 주요 기능

  • 라우팅( Routing )
    • 클라이언트 요청을 처리할 적절한 서비스로 보내주는(라우팅하는) 역할

  • 로드 밸런싱
    • 여러 인스턴스의 MS(마이크로 서비스)가 있을 떄, 부하 분산을 통해 요청을 서비스 인스턴스로 분배함. → 효율적인 자원 활용 + 성능 향상

  • 인증 및 권한 부여
    • 클라이언트의 요청을 검증 + 권한 확인 → 유효한 요청만을 백엔드로 전달 (보안 강화+불법적 엑세스 방지)

  • 서비스 집계
    • 여러 개의 MS에서 데이터를 가져와 하나의 응답으로 조합할 수 있음. → 클라이언트에게 편리한 API 제공

  • 캐싱 ( Redis 서버를 이용한 캐싱 )
    • 자주 요청되는 데이터를 캐시하여 빠른 응답 제공 가능 → 서비스 응답 시간 최적화

  • API변환 ( JSON요청 ↔ XML요청 )
    • 요청이나 응답의 형식을 변환하거나 조작 가능 ex) JSON ↔ XML

  • 오류 처리
    • 백엔드에서 발생한 오류를 처리하고 클라이언트에게 적절한 메세지 전달

      → 오류 전파 = 단일 서버가 아니기 때문에 오류 추적이 어렵기 때문에 나온 방안

  • 서비스 검색 및 등록( Service Discovery 및 Registration )
    • MS가 동적으로 확장되거나 축소될 때(=서비스가 죽었는지 살았는지) 이를 감지 및 새 인스턴스 자동으로 등록, 사용하지 않는 인스턴스 제거
      - 감지해주는 Eureka라는 서버가 있음

Spring Cloud Gateway

  • 비동기 방식으로 돌아가는 게이트웨이
  • API 게이트웨이를 만들기 위한 라이브러리
  • Spring Cloud Zuul → ( 동기 방식으로 작동하는 이유로 Spring boot 2.4이후 지원 중단 )

Spring Cloud Gateway 주요 기능

라우팅 (Routing)

  • 요청된 경로에 따라 적절한 서비스로 라우팅
  • 동적으로 서비스를 추가하거나 수정 가능

로드 밸런싱 ( Load Balancing )

  • 여러 인스턴스의 서비스에 요청을 분산하여 부하를 분담
  • Ribbon과 함께 사용하여 백엔드 서비스에 로드 밸런싱을 적용 가능

필터링 및 전/후처리 ( Filtering and Pre/Post Processing )

  • 클라이언트 요청이나 서비스 응답을 가로채 필터링하고 수정 가능
  • 이를 사용하여 보안, 로깅, 인증 등을 적용한다.

서비스 감지 및 등록 ( Service Discovery and Registration )

  • Eureka와 함께 사용하여 동적으로 서비스를 감지하고 등록 가능

서킷 브레이커 ( Circuit Breaker )

  • 서비스 간 통신에서 발생하는 장애 처리
  • Hystrix, resilience4j 사용, 주로 resilience4j 씀

폴백 ( Fallback )

  • 서비스 호출이 실패할 경우 대체 응답을 반환 가능

JWT 및 OAuth2 지원 ( JWT and OAuth2 Support )

  • 보안을 강화하기 위한 JWT, OAuth2 인증을 지원

캐싱 ( Caching )

  • 반복적인 요청에 대한 응답을 빠르게 반환

실습

1. ApiGateway, MS 만들어보기

  1. Lombok, Spring Cloud Routing → Gateway 선택해 프로젝트 생성

  2. properties → yml으로 바꾸기

  1. yml을 통해 이 게이트웨이 서버를 경유해 연결할 마이크로서비스 서버에 대한 설정을 위와 같이 등록하기
    • server.port, spring.application.name 을 쓰면 IDE가 자동완성해줌
server:
  port: 8000

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:  # 개별 route 마이크로서비스 정보를 routes에 기입함
        - id: first-service
          uri: http://localhost:8001/
          predicates:
            - Path=/first-service/**
        - id: second-service
          uri: http://localhost:8002/
          predicates:
            - Path=/second-service/**
# predicates에서 설정한 주소는 정확하게는
	# Path=http://localhost:8000/first-service/**를 요청하면
	# first-service로 보내주겠다는 뜻

실제로 http://localhost:8000/first-service/hello로 접속하면
first-service의 uri + predicates에 요청된 uri가
-> 8001포트에 요청된다
	-> http://localhost:8001/first-service/hello
=> port번호만 변경하고 뒤의 값은 그대로 전송한다.
  • id : 추후 eureka 서버 등에 등록할 서비스 명 미리 식별하도록 부여

  • uri : 게이트웨이 서버가 아래에 등록할 predicates의 패턴을 감지했을 때 어떤 서버로 포워딩할지

  • predicates : 어떤 조건을 만족할 떄 연동된 uri로 보내줄지 조건 작성

    (경로 기준, 시간 기준, 쿠키 기준 등 조건식의 기준은 다양하다.)

실행해보면, 평소 쓰던 서버인 Tomcat이 아니라 Netty에 의해 실행되는 것을 확인 가능

  1. 앞에 지정한 MS(first-service , second-service)를 일반적인 세팅으로 생성 (Lombok, Spring web 의존)

  2. 설정파일(yml)에 ApiGateway에서 지정한 port번호를 각 MS에 설정

    server:
      port: 8001
  3. 테스트를 위한 Controller 생성

    @RestController
    @RequestMapping(value = "/first-service")
    // predicates의 식별 주소를 조건문으로 지정했다면
    // 지정한 주소와 매핑되도록 값을 지정할 것
    public class FirstserviceController {
    
        @RequestMapping(value = "hello", method = RequestMethod.GET)
        public String hello(){
            return "Hello, First-service!";
        }
    }

ApiGateway는 Netty를 쓰지만 MS 자체는 tomcat을 쓰는 것을 볼 수 있다.

* 참고자료 : Netty vs Tomcat


2. 자바 config파일로 설정하기 및 기본 필터 설정

  1. 라우팅 정보 yml → 자바 Config클래스로 옮기는 법

    ( yml파일은 설정이 복잡하니까 )

server:
  port: 8000
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8001/
          predicates:
            - Path=/first-service/**
          filters:
            - AddRequestHeader=f-req,f-req-v
            - AddResponseHeader=f-res,f-res-v
        - id: second-service
          uri: http://localhost:8002/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=s-req,s-req-v
            - AddResponseHeader=s-res,s-res-v
  1. yml 설정을 옮겨 담을 클래스 생성

    @Configuration
    public class FilterConfig {
        @Bean
        public RouteLocator gatewayRoutes(RouteLocatorBuilder routeLocatorBuilder){
            return routeLocatorBuilder.routes()
                  // 설정 내용
                    // first-service 필터세팅
                    .route( r -> r.path("/first-service/**")
                            .filters( f -> f.addRequestHeader("f-req", "f-req-v")
                                    .addResponseHeader("f-res", "f-res-v"))
                            .uri("http://localhost:8001/")
                    )
                    // second-service 필터세팅
                    .route( r -> r.path("/second-service/**")
                            .filters( f -> f.addRequestHeader("s-req", "s-req-v")
                                    .addResponseHeader("s-res", "s-res-v"))
                            .uri("http://localhost:8002/"))
                    .build();   // 빌더패턴 종료
        }
    }

    필터를 게이트웨이에 걸었으므로

    MS에 직접 요청하면 필터가 동작하지 않음 (헤더에 값이 등록되지 않음)

  • 요청헤더에 든 값 컨트롤러로 감지하기

    pre-filter : request 처리하기 전에 실행되는 필터

    post-filter : response를 처리하기 전에 실행되는 필터

@RequestMapping(value = "/header-check", method = RequestMethod.GET)
    public String headerCheck(@RequestHeader("fsreqh") String header){
        return header;
    }
// MS단에서 header를 확인할 수 있다 (Pre필터 단계에서 헤더가 추가되므로)
	// @RequestHeader 어노테이션으로 감지받아 사용


3. 커스텀 필터 적용하기

  1. 커스텀 필터는 AbstractGatewayFilterFactory를 상속한 필터 클래스를 @Component 어노테이션을 이용해 등록하여 동작

  2. 생성자에서 super()을 이용해서 필터 기본 세팅에 필요한 정보를 넘겨주고

  3. apply라는 메서드 하나를 구현해줘서 동작

    • Pre-filter : 그냥 내부에 실행문으로 작성
    • Post-filter : return 구문 속에 실행문으로 작성
    @Component
    public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
        // Generic 내부에 설정에 필요한 자료형을 넣어줘야하는데
        // CustomFilter의 설정 클래스는 CustomFilter 내부의 Config로 사용할 것
            // Config를 이너클래스로 생성한 이유? -> 응집도를 높여주기 위해
        public CustomFilter(){
            super(Config.class);
        }
        public static class Config{
    
        }
        @Override
        public GatewayFilter apply(Config config) {
            // import org.springframework.http.server.reactive.ServerHttpRequest;
            // import org.springframework.http.server.reactive.ServerHttpResponse;
            return (exchange, chain) -> {
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
    
                // pre-filter는 그냥 작성하면 작동
                System.out.println("Custom pre filter : " + request.getId());
    
                // post-filter는 return 구문 속에 코드를 작성해서 만듦
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                        // Mono -> 비동기방식 서버에서 단일값 전달할 때 사용하는 양식
                    System.out.println("Custom post filter : " + response.getStatusCode());
                }));
            };
        }
    }
    - netty에서는 tomcat과 달리 ServerHttpRequestServerHttpResponse를 사용 (비동기 서버에서 사용)
    server:
      port: 8000
    
    spring:
      application:
        name: apigateway-service
      cloud:
        gateway:
          routes:
            - id: first-service
              uri: http://localhost:8001/
              predicates:
                - Path=/first-service/**
              filters:
                - CustomFilter #커스텀 필터 적용
            - id: second-service
              uri: http://localhost:8002/
              predicates:
                - Path=/second-service/**
              filters:
                - AddRequestHeader=s-req,s-req-v
                - AddResponseHeader=s-res,s-res-v
    - 등록된 필터는 yml/configuration에 필터 이름 기입
  • OrderedGatewayFilter 필터 우선순위 조정
    @Component
    public class CustomFilter2 extends AbstractGatewayFilterFactory<CustomFilter2.Config> {
        public CustomFilter2() {
            super(Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            GatewayFilter gatewayFilter = new OrderedGatewayFilter((exchange, chain) -> {
                // 필터 내용
            }, Ordered.HIGHEST_PRECEDENCE);
            // 구성 순위 조정이 가능하다.
    				return gatewayFilter;
        }
        public static class Config{
    
        }
    }

4. 전역 필터 적용(Global Filter) + Config 이너 클래스에 인자 전달하기

  • 커스텀 필터 : MS개별에 작용
  • 전역 필터 : gateway 서버에 전역적으로 작용 구현 방식은 커스텀 필터와 같되
    @Component
    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();
            // pre filter
                System.out.println("Global Filter default message : " + config.getMessage());
                // pre에서 사용할지 말지 Boolean값으로 판단
                if(config.isPre()){
                    System.out.println("Global Pre Filter : " + request.getId());
                }
    
            // post filter
                return chain.filter(exchange).then(Mono.fromRunnable(()->{
                    // post에서 사용할지 말지 Boolean값으로 판단
                        if(config.isPost()){
                            System.out.println("Global Post Filter : " + response.getStatusCode());
                        }
                }));
            };
        }
        @Getter
    		@Setter
        public static class Config{
            private String message;
            private boolean pre;
            private boolean post;
        }
    }
    - 등록된 필터는 yml/configuration에 필터 이름 기입

등록을 게이트웨이 서버측 자체에 해줌

→ yml/config에서의 등록을 gateway측에 직접 걸어줌

server:
  port: 8000

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:  # 전역필터 세팅
        - name: GlobalFilter
          args:
            message: Global Filter Default Message Test
            pre: ture
            post: true
      routes:
        - id: first-service
          uri: http://localhost:8001/
          predicates:
            - Path=/first-service/**
          filters:
				            - CustomFilter #커스텀 필터 적용
				- id: second-service
				          uri: http://localhost:8002/
				          predicates:
				            - Path=/second-service/**
				          filters:
				            - AddRequestHeader=s-req,s-req-v
				            - AddResponseHeader=s-res,s-res-v
- 모든 MS(first-service, second-service)에서 실행되는 모습을 볼 수 있다.

호출 우선 순위

  • 글로벌 pre → 커스텀 pre → 커스텀 post → 글로벌 post 이를 뒤틀고 싶을 때 OrderedGatewayFilter 사용 추후 config서버를 구성해서 해당 서버만 세팅을 바꿔주고 마이크로서비스 서버는 설정을 바꾸지 않아도 되게 구성 가능

5. 로깅 필터 적용

@Slf4j
@Component
public class LogFilter extends AbstractGatewayFilterFactory<LogFilter.Config> {
    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            // pre-filter
            log.info("log filter : {}",request.getId());
            // post- filter
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                log.info("log post filter : {}", response.getStatusCode());
            }));
        });
    }
    public LogFilter() {
        super(Config.class);
    }
    public static class Config{
    }
}
server:
  port: 8000

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:  # 전역필터 세팅
        - name: GlobalFilter
          args:
            message: Global Filter Default Message Test
            pre: true
            post: true
      routes:
        - id: first-service
          uri: http://localhost:8001/
          predicates:
            - Path=/first-service/**
          filters:
            - CustomFilter
            - LogFilter  # 로깅필터 추가
        - id: second-service
          uri: http://localhost:8002/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=s-req,s-req-v
            - AddResponseHeader=s-res,s-res-v
profile
취준생 필기노트

0개의 댓글