본문 바로가기
Framework/🍃Spring

Spring cloud를 통해 MSA 구현해보기 - 3탄 Eureka, API Gateway를 통한 서버사이드 서비스 디스커버리 패턴 구현

by 발개발자 2022. 2. 8.
반응형

이제 서비스 앞단에 API Gateway 위치시키는 ServerSide Service Discovery 패턴을 구현하고자 한다.

 

먼저 API Gateway가 먼지 araboja.

프로토스 게이트웨이는 아닙니다 껄껄,,

 

API Gateway란?

 

API Gateway는 API의 요청자인 Client와 Server를 연결하는 중계자(proxy) 역할이다.

 

 

API Gateway 이점을 정리하면 아래와 같다.

  1. 인증/인가 일괄 처리
  2. 로드밸런싱을 통한 트래픽 분산
  3. 로깅처리
  4. 서킷 브레이크 통한 서비스 관리

 

이러한 API Gateway 사용할 있는 오픈 소스에는 Netflix Zuul, Spring cloud gateway, ServiceComb EdgeService 등이 있다.

원래 Netflix Zuul 이용하고자 했지만, Spring boot 2.4.X부터는 zuul, hystrix가 더 이상 제공되지 않으며, Spring cloud 커뮤니티에서 Spring cloud gateway 권고하고 있다.

따라서 Netflix Zuul 대신 Spring cloud gateway 이용하여 API Gateway 구성하고자 한다.

 

자자 그러면 이제 침착하게 시나리오부터 짜보자,,

 

  • 시나리오

NetFlix Eureka를 통해 Eureka Server(서비스 레지스트리)를 구현한 후, SpringCloud Gateway를 구현하여 Eureka Client로 등록해준다. 그리고 기존에 구현한 3개의 서비스를 그대로 Eureka Client로 등록해준다.

정리하자면, Service Client들의 정보를 담당하는 Eureka 서버를 먼저 구성한 후 , Gateway, Service-a, Service-b, Service-b 4개의 Eureka 클라이언트 서비스를 구성하여 모든 서비스 호출은 Gateway 담당하며 전달해주고자 한다.

 

따라서 Service-a에서 Service-b 호출할 Gateway 거쳐야 하며, 외부 Client에서 Service호출시에도 Gateway 거쳐야 한다.

결국 외부 클라이언트건, 같은 Service계층의 호출이건 Service호출의 담당자인 Gateway 정보만 알면 된다.

 

 

 

 

그렇다면 Gateway 구현해보자.

 

 

  • Gateway 구현

 

  • Spring starter를 이용해 Gateway, Eureka Discovery Client를 추가한 후 프로젝트 생성

 

 

 

 

 

  • applicataion.yml 작성

 

applicataion.yml

server:
  port: 8800 #gateway의 port

eureka:
  client:
    fetch-registry: true # 유레카 클라이언트 활성화
    register-with-eureka: true # 유레카 클라이언트 활성화
    service-url:
      defaultZone: http://localhost:8761/eureka # 유레카 클라이언트로 등록

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: service-a
          uri: lb://SERVICE-A # 포워딩할 주소
          predicates:
             Path=/service1/** # 해당 gateway 서버의 /service1/**로 들어오는 요은 service-a로 인식하겠다는 조건
        - id: service-b
          uri: lb://SERVICE-B # 포워딩 할 주소
          predicates:
            - Path=/service2/** # 해당 gateway 서버의 /service2/**로 들어오는 요은 service-b로 인식하겠다는 조건

 

 

위와 같은 설정을 통해 http://gateway주소:8800/service1/ 통해 클라이언트가 호출할 경우, Gateway service-a 포워딩 시켜준다. 마찬가지로 http://gateway주소:8800/service2/ 통해 클라이언트가 호출할 경우, service-b 포워딩 시켜준다.

 

 

기존에 작성한 Eureka서버와 Service a, b 그리고 새로 작성한 Gateway 서버를 실행시켜준다.

 

Eureka 서버에 정상적으로 등록된 확인할 있다.

 

 

 

그러면 첫번째 상황인, 외부 클라이언트에서 Service 호출을 테스트해보자.

 

 

 

 

 

위의 결과를 보면, Gateway가 Service를 정상적으로 호출한  있고 동일 route id 여러 개의 인스턴스가 있으면 자동으로 로드밸런싱까지 시켜주는걸 확인할 있다. 즉, 오토스케일링으로 시스템을 자동으로 늘려줘도 즉시 가용할 있다는 장점이 있다.

 

 

 

그러면 Service에서 Gateway를 호출하는 상황을 구현해보기 위해 기존의 클라이언트사이드 로드밸런싱으로 구현된 Service1 수정해보자.

 

 

 

  • Service1Controller 수정 소스

 

@RestController
@RequestMapping("/service1")
public class Service1Controller {

 @Autowired
 private DiscoveryClient discoveryClient;

 private static final String GATEWAY_NAME = "GATEWAY-SERVICE";

@GetMapping("/test")
    public String callServiceA() throws UnsupportedOperationException, IOException {
        String url = "";
        String apiPath = "/service2/statuscheck";
        List<ServiceInstance> instance = discoveryClient.getInstances(GATEWAY_NAME);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response=null;
        
		url = instance.get(0).getUri().toString();
        url += apiPath;

        try{
            response=restTemplate.exchange(url,HttpMethod.GET, getHeaders(),String.class);
        }catch (Exception ex){
            System.out.println(ex);
        }

        System.out.println(response.getBody());

		return "Service-A: inst001 호출" + " > " + response.getBody().toString();
    }



    private static HttpEntity<?> getHeaders() throws IOException {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
        return new HttpEntity<>(headers);
    }



}

 

 

기존에 peer to peer 방식으로 Service1에서 Service2 다이렉트로 호출하는 소스를 Service1에서 Gateway 호출하는 소스로 수정하였다.

 

 

 

  • 그러면, 두번째 상황인 Service1에서 Gateway를 호출하여 Service2의 응답을 받는 테스트를 해보자.

 

 

 

 

위의 결과를 보면, Service-a Eureka 서버에 등록된 Gateway 정보를 불러와서 해당 IP 호출을 하여 Service-B 응답을 받는 것을 정상적으로 확인할 있다.

즉, 외부 Client => Gateway => Service-A => Gateway => Service-B 순으로 호출이 되며, 응답은 역순으로 받아와지게 된다.

 

 

  • 결론

먼저, Eureka Server와 EurekaClient를 통해 동적으로 변경되는 서비스들의 IP나 Port등과 같은 정보들을 서비스명으로 관리하며 서로의 정보를 쉽게 가져올 수 있었다. 여기서 설정을 통해 서버와 클라이언트는 HeartBeat를 주고 받을 수 있으며, 이를 통해 서버는 서비스가 가동 중인지 중단 중인지 확인하고 서버에서 등록 해제를 할 수 있다.

 

그리고 SpringCloud Gateway를 통해 ServerSide Service Discovery 패턴의 L4, L7스위치역할을 하는 Gateway를 구현할 수 있었다. 이를 통해 모든 Client의 요청은 Gateway가 담당을 하며, EndPoint가 단일화되기 때문에 PreHandle, PostHandle 을 통해 다양한 전, 후처리를 할 수 있다.

또한 서비스의 인스턴스가 다수인 경우 로드밸런싱도 원활하게 진행되는 걸 확인할 수 있었다. 하지만 Gateway도 결국 하나의 서버가 기용되어야 하며, 모든 트래픽이 몰리기 때문에 고려해야할 사항이 많을 것이다. 추후에 시간이 있는 경우, API Gateway에서 Filter를 통해 인증, 인가, 로깅 등 좀 더 정교한 기능을 장착해보고자 한다. 

 

그럼 최초의 목표였던 Ribbon을 통한 ClientSide Service discovery 구현과 SpringCloud Gateway를 통한 ServerSide Service discovery 구현을 완료하고 개념이해 및 테스트해본 것에 의의를 두며, 글을 마쳐야 겠다. 그럼 20000...

 

소스자료

Git : asd9211/msa (github.com)

반응형

댓글