Spring/MSA

[스프링 MSA] MSA_S2 : API Gateway Service (1)

ajeong7038 2023. 10. 3. 15:58

`Spring Cloud로 개발하는 마이크로서비스 애플리케이션 (MSA)` 강의를 듣고 정리한 자료입니다

✨ API Gateway

- 사용자가 설정한 라우팅 설정에 따라서 각각 엔드포인트로 요청 및 응답을 하는 프록시(proxy) 역할을 한다
- 일종의 진입로 역할
- 일괄적으로 처리


✨ 프록시 서버 (proxy server)

정의

- 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다

프록시 vs 프록시 서버

- 프록시 : 서버와 클라이언트 사이에서 중계기로써 대리로 통신을 수행하는 것 (ex: API Gateway)
- 프록시 서버 : 서버와 클라이언트 사이에서 중계 기능을 하는 것

- 쉽게 말하면 `프록시 서버`는 컴퓨터 시스템이나 응용 프로그램을, `프록시`는 통신 자체를 의미한다 (사실 잘 모르겠어요...)
- 프록시 서버와 게이트웨이는 다른 개념!
- 클라이언트 -> 게이트웨이 -> 서비스


✨ Spring Cloud에서의 MSA 간 통신

1. RestTemplate

- 인스턴스 생성 + 포트번호 & 파라미터

2. Feign Client

- 특정한 인터페이스 생성
- 인터페이스에서 웹으로 따로 호출하고 싶은 추가적인 마이크로서비스의 이름 등록
    -> 마이크로서비스의 이름만을 가지고 호출할 수 있게 된다

Ribbon

- Load Balancer 역할 담당
- 비동기 처리 시 문제점을 겪음
- 클라이언트 사이드 내에 있다

Netflix Zuul

- 클라이언트 -> 넷플릭스 Zuul -> 서비스
    -> API Gateway와 동일한 역할 수행
=> Netfilx Zuul 실습을 해보자!


✨ Netflix Zuul

1. RestController 추가

- 일반적인 컨트롤러와 REST 컨트롤러의 차이점은 request body와 response body를 구현하느냐, 아니면 제공된 것을 쓰느냐의 차이
cf) RequestMapping : 사용자로부터 요청되는 URI 값을 지정해 놓는 것

2. Zuul 의존성 추가

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

✨ Zuul 관련 설정 오류

Maven 쓰는데 https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-zuul/2.2.9.RELEASE에 나온 대로 했더니

<artifactId>spring-cloud-starter-netflix-zuul</artifactId>-->

여기에 빨간 줄이 떴다...

해결

<dependencies>
<!-- 이하 생략... -->
    <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>

이 밑에

<dependencyManagement>
    <dependencies>
       <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <version>${spring-cloud.version}</version>
          <type>pom</type>
          <scope>import</scope>
       </dependency>
    </dependencies>
</dependencyManagement>

이렇게 추가했더니 문제 해결!
 

✨ Netflix Zuul - Filter 적용

- Zuul 필터 사용을 위해 `ZuulFilter`라는 추상 클래스 상속
    -> 컴포넌트 역할로 등록
- 빨간 줄이 뜨는 이유는 추상 클래스인 `ZuulLoggingFilter`에서 해당하는 추상 클래스의 추상 메소드를 적용하지 않았기 때문에 오류가 생김

추상클래스

- 일부 메소드가 구현되지 않고 선언만 되어 있는 클래스
- 일부 클래스의 공통적인 부분을 추출해 어느 정도 규격(틀)을 잡아 놓는 추상적인 클래스
- new (인스턴스화) 불가능 : 객체 생성 X
- 객체 생성이 불가능하다는 점 외 일반 클래스와 별 다를 바 없다
- 인터페이스는 멤버 변수를 갖고 있고, 추상 클래스는 멤버 변수를 갖고 있지 않다

추상 메소드 적용

- 윈도우 기준 `Alt` + `Insert` => Generate -> `Implement Methods...` 클릭 -> 자동 생성
- filterType() : 사전 필터 or 사후 필터
- filterOrder() : 여러 개의 필터가 있는 경우 순서를 의미함
    -> 하나만 있는 경우 1 반환
- shouldFilter() : 원하는 옵션에 따라 필터로 사용 여부 지정
- run : 실제 동작 지정

ZuulLoggingFilter

- `@Slf4j` 어노테이션으로 쉽게 log 출력 가능

- 추상 메소드 적용한 모습 (@Override 4개)

package com.example.zuulservice.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Slf4j
@Component
public class ZuulLoggingFilter extends ZuulFilter {
    @Override
    public Object run() throws ZuulException {
        log.info("******** printing logs: ");

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        log.info("******** " + request.getRequestURI());

        return null;
    }
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }
}

application.yml

server:
  port: 8000

spring:
  application:
    name: my-zuul-service

zuul:
  routes:
    first-service:
      path: /first-service/**
      url: http://localhost:8081
    second-service:
      path: /second-service/**
      url: http://localhost:8082

- zuul.routes : 라우터 개개인에 url 등록

- 127.0.0.1:8000/first-service -> path 인식, 8081 포트로 보냄


✨ Spring Cloud Gateway

버전 설정 잊지 말기!

spring boot version : 2.4.1
spring cloud version : 2020.0.0

 

- 기존 Zuul과 다른 점 : `Netty` 비동기 서버 작동

404 오류 뜨는 이유

- 기존 first-service나 second-service는 `http://localhost:8081/welcome`로 호출을 하는데, `ApigatewayServiceApplication`을 실행시키게 되면 여기서는 `http://localhost:8081/first-service/welcome`로 요청을 받게 되기 때문에 404 오류가 나는 것이다
- 그러므로 `first-service`라는 글자를 가질 수 있게끔 컨트롤러를 바꿔야 한다
API Gateway 사용 시 주의점
- 값이 넘어갈 때 주소값에 있는 것이 포워딩되어 넘어가므로 주소 설정에 주의해야 한다 (맵핑 정보 등록 필요)


✨ Spring Cloud Gateway - Filter

- 동작 : Client -> Gateway -> Predicate -> Pre Filter & Post Filter -> Service

 

1. Gateway Handler Mapping

- 요청 정보를 받는다

2. Predicate

- 어떤 이름으로 요청되었는지 조건 분기
- application.yml 파일에서 조건절로 쓰임

3. 사전 필터와 사후 필터 분리

- Property 방법과 Java Code 방법이 있다
- Property 방법의 예시로는 yml 파일을 이용해 처리하는 방법이 있다


✨ 필터 처리 방법

1. Java Code 방식

package com.example.apigatewayservice.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/first-service/**")
                        .filters(f -> f.addRequestHeader("first-request", "first-request-header")
                                .addResponseHeader("first-response", "first-response-header"))
                        .uri("http://localhost:8081/"))
                .route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                .addResponseHeader("second-response", "second-response-header"))
                        .uri("http://localhost:8082/"))
                .build();
    }
}

2. Property (yml 파일) 방식

server:
  port: 8000

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

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates: # ???
            - Path=/first-service/**
          filters:
            - AddRequestHeader=first-request, first-request-header2 # key, value
            - AddResponseHeader=first-response, first-response-header2 # key, value
        - id: second-service
          uri: http://localhost:8082/
          predicates: # ???
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-request-header2 # key, value
            - AddResponseHeader=second-response, second-response-header2 # key, value

 


✨ 참고 자료

https://www.tibco.com/sites/tibco/files/media_entity/2020-05/api-gateway-diagram.svg
https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%84%9C%EB%B2%84
https://velog.io/@im_joonchul/%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4Abstract-class
https://ggparkitbank.tistory.com/120?category=1015014