Spring Cloud

진이진이·2022년 6월 21일
0
post-thumbnail

Spring Cloud란

마이크로서비스의 개발, 배포, 운영에 필요한 아키텍처를 쉽게 구성할 수 있도록 지원하는
Spring Boot기반의 프레임워크로 "MSA구성을 지원하는 Springboot기반 Framework" 다

Service Discovery

마이크로서비스 아키텍처(MSA)로 구성되어 있는 서비스들은 각자 다른 IP와 Port를 가지고 있으며 동적으로 변경될 가능성이 많기 때문에 이러한 정보에 대해서 저장하고 관리해주는 것이 Service Discovery의 역활이다.

  • Service Discovery에는 두 가지의 구현 방법이 있다.

    • 클라이언트 사이드 디스커버리 패턴(Client-Side Discovery pattern)
      : 서비스 클라이언트가 Service register에서 서비스의 위치를 찾아서 호출하는 방식
      EX ) Netflix OSS ( Eureka )

    • 서버 사이드 디스커버리 패턴(Server-Side Discovery pattern)
      : 로드밸런서를 호출하면 Service Register로 부터 등록된 서비스의 위치를 전달하는 방식
      EX ) AWS Elastic Load Balancer(ELB), Kubernetes

두 가지 중 Eureka에 대해 적어본다.

🧐Eureka

AWS와 같은 Cloud 시스템에서 서비스의 로드 밸런싱과 실패처리 등을 유연하게 가져가 위해 각 서비스들의 IP / Port / InstanceId를 가지고 있는 REST 기반의 미들웨어 서버

Netflix는 2007년 심각한 DB손상으로 3일간 서비스 장애를 겪은 후, 신뢰성 높고 수평확장이 가능한 Cloud System으로 이전을 해야 함을 느꼈고 이 때 쌓은 경험치를 바탕으로 MSA 기술을 Spring에게 오픈소스로 풀게 되었다.

Eureka Server와 Client의 상호작용

Eureka-Server와 Eureka-Client는 REST 통신하여 상호작용을 한다.

  • Eureka Server
  1. Spring boot에서 dependencies에 Eureka server 추가하고 프로젝트 생성

  2. @EnableEurekaServer

  3. application.properties

    server.port=8761
    
    spring.application.name=discovery-service
    
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
  • Eureka Client
  1. Spring boot에서 dependencies에 Eureka Discovery Clinet 추가하고 프로젝트 생성

  2. @EnableEurekaClient

  3. application.yml

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

Spring Cloud Gateway(SCG)

  • 필요한 이유
    API Gateway가 필요한 이유는 안전한 API유통과 Client 요청별로 유연하게 대처하기 위해서

      1. Frontend 개발자 에게 통일된 요청 정보가 필요함. Eureka는 단지 등록된 서비스들을 관리하는 역할만 함.
      2. Load Balance와 유저 인증등 통일된 로직을 한곳에서 할 수 있음.
      3. Nexflix Zuul이 있으나 2.4부터 nexflix 서비스들은 유지만 하는상태
      4. Spring 측에서 Zuul을 대체한 Spring cloud Gateway를 만듬. (netty vs tomcat)
                                                               Zuul은 Web/WAS로 Tomcat을 사용하고, SCG는 Netty를 사용
  • Spring Cloud Gateway

  1. dependencies
    Eureka Discovery Clinet, Gateway, Lombok, Spring Web

  2. @EnableEurekaClient

  3. application.yml

      server:
      port: 8000
    
    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}}
    
    spring:
      application:
        name: gateway-service
    
      cloud:
        gateway:
          routes:
            - id: my-serv1
              uri: lb://MY-SERVICE1
              predicates:
                - Path=/serv1/**
    
            - id: my-serv2
              uri: lb://MY-SERVICE2
              predicates:
                - Path=/serv2/**

Filter 추가

 cloud:
    gateway:
      routes:
        - id: my-serv1
          uri: lb://MY-SERVICE1
          predicates:
            - Path=/serv1/**
          filters:
            - RewritePath=/serv1/?(?<segment>.*), /$\{segment}

RewritePath 확인

@RestController
public class MyController {
    @GetMapping("/test")
    public String test() {
        return "test";
    }
}
  • Logging Filter 와 Global Filter 적용
    Logging Filter

    @Component
    @Slf4j
    public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {
        public LoggingFilter() {
            super(Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            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 Filter Start: request id -> {}", request.getId());
                }
    
                //Custom Post Filter
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    if(config.isPostLogger()) {
                        log.info("Logging POST Filter End: response code -> {} ", response.getStatusCode());
                    }
                }));
            }, Ordered.LOWEST_PRECEDENCE); // 우선순위 지정할 수 있다
            //HIGHEST_PRECEDENCE
    
            return filter;
        }
    
        @Data
        public static class Config {
            private String baseMessage;
            private boolean preLogger;
            private boolean postLogger;
        }
    
    }

    Global Filter

     @Component
    @Slf4j
    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();
    
               log.info("Global Filter 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 code -> {}", response.getStatusCode());
                   }
                   log.info("Custom Post");
               }));
           });
       }
    
       @Getter @Setter
       public static class Config {
           String baseMessage;
           private boolean preLogger;
           private boolean postLogger;
       }
    }

    application.yml

     server:
     port: 8000
    
     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}}
    
     spring:
       application:
         name: gateway-service
    
     cloud:
       gateway:
         routes:
           - id: my-serv1
             uri: lb://MY-SERVICE1
             predicates:
               - Path=/serv1/**
             filters:
               - RewritePath=/serv1/?(?<segment>.*), /$\{segment}
               - name: LoggingFilter
                 args:
                   baseMessage: Hi, serv1.
                   preLogger: true
                   postLogger: true
    
           - id: my-serv2
             uri: lb://MY-SERVICE2
             predicates:
               - Path=/serv2/**
             filters:
               - RewritePath=/serv2/?(?<segment>.*), /$\{segment}
               - name: LoggingFilter
                 args:
                   baseMessage: Hi, serv2.
                   preLogger: true
                   postLogger: true
         default-filters:
           - name: GlobalFilter
             args:
               baseMessage: Spring Cloud Gateway Global Filter
               preLogger: true
               postLogger: true

profile
개발 어린이

0개의 댓글