Section 2: API Gateway Service - KwangtaekJung/MSA-SpringCloud-user-service GitHub Wiki

Section 2: API Gateway Service

API Gateway Service ๊ฐœ์š”
Netflix Ribbon๊ณผ Zuul
Spring Cloud Gateway - ๊ธฐ๋ณธ
Spring Cloud Gateway - Filter
Spring Cloud Gateway - Eureka ์—ฐ๋™
Spring Cloud Gateway - Load Balancer

API Gateway Service ๊ฐœ์š”

  • ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ
  • ์„œ๋น„์Šค ๊ฒ€์ƒ‰ ํ†ตํ•ฉ
  • ์‘๋‹ต ์บ์‹ฑ
  • ์ •์ฑ…, ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ๋ฐ QoS ๋‹ค์‹œ ์‹œ๋„
  • ์†๋„ ์ œํ•œ
  • ๋ถ€ํ•˜ ๋ถ„์‚ฐ
  • ๋กœ๊น…, ์ถ”์ , ์ƒ๊ด€ ๊ด€๊ณ„
  • ํ—ค๋”, ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด ๋ฐ ์ฒญ๊ตฌ ๋ณ€ํ™˜
  • IP ํ—ˆ์šฉ ๋ชฉ๋ก์— ์ถ”๊ฐ€

Netflix Zuul ๊ตฌํ˜„

  • Spring Boot: 2.4.0 ์ดํ•˜ ์—์„œ ์ง€์›ํ•จ. (์ตœ์‹  ๋ฒ„์ „์˜ Spring Cloud Routing์—์„œ๋Š” Zuul ์ง€์›ํ•˜์ง€ ์•Š์Œ.)
  • First Service, Second Service
    • Spring Boot: 2.3.8
    • Developer Tools: Lombok
    • Web: Spring Web
  • Zuul Service
    • Spring Boot: 2.3.8
    • Web: Spring Web
    • Spring Cloud Routing: Zuul (API Gateway ์—ญํ• ) @EnableZuulProxy
      • ZuulFilter

Spring Cloud Gateway - ๊ธฐ๋ณธ

  • Developer Tools: Spring Boot DevTools
  • Spring Cloud Discovery: Eureka Discovery Client
  • Spring Cloud Routing: Gateway

Spring Cloud Gateway - Filter

  • Filter using Java Code - FilterConfig.java
    @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();
      }
    }
    
  • Filter using with application.yml
    • Configuration java ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  applicaiton.yml ์„ค์ • ๋งŒ์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•จ.
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-serivce
          uri: http://localhost:8081
          predicates:
            - Path=/first-service/**
          filters:
            - AddRequestHeader=first-request, first-request-header2
            - AddResponseHeader=first-response, first-response-header2
        - id: second-serivce
          uri: http://localhost:8082
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-request-header2
            - AddResponseHeader=second-response, second-response-header2
  • Custom Filter
    • ์‚ฌ์šฉ์ž ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  application.yml์—์„œ ํ•„ํ„ฐ๊ฐ€ ํ•„์š”ํ•œ routes ์— ์„ค์ •ํ•œ๋‹ค.
@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();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Custom PRE filter: request id -> {}", request.getId());

            // Custom Post Filter
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("Custom POST filter: request id -> {}", response.getStatusCode());
            }));
        });
    }

    public static class Config {

    }
}
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-serivce
          uri: http://localhost:8081
          predicates:
            - Path=/first-service/**
          filters:
#            - AddRequestHeader=first-request, first-request-header2
#            - AddResponseHeader=first-response, first-response-header2
            - CustomFilter
        - id: second-serivce
          uri: http://localhost:8082
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-request-header2
#            - AddResponseHeader=second-response, second-response-header2
            - CustomFilter
  • Global Filter
    • Global Filter๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  applicatin.yml์— default-filters๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • Logging Filter
    • ์ฐจ์ด์ ์„ ๋ชจ๋ฅด๊ฒ ๊ตฐ. ใ…กใ…ก;
@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
//        return ((exchange, chain) -> {
//            ServerHttpRequest request = exchange.getRequest();
//            ServerHttpResponse response = exchange.getResponse();
//
//            log.info("Global baseMessage: {}", config.getBaseMessage());
//
//            if (config.isPreLogger()) {
//                log.info("Global Filter Start: request id -> {}", request.getId());
//            }
//
//            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
//                if (config.isPostLogger()) {
//                    log.info("Global Filter End: response id -> {}", response.getStatusCode());
//                }
//            }));
//        });

        GatewayFilter filter = new OrderedGatewayFilter((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Logging Filter baseMessage: {}", config.getBaseMessage());

            if (config.isPreLogger()) {
                log.info("Logging PRE Start: request id -> {}", request.getId());
            }

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                if (config.isPostLogger()) {
                    log.info("Logging POST End: response id -> {}", response.getStatusCode());
                }
            }));
        }, Ordered.HIGHEST_PRECEDENCE);

        return filter;
    }

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

    }
}

Spring Cloud Gateway - Eureka ์—ฐ๋™

  • Eureka Server: discovery service
  • Eureka Client: APIGATEWAY-SERVICE, MY-FIRST-SERVICE, MY-SECOND-SERVICE

Spring Cloud Gateway - Load Balancer

  • uri์— port๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š๊ณ  Load Balancer๋ฅผ ํ†ตํ•˜๋„๋ก ํ•œ๋‹ค.
      routes:
        - id: first-serivce
#          uri: http://localhost:8081
          uri: lb://MY-FIRST-SERVICE
  • First Service, Second Service๋ฅผ ๊ฐ 2๊ฐœ์”ฉ ๊ธฐ๋™
    • ๊ถ๊ทน์ ์œผ๋กœ Random Port๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ Load Balancer๋ฅผ ํ†ตํ•˜๋„๋ก ํ•œ๋‹ค. ๋ฐฉ๋ฒ•1,2,3)์€ ์ฐธ๊ณ ๋งŒ ํ•˜์ž.
    • ๋ฐฉ๋ฒ•1) VM Options -> -Dserver.port=[๋‹ค๋ฅธํฌํŠธ]
    • ๋ฐฉ๋ฒ•2) $ mvn spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9003'
    • ๋ฐฉ๋ฒ•3)
      $ mvn clean compile package
      $ java -jar -Dserver.port=9004 ./target/user-service-0.0.1-SNAPSHOT.jar
      
    • ๋ฐฉ๋ฒ•4) Random Port ์„ค์ •ํ•œ๋‹ค. ์ฒซ๋ฒˆ์งธ๋Š” Intellij์—์„œ Run, ๋‘๋ฒˆ์งธ๋Š” Terminal์—์„œ ํฌํŠธ ์ง€์ • ์—†์ด application ๊ตฌ๋™ $ mvn spring-boot:run
server:
  port: 0  #random port

spring:
  application:
    name: my-first-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:  #๋žœ๋คํฌํŠธ ์„ค์ • ์‹œ Eureka Server์—์„œ port๊ฐ€ 0์œผ๋กœ ๋ชจ๋‘ ํ‘œ์‹œ๋˜๋ฏ€๋กœ ๊ฐ๊ฐ์„ ๊ตฌ๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ์ฒ˜๋Ÿผ ์„ค์ •ํ•œ๋‹ค.
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}