๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ“”๊ฐ•์˜ ์ •๋ฆฌ ๋…ธํŠธ

[Spring Cloud๋กœ ๊ฐœ๋ฐœํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค] Section 2: API Gateway Service

API Gateway Service

API Gateway๋Š” ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ๋˜๋Š” ๋ชจ๋“  API ์š”์ฒญ์˜ ๊ด€๋ฌธ ์—ญํ• ์„ ํ•œ๋‹ค. ๋‚ด๋ถ€ ์„œ๋ฒ„ ์‹œ์Šคํ…œ์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋‚ด๋ถ€๋กœ ์ˆจ๊ธฐ๊ณ  ์™ธ๋ถ€์˜ ์š”์ฒญ์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ์‘๋‹ต์„ ํ•œ๋‹ค. ์ฆ‰, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ์•Œ ํ•„์š”์—†์ด ์•ฝ์†ํ•œ ํ˜•ํƒœ์˜ API์˜ ํ†ต์‹ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

API Gateway์˜ ๊ธฐ๋Šฅ

  • ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ผ๊ด„์ ์œผ๋กœ ์ฒ˜๋ฆฌ
  • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ
  • ์ „์ฒด ์‹œ์Šคํ…œ์˜ ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐ ์‹œํ‚ค๋Š” ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ์˜ ์—ญํ• 
  • ๋™์ผํ•œ ์š”์ฒญ์— ๋Œ€ํ•œ ๋ถˆํ•„์š”ํ•œ ๋ฐ˜๋ณต ์ž‘์—…์„ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ์บ์‹ฑ
  • ์‹œ์Šคํ…œ ์ƒ์„ ์˜ค๊ณ ๊ฐ€๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์‹œ์Šคํ…œ ๋‚ด๋ถ€์— ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Œ
  • ํšŒ๋กœ์ฐจ๋‹จ๊ธฐ ๊ธฐ๋Šฅ
  • ์ธ์ฆ ๋ฐ ์ธ๊ฐ€

Spring Cloud์—์„œ MSA๊ฐ„ ํ†ต์‹ 

- RestTemplate

- Feign Client

 

Netfilx Zuul๋กœ ๊ตฌํ˜„

<client - netfilx zull - {service1, service2}>

Spring boot 2.4์—์„œ๋Š” Maintenance ์ƒํƒœ (๋” ์ด์ƒ ์ง€์›ํ•˜์ง€์•Š๋Š” ์ƒํƒœ)

Spring Cloud Gateway

spring cloud gateway - filter

Request, Response Header์— ์„œ๋น„์Šค์— ๋งž๋Š” ๊ฐ’ ์ถ”๊ฐ€.

application.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
            - AddResponseHeader=first-response, first-response-header2
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-request-header2
            - AddResponseHeader=second-response, second-response-header2

cloud.gateway.routes ์ •๋ณด์— ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค์˜ id, uri์ •๋ณด์™€ ์ถ”๊ฐ€์ ์ธ path, filter์ •๋ณด๋ฅผ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

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")) // ํ—ค๋” ํ•„ํ„ฐ
                        .uri("http://localhost:8081")) // uri์ •๋ณด
                .route(r -> r.path("/second-service/**") // ๋ผ์šฐํŠธ ๋“ฑ๋ก
                        .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                .addResponseHeader("second-response", "second-response")) // ํ—ค๋” ํ•„ํ„ฐ
                        .uri("http://localhost:8082")) // uri์ •๋ณด
                .build();
        // .yml ๋™์ž‘์„ ์ž๋ฐ”์ฝ”๋“œ๋กœ๋„ ๊ฐ€๋Šฅ
    }
}

RouterLocator๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” FilterConfig๋ฅผ ์Šคํ”„๋ง ๋นˆ์— ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋นŒ๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ .yml๊ณผ ๊ฐ™์ด route์ •๋ณด๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ํ•„ํ„ฐ์ •๋ณด ๋˜ํ•œ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

CustomFilter - ์ปค์Šคํƒ€๋งˆ์ด์ง• ํ•„ํ„ฐ ์ถ”๊ฐ€

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

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

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

            // Custom Post filter
            // ์ฒด์ธ์— exchange ์ ์šฉํ•˜๊ณ  ์ ์šฉ์ดํ›„์— then ์‹คํ–‰
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("Custom PRE filter: response code -> {}", response.getStatusCode());
            }));
        };
    }

    public static class Config {

        // configration ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์ด ํด๋ž˜์Šค์— ๋„ฃ์–ด์ฃผ๋ฉด๋œ๋‹ค.
    }
}

AbstractGatewayFilterFactory์˜ apply() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ ์š”์ฒญ ์ „, ํ›„๋กœ ์›ํ•˜๋Š” ๋™์ž‘์ด๋‚˜ ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  ๋“ฑ๋กํ•œ ๋นˆ์„ ์‚ฌ์šฉํ•˜๋„๋ก .yml ํŒŒ์ผ์—์„œ ์„ค์ •ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

 

CustomFilter๋ฅผ ์ ์šฉํ•œ application.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:
            - CustomFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - CustomFilter

Customํ•„ํ„ฐ๋Š” ๋ผ์šฐํ„ฐ๋งˆ๋‹ค ์ง€์ •ํ•ด์ค˜์•ผ๋งŒ ์„ค์ •๋œ๋‹ค.

GlobalFilter ์ ์šฉํ•˜๊ธฐ

CustomFilter๋Š” ๊ฐ๊ฐ์˜ ๋ผ์šฐํ„ฐ๋งˆ๋‹ค ์ง€์ •ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค. ๋ชจ๋“  ๋ผ์šฐํ„ฐ์— ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•ด๋ณด์ž.

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

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

            log.info("Global filter baseMessage: reqeust id -> {}", config.getBaseMessage());

            if (config.isPreLogger()) {
                log.info("Global Fileter Start: request id -> {}", request.getId());
            }
            // Custom Post filter
            // ์ฒด์ธ์— exchange ์ ์šฉํ•˜๊ณ  ์ ์šฉ์ดํ›„์— then ์‹คํ–‰
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                if (config.isPostLogger())
                    log.info("Global filter End: response code -> {}", response.getStatusCode());
            }));
        };
    }

    @Data
    public static class Config {
        // configration ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์ด ํด๋ž˜์Šค์— ๋„ฃ์–ด์ฃผ๋ฉด๋œ๋‹ค.
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }
}

ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ  .ymlํŒŒ์ผ์— ์„ค์ •์ •๋ณด๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

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

 

ํ•„ํ„ฐ์ ์šฉ ํ›„ first-service๋ฅผ ํ˜ธ์ถœํ•ด๋ณด๋ฉด ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด Global - Custom - ์‹คํ–‰ - Custom - Global ์ˆœ์„œ๋กœ ๋‚˜์˜จ๋‹ค.

 

์ถ”๊ฐ€์ ์œผ๋กœ LoggingFilter๋ฅผ ํŠน์ •์„œ๋น„์Šค์—๋งŒ ์ ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  .yml์„ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            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

์‹คํ–‰ ์ˆœ์„œ

Spring Cloud Gateway - Eureka ์—ฐ๋™

์ง€๊ธˆ๊นŒ์ง€ ์„œ๋น„์Šค๋ฅผ ์œ ๋ ˆ์นด์— ๋“ฑ๋กํ•˜์ž.

 

๊ฐ๊ฐ์˜ ์„œ๋น„์Šค์— ์œ ๋ ˆ์นด ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

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

 

๋ผ์šฐํŠธ uri ์„ค์ •์‹œ uri๊ฐ€ ์•„๋‹Œ ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋œ lb์ด๋ฆ„์œผ๋กœ ์„ค์ •ํ•˜๋„๋ก ํ•œ๋‹ค. 

ํฌํŠธ๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋žœ๋คํ•œ ํฌํŠธ๊ฐ€ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

routes:
        - id: first-service
          uri: lb://MY-FIRST-SERVICE #http://localhost:8081/ ์ด์ œ ํฌํŠธ๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์Œ

 

ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์œ ๋ ˆ์นด์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค๋ฅผ Discoveryํ•˜๊ณ  ํ•ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ์ฐพ์•„ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

Eureka์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค๋“ค

๋กœ๋“œ ๋ฐธ๋Ÿฐ์Šค ๊ธฐ๋Šฅ

๊ธฐ์กด์˜ first-service์™€ second-service๋ฅผ ๋žœ๋คํฌํŠธ๋กœ ์‹คํ–‰ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ด๋ณด์ž. ๊ทธ๋ฆฌ๊ณ  ์œ ๋ ˆ์นด์—๋Š” ์ธ์Šคํ„ด์Šคid๋กœ ์œ ์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง€๋„๋ก ๋“ฑ๋กํ•˜์ž.

 

๋ณ€๊ฒฝ๋œ .yml ํŒŒ์ผ

server:
  port: 0 # random port

spring:
  application:
    name: my-first-service
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance.id:${random.value}}

๋ฐ”๋€ ์„œ๋น„์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‘ ๊ฐœ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด..

์„œ๋น„์Šค์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์ธ์Šคํ„ด์Šคid๋ฅผ ๋งŒ๋“ค์–ด ์œ ๋ ˆ์นด์— ๋“ฑ๋กํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ด์ œ ํด๋ผ์ด์–ธํŠธ์—์„œ ์š”์ฒญ์ด ์˜ค๋ฉด ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๊ฐ€ ์•Œ์•„์„œ ๊ฐ๊ฐ์˜ ์ธ์Šคํ„ด์Šค์— ์š”์ฒญ์„ ๋ฐฐ๋ถ„ํ•ด์ค€๋‹ค. (๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ)