api gateway란?

bw1611·2024년 3월 16일

API GATEWAY가 필요한 이유

클라이언트와 백엔드 사이에 위치 한 API 관리 툴로 내부의 여러 API들에 대한 통합된 하나의 엔트리 포인트를 제공한다. 중간에 위치해서 클라이언트의 특정 API에 대한 요청을 라우팅해주는 역할을 수행한다.

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

Spring Cloud에서의 MSA 간 통신

  • RestTemplate

    • 스프링 프레임워크에서 제공하는 HTTP 클라이언트 라이브러리
    • HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 외부 서비스와 통신 가능
    • 개발자가 수동으로 HTTP 요청을 구성하고 실행해야하기 때문에 번거로울 수 있음
    • 일반적으로 동기적으로 동작하며, 비동기 작업을 지원하지 않음
  • Feign Client

    • Netflix에서 개발한 라이브러리, 스프링 클라우드 프레임워크에 포함되어 있음
    • Feign은 인터페이스 기반으로 작성되어 있어 개발자가 선어적으로 서비스 클라이언트를 작성할 수 있음
    • Restful 서비스의 클라이언트를 선언하고 구성하는데 간편
    • 내부적으로는 Ribbon과 같은 로드 밸런서와 함께 사용될 수 있어 트래픽 분산에 유용
    • 일반적으로 RestTemplate보다 추상화 수준이 높아 코드가 간결하고 확장성이 좋음
  • Netflix Ribbon (현재 버전 종료)

    • 서비스들을 이름으로 호출하며 Health check를 통해 로드 밸런서 기능을 제공
    • 비동기 방식을 적용할 수 없어 최근에는 사용되지 않음
    • client에서 gateway로 요청하는 로직이 아닌 client자체에서 서비스 이름을 부르도록 하는 로직형태 (현재는 Spring Cloud Loadbalancer로 대체 가능)

Netflix Zuul (Maintenance 상태)

서버에서 실제 gateway처럼 클라이언트의 요청을 받아내는 역할을 수행하는 서비스이다. 하지만 현재는 Spring Cloud Gateway로 대체하여 사용하는 것을 권장하고 있고 Spring boot 2.3까지의 버전만 지원하고 있다.

  • zuul 필터 기능
    • 인증과 보안 : 클라이언트 요청 시, 각 리소스에 대한 인증 확ㅇ니하고 만족하지 않으면 거부
    • 모니터링 : 의미있는 데이터 및 통계를 제공
    • 동적 라우팅 : 필요에 따라 요청을 다른 클러스터로 동적 라우팅

  • Pre filter

    • Origin으로 라우팅되기전에 실행됩니다.
    • 요청 인증, Origin 서버 선택, 디버그 정보 로깅 등이 있습니다.
  • Routing filter

    • 요청을 Origin으로 Routing하는 작업을 수행합니다.
    • Apache HttpClient나 Netflix Ribbon을 사용해서 HTTP 요청을 작성하고 전송합니다.
  • Post filter

    • 요청이 Origin으로 라우팅된 후 실행됩니다.
    • 응답에 표준 HTTP 헤더를 추가하고 통계 및 메트릭을 수집합니다.
  • Error filter

    • 다른 단계에서 오류 발생시 실행됩니다.

zuul을 이용하지 않게 된 이유

자바로 개발되었으며 Servlet 스택 위에 구축되었는데 서블릿 기반의 동기 I/O 방식을 사용하고 있기 때문에 비동기 및 논블로킹 처리가 불가능합니다. 이에 반해 Spring Cloud Gateway는 Spring 프레임워크를 기반으로 개발되었으며, 스프링 리액티브 스택 위에 구축되어 비동기 및 논블로킹 처리를 지원하기 때문에 zuul이 밀렸다고 생각된다.

Spring Cloud Gateway

filter 적용하기

  • Java Code Filter
@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();
    }
}

yml에서 설정할 수 있는 정보를 Java 코드를 이용하여 라우팅해줄 수 있다. 하는 과정이 다소 번거롭기 때문에 yml에 설정하는 것이 전역적으로 관리하기 편하여 Java code로 하는 것은 비추천하는 방식이다.

  • yml Filter
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
        routes:
          - id: first-service
            uri: http://localhost:8001
            predicates:
              - Path=/first-service/**
            filters:
              - AddRequestHeader=first-request, first-requests-header2
              - AddResponseHeader=first-response, first-response-header2
          - id: second-service
            uri: http://localhost:8002
            predicates:
              - Path=/second-service/**
            filters:
              - AddRequestHeader=second-request, second-requests-header2
              - AddResponseHeader=second-response, second-response-header2

AddRequestHeader와 AddResponseHeader에 key-value 형식으로 값을 넣어준다.

  • Custom Filter
@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        // custom pre filter
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest(); // ServerHttpRequest 비동기 방식
            ServerHttpResponse response = exchange.getResponse();

            log.info("Custom pre filter = {}", request.getId());

            // custom post filter
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("Custom POST filter = {}", response.getStatusCode());
            }));
        });
    }


    public static class Config {
        // config 정보 입력
    }
}

AbstractGatewayFilterFactory는 Spring Cloud Gateway의 필터를 구현하기 위한 추상클래스이며 사전 필터(pre-filter)는 클라이언트의 요청을 가로채서 수정할 수 있고, 사후 필터(post-filter)는 서버의 응답을 가로채서 수정할 수 있다.

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:
            - CustomFilter

원래 복잡하게 설정했던 필터를 우리가 빈으로 설정한 CustomFilter를 통하여 쉽게 정의할 수 있고 서비스에 맞는 필터를 적용할 수 있다. (로깅, 보안 검사, 인증, 인가 등)

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

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

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

            log.info("Global pre basesMessage = {}", config.getBaseMessage());

            if (config.isPreLogger()){
                log.info("Global pre start - request id = {}", request.getId());
            }

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


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

GlobalFilter는 모든 서비스에 적용되어야 하는 필터의 경우 설정할 수 있다.

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

CustomFilter처럼 각 서비스에 등록하는게 아니라 전역에서 처리하기 위하여 default-filters를 적용해 준다.

application name으로 서비스 찾기

기존에는 uri: http://localhost:8001을 입력하여 서비스를 찾았는데 랜덤 포트를 적용한다면 이렇게 할 수 없는 문제가 생긴다. 해결방법으로는 eureka 서버에 등록된 application 이름으로 찾아서 등록해주면 된다.

  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true
      routes:
        - id: first-service
          uri: lb://MY-FIRST-SERVICE
          predicates:
            - Path=/first-service/**
        - id: second-service
          uri: lb://MY-SECOND-SERVICE
          predicates:
            - Path=/second-service/**
profile
Java BackEnd Developer

0개의 댓글