반응형

1. 서론

이번 포스팅에서는 6장인 스프링 클라우드와 주울로 서비스 라우팅에 대해 알아보도록 하겠습니다.

 

2. 서비스 게이트웨이란?

서비스 게이트웨이는 서비스 클라이언트와 호출될 서비스 사이에서 중개 역할을 하며,

어플리케이션 안의 마이크로 서비스 호출로 유입되는 모든 트래픽에 대해 게이트키퍼 역할을 합니다.

 

아래는 서비스 게이트웨이를 적용한 일반적인 호출 그림입니다.

 

그림을 보시는것과 같이 서비스 게이트웨이는 중앙 집중식 정책 시행 지점의 역할로,

아래와 같은 이점을 얻을 수 있습니다.

 

  1. 정적 라우팅 : 단일 서비스 URL과 API 경로로 모든 서비스를 호출하게 합니다.
  2. 동적 라우팅 : 서비스 요청 데이터를 기반으로 서비스 호출자 대상에 따라 지능형 라우팅을 수행 할 수 있습니다.
  3. 인증 & 인가 : 호출의 맨 앞단에 있기 때문에, 인증 & 인가를 확인하기 최적의 장소입니다.
  4. 측정 지표 수집 & 로깅 : 서비스 호출에 대한 측정 지표와 로그 수집에 용이합니다.
앞장에서 설명한것과 같이 서비스 게이트웨이 역시 잘못 설계 시 병목점이 될 수 있습니다.
책에서는 서비스 게이트웨이 앞단에 로드 밸런서를 두고, 게이트웨이를 stateless로 하는 것을 권장합니다.

 

3. 스프링 클라우드와 넷플릭스 주울 소개

스프링 클라우드에서는 주울이라는것을 통해 서비스 게이트웨이를 제공합니다.

 

아래는 주울이 제공하는 기능입니다.

 

  1. 어플리케이션의 모든 서비스 경로를 단일 URL로 매핑
  2. 게이트웨이로 유입되는 요청을 검사하고 대응할 수 있는 필터 작성

 

주울서버를 만드는 방법은 아래와 같습니다.

 

 

1) 주울 라이브러리 추가

 

compile 'org.springframework.cloud:spring-cloud-starter-netflix-zuul:2.2.3.RELEASE'

 

2) 주울 서비스를 위한 스프링 클라우드 어노테이션 추가

 

주울 서버로 등록하기 위해 @EnableZuulProxy 어노테이션을 추가합니다.

 

@SpringBootApplication
@EnableZuulProxy
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}

 

@EnableZuulServer 도 있으며, 이는 자체 라우팅 서비스를 만들고 내장된 주울 기능을 사용하지 않을 때 사용하는 어노테이션입니다.

 

3) 유레카와 통신하는 주울 구성

 

주울은 스프링 클라우드 제품과 같이 동작하도록 설계되었습니다.

따라서, 자동으로 유레카를 사용해 서비스 ID로 서비스를 찾은 후 넷플릭스 리본으로 주울 내부에서 요청에 대한 클라이언트 측 부하 분산을 수행합니다.

 

아래는 유레카를 사용하도록 application.yml을 수정한 내용입니다.

 

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

 

4. 주울에서 경로 구성

주울은 기본적으로 리버스 프록시입니다.

리버스 프록시는 자원에 접근하려는 클라이언트와 자원 사이에 위치한 중개 서버를 의미합니다.

 

주울은 아래와 같은 프록시 메커니즘을 제공합니다.

 

  1. 서비스 디스커버리를 이용한 자동 경로 매핑
  2. 서비스 디스커버리를 이용한 수동 경로 매핑
  3. 정적 URL을 이용한 수동 경로 매핑

 

1) 서비스 디스커버리를 이용한 자동 경로 매핑

 

주울은 application.yml 을 통해 모든 경로를 매핑하며, 특별한 구성 없이도 서비스 ID를 기반으로 요청을 자동 라우팅 합니다.

 

아래와 같이 서비스의 엔드포인트 경로 첫 부분에 호출하려는 서비스를 표시하여 사용합니다.

 

http://localhost:5555/organizationservice/v1/organizations/~~

 

아래는 유레카와 주울의 조합으로 동작하는 그림입니다.

 

 

 

주울의 매핑 정보를 확인하고 싶은 경우에는 /routes 엔드포인트를 통해 가능합니다.
http://localhost:5555/actuator/routes

 

2) 서비스 디스커버리를 이용한 수동 경로 매핑

 

주울은 유레카 서비스 ID로 자동 생성된 경로에 의존하지 않고 명시적으로 매핑 경로를 정의할 수도 있습니다.

 

아래는 application.yml 을 통해 organizationservice -> organization으로 경로를 수동 매핑한 예제 입니다.

 

zuul:
  ignored-services: 'organizationservice'
  routes:
    organizationservice: /organization/**

 

ignored-services 설정은 자동 생성 된 유레카 서비스 ID 경로를 제외하고 사용자 정의한 경로만 사용할 때 쓰며,

쉼표 구분자로 여러개 기입할 수 있습니다.

 

 

3) 정적 URL을 이용한 수동 경로 매핑

 

주울은 유레카로 관리하지 않는 서비스도 라우팅하도록 기능을 제공합니다.

 

아래는 정적 URL을 수동으로 매핑한 예제입니다.

 

zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      url: http://licenseservice-static:8081

 

이 설정은 한가지 문제점을 가지고 있습니다.

바로 유레카를 사용하지 않아, 요청할 경로가 하나만 있다는 점입니다.

 

하지만, 리본을 사용하여 클라이언트 부하 분산을 이용할 수 있습니다.

 

아래는 리본을 사용하여 정적 URL을 수동 매핑한 예제입니다.

 

zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      serviceId: licensestatic
ribbon:
  eureka:
   enabled: false

licensestatic:
  ribbon:
    listOfServers: http://licenseservice-static1:8081, http://licenseservice-static2:8082

 

 

4) 경로 구성을 동적으로 로딩

 

주울은 경로 구성 정보를 동적으로 로딩이 가능합니다.

컨피그 서버에서 살펴본것과 같이 액츄에이터의 /refresh를 호출하는 방법으로 아래와 같이 application.yml 을 구성하면 됩니다.

 

 

5) 주울과 서비스 타임아웃

 

주울은 히스트릭스 타임아웃 프로퍼티를 설정하여, 오래 수행되는 서비스 호출을 차단하여 성능에 악영향을 끼치지 않도록 할 수 있습니다.

 

아래는 히스트릭스 타임아웃 프로퍼티를 설정한 예입니다.

 

 

위 설정의 경우, 모든 서비스에 대한 타임아웃이 2.5초로 설정되어 집니다.

 

만약, 특정 서비스에 대해 별도로 타임아웃을 설정하기 위해서는 아래와 같이 default 부분을 서비스 ID로 재정의하면 됩니다.

 

기본적으로, 주울은 리본 + 유레카를 사용합니다.

여기서 리본은 5초의 디폴트 타임아웃 설정이 있으며, 위 ribbon.ReadTimeout 을 통해 커스텀이 가능합니다.

 

5. 주울의 진정한 힘! 필터

주울은 모든 서비스 호출의 진입점이기 때문에, 호출에 대해 사용자 정의 로직을 작성할 수 있도록 필터를 제공합니다.

 

아래는 제공하는 필터의 종류입니다.

 

  1. 사전 필터 : 목표 대상에 대한 실제 요청이 발생하기 전에 호출되는 필터
  2. 사후 필터 : 서비스를 호출하고 응담을 클라이언트로 전송한 후 호출되는 필터
  3. 경로 필터 : 서비스가 호출되기 전에 가로채는데 사용되는 필터

 

아래는 필터들이 주울에서 어떻게 동작되는지에 대한 그림입니다.

 

 

 

 

 

 

 

 

반응형

 

 

 

 

 

 

6 . 상관관계 ID를 생성하는 주울의 사전 필터 작성

아래는 주울의 사전 필터를 만든 예제 코드입니다.

 

@Component
public class TrackingFilter extends ZuulFilter{
    private static final int      FILTER_ORDER =  1;
    private static final boolean  SHOULD_FILTER=true;
    private static final Logger logger = LoggerFactory.getLogger(TrackingFilter.class);

    @Autowired
    FilterUtils filterUtils;

    @Override
    public String filterType() {
        return FilterUtils.PRE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private boolean isCorrelationIdPresent(){
      if (filterUtils.getCorrelationId() !=null){
          return true;
      }

      return false;
    }

    private String generateCorrelationId(){
        return java.util.UUID.randomUUID().toString();
    }

    public Object run() {

        if (isCorrelationIdPresent()) {
           logger.debug("tmx-correlation-id found in tracking filter: {}. ", filterUtils.getCorrelationId());
        }
        else{
            filterUtils.setCorrelationId(generateCorrelationId());
            logger.debug("tmx-correlation-id generated in tracking filter: {}.", filterUtils.getCorrelationId());
        }

        RequestContext ctx = RequestContext.getCurrentContext();
        logger.debug("Processing incoming request for {}.",  ctx.getRequest().getRequestURI());
        return null;
    }
}

 

여기서, 유의깊게 볼것은 ZuulFilter 입니다.

 

주울은 ZuulFilter를 구현하게 하여 필터를 등록할 수 있게 제공합니다.

 

아래는 ZuulFilter 를 구현하기 위해서는 아래 4개 메서드를 재정의해야 합니다.

 

public String filterType();
public int filterOrder();
boolean shouldFilter();
Object run() throws ZuulException;

 

 

각 메서드의 의미는 아래와 같습니다.

 

  • filterType : 사전, 경로, 사후필터인지 지정
  • filterOrder : 주울이 다른 필터 유형으로 요청을 보내야 하는 순서
  • shouldFilter : 필터의 활성화 여부
  • run : 필터를 통과할 때 수행되는 로직

 

 

위 TrackingFilter 는 사전필터로, 유입되는 요청에 상관관계 ID를 부여하는 필터입니다.

 

run 메서드에 있는 FilterUtils.setCorrelationId 메서드는 아래와 같습니다.

 

public void setCorrelationId(String correlationId){
    RequestContext ctx = RequestContext.getCurrentContext();
    ctx.addZuulRequestHeader(CORRELATION_ID, correlationId);
}

 

 

 

메서드를 보면 addZuulRequestHeader 를 볼 수 있습니다.

 

주울은 유입되는 요청에 직접 헤더를 추가하거나 수정하는 것을 금합니다.

때문에, 주울이 별도로 관리하는 헤더 맵에 추가해야 합니다.

 

이 주울이 별도로 관리하는 헤더 맵에 데이터를 추가 및 수정하는 메서드가 바로 addZuulRequestHeader 이며,

이는 주울이 서비스를 호출할 때 요청 헤더와 합쳐 전송하게 됩니다.

 

7. 상관관계 ID를 전달받는 사후 필터 작성

주울은 서비스 클라이언트 대신해 실제 HTTP 호출을 수행합니다.

때문에, 주울은 사후필터를 통해 서비스 호출에 대한 응답에 대해서 검사, 수정, 추가 정보를 넣을 수 있습니다.

 

아래는 사후필터의 예제입니다.

 

@Component
public class ResponseFilter extends ZuulFilter{
    private static final int  FILTER_ORDER=1;
    private static final boolean  SHOULD_FILTER=true;
    private static final Logger logger = LoggerFactory.getLogger(ResponseFilter.class);
    
    @Autowired
    FilterUtils filterUtils;

    @Override
    public String filterType() {
        return FilterUtils.POST_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();

        logger.debug("Adding the correlation id to the outbound headers. {}", filterUtils.getCorrelationId());
        ctx.getResponse().addHeader(FilterUtils.CORRELATION_ID, filterUtils.getCorrelationId());

        logger.debug("Completing outgoing request for {}.", ctx.getRequest().getRequestURI());

        return null;
    }
}

 

예제를 보면, 사전필터와 동일하게 ZuulFilter를 상속받은 구조인 것을 아실것입니다.

 

사후 필터로 만들기 위해 filterType 는 FilterUtils.POST_FILTER_TYPE 로 세팅하였으며,

run에서는 서비스 호출 응답에 상관관계 ID를 넣어주는것을 보실 수 있습니다.

 

8. 동적 경로 필터 적용

마지막으로 살펴볼 필터는 동적 경로 필터입니다.

 

동적 경로 필터는 주울로 들어온 요청의 데이터를 통해 어느 서비스를 호출할건지 동적으로 설정할 수 있도록 개발자에게 위임하는 필터입니다.

 

동적 경로 필터는 A/B 테스팅과 같은 작업을 수행할 때 이용할 수 있습니다.

아래는 동적 경로 필터를 사용하여 A/B 테스팅을 수행할 시 이루어지는 그림입니다.

 

 

아래는 동적 경로 필터의 예제입니다.

 

@Component
public class SpecialRoutesFilter extends ZuulFilter {
    private static final int FILTER_ORDER =  1;
    private static final boolean SHOULD_FILTER =true;

    @Autowired
    FilterUtils filterUtils;

    @Autowired
    RestTemplate restTemplate;

    @Override
    public String filterType() {
        return filterUtils.ROUTE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private ProxyRequestHelper helper = new ProxyRequestHelper();
    
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();

        AbTestingRoute abTestRoute = getAbRoutingInfo( filterUtils.getServiceId() );

        if (abTestRoute!=null && useSpecialRoute(abTestRoute)) {
            String route = buildRouteString(ctx.getRequest().getRequestURI(),
                    abTestRoute.getEndpoint(),
                    ctx.get("serviceId").toString());
            forwardToSpecialRoute(route);
        }

        return null;
    }
}

 

위에서 유의 깊게 볼것은 2개가 있습니다.

 

  1. filterType에 동적 경로 필터로 등록하는 filterUtils.ROUTE_FILTER_TYPE
  2. 주울 라이브러리에서 제공하는 ProxyRequestHelper 클래스

ProxyRequestHelper 는 서비스 요청의 프록싱을 도와주는 주울에서 제공하는 helper 클래스입니다.

아래는 helper 클래스를 사용하는 forwardToSpecialRoute 메서드입니다.

 

private void forwardToSpecialRoute(String route) {
    RequestContext context = RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();

    MultiValueMap<String, String> headers = this.helper
            .buildZuulRequestHeaders(request);
    MultiValueMap<String, String> params = this.helper
            .buildZuulRequestQueryParams(request);

    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);

    if (request.getContentLength() < 0) {
        context.setChunkedRequestBody();
    }
    this.helper.addIgnoredHeaders();
    CloseableHttpClient httpClient = null;
    HttpResponse response = null;
    try {
        httpClient  = HttpClients.createDefault();
        response = forward(httpClient, verb, route, request, headers,
                params, requestEntity);
        setResponse(response);
    } catch (Exception ex ) {
        ex.printStackTrace();
    } finally{
        try {
            httpClient.close();
        }
        catch(IOException ex){}
    }
}

 

코드에서 보듯이 주울로 들어온 요청에 대한 header와 params 데이터를

간편하게 복사하는 buildZuulRequestHeaders, buildZuulRequestQueryParams 메서드를 제공하는 것을 볼 수 있습니다.

 

9. 마무리

이번 포스팅에서는 스프링 클라우드와 주울로 서비스 라우팅에 대해 알아보았습니다.

다음에는 마이크로서비스의 보안에 대해 포스팅하겠습니다.

반응형
반응형

1. 서론

이번 포스팅에서는 4장인 서비스 디스커버리에 대해 알아보도록 하겠습니다.

 

2. 서비스 위치 찾기

분산 아키텍처에서는 시스템의 물리적 위치 주소를 알아야하며, 이를 서비스 디스커버리라고 합니다.

 

서비스 디스커버리는 마이크로서비스 아키텍처 & 클라우드 환경에서 매우 중요합니다.

 

아래는 서비스 디스커버리의 이점입니다.

 

  1. 앱 팀은 서비스 디스커버리를 통해 해당 환경에서 실행하는 서비스 인스턴스 개수를 신속하게 수평 확장, 축소 가능합니다.
  2. 서비스 디스커버리 엔진은 사용할 수 없는 서비스 인스턴스로는 요청이 가지 않도록 라우팅하여 앱의 회복성이 향상됩니다.

 

이점만 봤을때는, 기존의 DNS 와 로드밸런서를 두는 것과 다른게 없는것 같습니다.

아래는 일반적인 DNS & 로드밸런서를 두어 사용하는 그림입니다.

 

 

이 구조가 나쁜것은 아닙니다.

하지만, 마이크로서비스 & 클라우드 환경에서는 아래와 같은 이유로 사용하기에 제약이 있습니다.

 

  1. 단일 장애 지점 : 로드밸런서가 전체 인프라 스트럭처의 단일 장애 지점으로, 로드 밸런서에 문제가 생기면 전체 앱도 다운이 됩니다.
  2. 수평 확장의 제약성 : 로드 밸런서 클러스터에 서비스를 모아 연결하므로 부하 분산 인프라 스트럭처를 여러 서버에 수평적으로 확장할 수 있는 능력이 제한됩니다.
  3. 정적 관리 : 로드 밸런서는 일반적으로 서비스를 신속히 등록 및 제거하기에 힘듭니다.
  4. 복잡성 : 로드 밸런서는 라우팅 테이블을 통해 프록시를 하며, 매핑 규칙을 수동으로 조작해야 하기 때문에 복잡성이 증가합니다.

 

3. 클라우드에서 서비스 디스커버리

그렇다면, 클라우드 환경에서 어떠한 점을 고려하여 디스커버리를 사용해야 할까요?

 

클라우드 환경은 서비스 인스턴스가 무수히 많아졌다가 줄어들 수 있기 때문에, 

아래와 같은 이점을 제공하는 서비스 디스커버리를 사용해야 합니다.

 

  1. 고가용성 : 클러스터 지원
  2. 피어 투 피어 : 클러스터의 노드들은 서비스의 상태를 공유
  3. 부하 분산 : 동적으로 서비스 요청의 부하 분산
  4. 회복성 : 서비스 정보를 로컬에 캐싱을 통한 회복성
  5. 장애 내성 : 비정상 서비스를 탐지하여 요청 차단

 

그럼, 위의 이점들을 제공하는 서비스 디스커버리를 선택하였으면 이제 사용해야 합니다.

 

아래는 일반적인 서비스 디스커버리의 동작 아키텍처의 개념과 그림입니다.

 

  • 서비스 등록
  • 클라이언트가 서비스 주소 검색
  • 정보 공유
  • 상태 모니터링

 

 

서비스는 구동 시 서비스 디스커버리에 자신의 물리적 주소를 등록합니다.

보통, 서비스의 각 인스턴스는 고유한 물리적 주소를 가지고 있지만 동일한 서비스 ID로 등록합니다.

동일 서비스에 대한 그룹핑을 위해서 입니다.

 

추가로, 서비스는 일반적으로 1개의 서비스 디스커버리에 등록되며, 정보는 P2P(피어투피어) 모델을 통해 공유합니다.

 

 

이 아키텍처에는 문제점이 하나 있습니다.

바로, A 서비스 인스턴스가 B 서비스 인스턴스를 호출할 때마다 서비스 디스커버리에서 B 서비스 인스턴스들의 정보를 요청하여 

사용하게 된다는 점입니다.

이는 각 서비스간의 서비스 디스커버리와의 결합도가 증가한 것이며, 서비스 디스커버리 장애 시 문제가 될 수 있습니다.

 

이러한 문제를 해결하기 위해, 클라이언트 측 부하 분산을 사용할 수 있습니다.

 

클라이언트 측 부하 분산 방법으로는 클라이언트 로컬에 요청하는 서비스들의 인스턴스 정보들을 캐싱해놓고 사용하는 것입니다.

 

아래는 클라이언트 측 부하 분산을 적용한 아키텍처 그림입니다.

 

 

1) 스프링과 넷플릭스 유레카를 사용한 서비스 디스커버리

 

스프링 클라우드와 넷플릭스에서 제공하는 기능을 이용하여 위 아키텍처를 구성할 수 있습니다.

 

아래는 스프링 클라우드와 넷플릭스 기능을 적용한 그림입니다.

 

 

그림을 보시면 유레카, 리본이라는 것을 볼 수 있습니다.

 

각 개념은 간단히 아래와 같습니다.

 

용어 의미
유레카 서비스 디스커버리로서 서비스 인스턴스들의 위치를 관리
리본 클라이언트 부하 분산을 위해 로컬에 서비스 정보들을 캐싱하며, 주기적으로 유레카에서 데이터를 조회하여 갱신합니다. 추가로 로드밸렁싱 역할을 수행

 

 

반응형

 

 

4. 스프링 유레카 서비스 구축

이제 스프링 부트를 통해 유레카 서비스를 구축하겠습니다.

 

1) 유레카 서버 라이브러리 추가

 

compile 'org.springframework.cloud:spring-cloud-starter-eureka-server:1.4.7.RELEASE'

 

2) application.yml 파일 설정

 

 

유레카는 기본적으로 모든 서비스가 등록할 기회를 갖도록 5분을 기다린 후 등록된 서비스를 공유합니다.

그리고, 서비스 인스턴스의 상태를 확인해야 하기 때문에 유레카는 10초 간격으로 연속 3회의 heartbeat를 받아야 하므로 등록된 서비스는 보여지는데 30초가 소요됩니다.

 

유레카 서버에서 서비스별 정보는 아래와 같이 REST API 를 통해 확인 할 수 있습니다.

 

http://<eureka ip>:8761/eureka/apps/<APPS ID>

 

 

3) EnableEurekaServer 어노테이션 사용

 

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

 

위 3개의 설정으로 유레카 서버의 세팅은 완료되었습니다.

 

5. 스프링 유레카에 서비스 등록

 

이젠 서비스 측에서 위에서 세팅한 유레카 서버에 자신을 등록하는 법을 알아보겠습니다.

 

1) 유레카 클라이언트 라이브러리 등록

 

compile 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.2.3.RELEASE'

 

2) application.yml 파일 설정

 

 

유레카 클라이언트는 아래 두가지 구성 요소가 필요합니다.

 

  1. 어플리케이션 ID : 서비스 인스턴스의 그룹을 의미하며, spring.application.name 으로 설정한 값으로 세팅되어 집니다.
  2. 인스턴스 ID : 개별 서비스 인스턴스를 인식하는 임의의 숫자입니다.

위 fetchRegistry 설정은 서비스 레지스트리에서 가져온 정보를 로컬에 캐싱할지의 설정입니다.

 

6. 서비스 디스커버리를 사용한 서비스 검색

이젠 유레카에 등록을 했으니 데이터를 받아와 사용하는 법을 알아보겠습니다.

 

유레카에서 데이터를 받아와 사용하는 방법은 아래와 같이 3가지가 있습니다.

 

  1. 스프링 디스커버리 클라이언트
  2. RestTemplate 가 활성화된 스프링 디스커버리 클라이언트
  3. 넷플릭스 Feign 클라이언트

 

1) 스프링 DiscoveryClient로 서비스 인스턴스 검색

 

먼저 아래와 같이, @EnableDiscoveryClient 를 사용하여 앱에서 DiscoveryClient를 DI 할 수 있도록 합니다.

 

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

아래는 DiscoveryClient 를 통해 조직 서비스에 call을 날려 데이터를 가져오는 메소드 입니다.

 

@Component
public class OrganizationDiscoveryClient {

    @Autowired
    private DiscoveryClient discoveryClient;

    public Organization getOrganization(String organizationId) {
        RestTemplate restTemplate = new RestTemplate();
        List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");

        if (instances.size()==0) return null;
        String serviceUri = String.format("%s/v1/organizations/%s",instances.get(0).getUri().toString(), organizationId);
    
        ResponseEntity< Organization > restExchange =
                restTemplate.exchange(
                        serviceUri,
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}

 

 

위의 소스는 저수준 코드로 보시면 됩니다.

직접 유레카서버에서 조직 서비스에 대한 인스턴스들을 가져와 자체적으로 로드밸렁싱을 하기 때문입니다.

 

 

2) 리본 지원 스프링 RestTemplate을 사용한 서비스 호출

 

@LoadBalanced 를 통해 리본이 지원하는 RestTemplate 를 생성하도록 지정합니다.

 

@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}
@Component
public class OrganizationRestTemplateClient {
    @Autowired
    RestTemplate restTemplate;

    public Organization getOrganization(String organizationId){
        ResponseEntity<Organization> restExchange =
                restTemplate.exchange(
                        "http://organizationservice/v1/organizations/{organizationId}",
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}

 

위 코드를 보면, 첫번째 방법보다 개발자의 작업이 줄어든것을 볼 수 있습니다.

리본 RestTemplate는 내부적으로 URL에 명시한 APP ID를 통해 캐싱된 서비스 인스턴스에서 호출합니다.

자체적으로 라운드 로빈으로 인스턴스들을 호출하기 때문에 클라이언트측에서 로드밸런싱을 하고 있다고 보시면 됩니다.

 

3) 넷플릭스 Feign 클라이언트로 서비스 호출

 

@EnableFeignClients 를 사용하여 FeignClient를 사용할 수 있도록 합니다.

 

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

@FeignClient("organizationservice")
public interface OrganizationFeignClient {
    @RequestMapping(
            method= RequestMethod.GET,
            value="/v1/organizations/{organizationId}",
            consumes="application/json")
    Organization getOrganization(@PathVariable("organizationId") String organizationId);
}

 

위 소스는 가장 추상화가 되어진 코드로 보시면 됩니다.

코드를 보시면 아시겠지만 스프링 클라우드는 Controller와 비슷하게 작성할 수 있도록 개발자에게 제공하였습니다.

 

내부적으로 리본이 사용되기 때문에 로컬 캐싱 및 RR과 같은 로드밸런싱도 동작합니다.

 

7. 마무리

이번 포스팅에서는 서비스 디스커버리에 대해 알아보았습니다.

다음에는 나쁜 상황에 대비한 스프링 클라우드와 넷플릭스 히스트릭스의 클라이언트 회복성 패턴에 대해 포스팅하겠습니다.

반응형
반응형

1. 서론

이번 포스팅에서는 3장인 스프링 클라우드 컨피그 서버로 구성 관리에 대해 알아보도록 하겠습니다.

2. 구성(그리고 복잡성) 관리

클라우드에서 실행되는 마이크로 서비스의 구성관리는 매우 중요합니다.

사람이 수동으로 구성 및 배포 시 예상치 못한 장애가 발생할 확률이 올라가고, 유연하게 앱을 확장하기가 힘들어 집니다.

 

때문에, 구성관리는 아래의 네 가지 원칙을 고려해야 합니다.

 

  1. 분리 : 실제 물리적인 서비스의 배포와 서비스 구성 정보를 완전히 분리.
  2. 추상화 : 서비스 인터페이스 뒷 단에 있는 구성 데이터의 접근 방식을 추상화.
  3. 중앙 집중화 : 어플리케이션의 구성 정보를 가능한 소수 저장소에 집중화.
  4. 견고성 : 고가용성과 다중성 포함.

 

1) 구성 관리 아키텍처

 

구성관리는 2장에서 살펴본 부트스트래핑 단계입니다.

 

 

부트스트래핑 단계의 과정은 아래와 같습니다.

 

 

  1. 클라이언트는 구성 관리 서비스에게 요청하여 정보를 얻어옵니다.
  2. 실제 구성 정보는 별도 저장소에 있으며 구성 관리 서비스는 저장소에서 조회하여 제공합니다.
  3. 개발자들은 어플리케이션과는 별도로 구성정보를 변경할 수 있도록 빌드 및 배포 파이프라인을 만들어 운영합니다.
  4. 구성관리 서비스는 저장소의 변경을 탐지하여 어플리케이션이 갱신하도록 합니다.

 

이러한 구성관리를 위한 오픈 소스 솔루션으로는 아래와 같이 여러 가지가 있습니다.

  • Etcd
  • 유레카
  • 콘설(Consul)
  • 주키퍼(Zookeeper)
  • 스프링 클라우드 컨피그 서버

 

 

반응형

 

 

3. 스프링 클라우드 컨피그 서버 구축

여기서는 위 종류 중에 스프링 클라우드 컨피그 서버를 사용하겠습니다.

스프링 클라우드 컨피그 서버는 스프링 부트로 만든 REST 기반의 어플리케이션입니다.

 

컨피그 서버를 사용하기 위해서는 일단, 아래와 같이 dependency를 추가해줍니다.

 

compile 'org.springframework.cloud:spring-cloud-config-server:2.2.3.RELEASE'
compile 'org.springframework.cloud:spring-cloud-starter-config:2.2.3.RELEASE'

 

그 후, resources/application.yml 파일에 컨피그 서버 정보를 기입합니다.

기입할 정보는 아래와 같습니다.

 

  • 컨피그 서비스가 수신 대기할 포트
  • 구성 데이터를 제공하는 백엔드 위치

이제 컨피그 서버가 서비스별 제공할 구성 정보를 만듭니다.

컨피그 서버에서 어플리케이션 구성 파일의 명명 규칙은 appname-env-yml 입니다.

 

2장에서 살펴본 라이선싱 서비스의 구성정보를 예로 한다면 아래와 같이 파일을 구성하시면 됩니다.

 

  • file path : resources/config/licensingservice/
  • file name : licensingservice.yml, licensingservice-dev.yml, licensingservice-real.yml

 

1) 컨피그 부트스트랩 클래스 설정

 

스프링 클라우드에서는 아래와 같이 @EnableConfigServer 어노테이션을 사용하여 컨피그 서버로 등록할 수 있습니다.

 

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConfigServerApplication.class, args);
	}
}

 

 

2) 파일 시스템과 스프링 클라우드 컨피그 서버 사용

 

컨피그 서버는 application.yml를 사용하여 앱의 구성 데이터를 보관할 저장소를 지정합니다.

 

가장 간편한것은 파일 시스템 기반이며, 아래는 컨피그 서버의 application.yml 예제입니다.

 

server:
   port: 8888
spring:
  profiles:
    active: native
  cloud:
     config:
       server:
           native:
              searchLocations: file://<chapter 3>/confsvr/src/main/resources/config/licensingservice,
                               file://<chapter 3>confsvr/src/main/resources/config/organizationservice

 

application.yml에서 주의 깊게 볼것은 마지막의 spring.cloud.config.server.native.searchLocations 입니다.

 

각 앱별로 제공할 구성 데이터의 파일 위치를 쉼표를 구분자로 기입해주시면 됩니다.

 

이제 컨피그 서버를 띄우신 다음, 컨피그 서버가 제공하는 구성 데이터를 확인하고 싶으시다면 아래 url을 통해 확인이 가능합니다.

프로퍼티별 정보를 보고 싶다면 uri의 마지막 default를 dev 혹은 real과 같이 구성한 프로퍼티로 수정하면 됩니다.

 

http://localhost:8888/licensingservice/default

 

4. 스프링 클라우드 컨피그와 스프링 부트 클라이언트의 통합

이번에는 컨피그 서버를 이용하는 클라이언트에 대해 진행하도록 하겠습니다.

 

1) 컨피그 서버 클라이언트 라이브러리 추가

 

compile 'org.springframework.cloud:spring-cloud-config-client:2.2.3.RELEASE'

 

2) bootstrap.yml, application.yml 정보 추가

 

클라이언트는 resources 디렉터리 하위에 bootstrap.yml 혹은 application.yml에 컨피그 서버에 대한 정보를 넣으면 됩니다.

 

일반적으로 bootstrap.yml 에는 아래 정보들을 명시합니다.

  • 서비스 어플리케이션 이름
  • 어플리케이션 프로파일
  • 스프링 클라우드 컨피그 서버에 접속할 URI
spring:
  application:
    name: licensingservice
  profiles:
    active: default
  cloud:
    config:
      uri: http://localhost:8888

 

위 spring.application.name 은 컨피그 서버의 디렉터리 이름과 일치해야합니다.

즉, 컨피그 서버에 licensingservice 디렉터리가 있어야 합니다.

 

spring.cloud.config.uri 에는 클라이언트가 접속할 컨피그 서버의 엔드포인트를 기입합니다.

 

일반적으로 application.yml에는 컨피그 서버를 사용하지 못하는 경우에도 사용할 구성 데이터를 명시합니다.

 

 

3) 클라이언트 구성 정보 확인

 

위 컨피그 서버의 구성 정보는 http://localhost:8888/licensingservice/default 를 통해 알았습니다.

그럼, 클라이언트는 컨피그 서버한테 구성 정보를 잘 받아 왔는지 어떻게 확인 할 수 있을까요?

 

스프링 액츄에이터를 사용하면 간편히 http 호출로 확인이 가능합니다.

 

스프링 액츄에이터 라이브러리를 추가 후 아래와 같이 url을 호출하시면 됩니다.

 

http://localhost:8080/actuator/env

 

4) 깃과 스프링 클라우드 컨피그 서버 사용

 

위에서 살펴본 방법은 파일시스템을 이용하여 컨피그 서버의 구성 정보를 관리 했습니다.

 

컨피그 서버는 파일시스템이 아닌 깃과도 연동이 됩니다.

 

깃과 연동할 시에는 컨피그 서버의 application.yml을 아래와 같이 수정해줍니다.

 

server:
  port: 8888
spring:
  cloud:
    config:
      discovery:
        enabled: true
      server:
        encrypt.enabled: false
        git:
          uri: https://github.com/klimtever/config-repo/
          searchPaths: licensingservice,organizationservice
          username: native-cloud-apps
          password: 0ffended

 

spring.cloud.config.server.git.uri 에 깃 레파지토리 주소를 기입합니다.

spring.cloud.config.server.git.searchPaths 는 컨피그 서버가 호스팅하는 서비스들이며 쉼표 구분자를 통해 기입합니다.

searchPaths에 있는 서비스들은 실제로 깃에 디렉터리로 존재해야 합니다.

 

5) 프로퍼티 갱신

 

컨피그 서버가 관리하는 구성 정보가 변경된 경우, 클라이언트들은 변경된 구성정보를 어떻게 다시 가져 올 수 있을까요?

일반적으로 클라이언트는 처음 구동시에만 컨피그 서버에서 정보를 가져옵니다.

 

스프링 액츄에이터는 이를 위해 @RefreshScope 어노테이션을 제공합니다.

@RefreshScope 를 명시한 어플리케이션은 /refresh 엔드포인트가 생성되며 호출 시 스프링 프로퍼티만을 다시 로드합니다.

여기서 스프링 프로퍼티는 컨피그서버에 있기 때문에 다시 컨피그서버에서 가져오게 됩니다.

 

@SpringBootApplication
@RefreshScope
public class Application { 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}

 

5. 중요한 구성 정보 보호

구성 정보에는 노출되면 안되고, 암호화 된 정보로 있어야 할 수 있습니다.

 

이를 위해 스프링 클라우드 컨피그 서버는 쉽게 대칭 및 비대칭 암호화 방법을 제공합니다.

 

구성에 따라, 컨피그 서버에서 암호화를 하여 클라이언트에 제공할 수 있으며,

반대로, 암호화된 정보를 컨피그 서버가 가지고 있고 클라이언트가 복호화하여 사용하게 할 수도 있습니다.

6. 마무리

이번 포스팅에서는 스프링 클라우드 컨피그 서버로 구성 관리에 대해 알아보았습니다.

다음에는 서비스 디스커버리에 대해 포스팅하겠습니다.

반응형
반응형

1. 서론

이번 포스팅에서는 2장인 스프링 부트로 마이크로서비스 구축에 대해 알아보도록 하겠습니다.

 

2. 아키텍트의 이야기: 마이크로서비스 아키텍처 설계

 

마이크로서비스 아키텍처를 구축할 때 프로젝트의 아키텍트는 아래 세 가지 일에 집중해야 합니다.

 

  1. 비즈니스 문제의 분해
  2. 서비스 세분화의 확정
  3. 서비스 인터페이스의 정의

 

1) 비즈니스 문제의 분해

 

마이크로 서비스 아키텍트는 비즈니스 문제를 각 영역을 대표하는 덩이로 분해하고, 영역의 특정 부분과 연관된 비즈니스 규칙과 데이터 로직을 이 덩이들 안에 캡슐화해야합니다.

 

분해하는것은 말처럼 쉬운일이 아닙니다.

하지만, 아래와 같은 지침을 사용한다면 분해하는데에 있어 도움이 될 수 있습니다.

 

  1. 비즈니스 문제를 기술하고 그 문제를 기술하는 데 사용된 명사에 주목 : 문제 기술에 자주 반복되는 명사로 논리적인 영역이 정해집니다.
  2. 동사에 주목 : 문제 기술의 동사를 본다면 행위를 명확히 알 수 있습니다.
  3. 데이터 응집성을 찾아라 : 서로 연관성이 높은 데이터 부분들을 찾아 마이크로서비스가 자기 데이터를 완전히 소유하도록 해야 합니다.

 

아래는 EagleEye의 모놀리식 서비스의 그림입니다.

 

 

이를 분해를 한다면 아래와 같은 데이터 모델이 나옵니다.

 

 

 

 

2) 서비스 세분화의 확정

 

위 데이터 모델을 본다면 조직, 계약, 자산, 라이선스로 4개의 마이크로서비스가 나올것을 예상할 수 있습니다.

 

그렇다면, 위 4개의 서비스는 서로 독립적으로 빌드하고 배포할 수 있어야 합니다.

 

하지만, 데이터 모델에서 서비스를 추출하는 일은 쉬운일이 아닙니다.

이유는, 서비스가 접근하는 실제 데이터베이스 테이블을 서비스에 따라 정리하고 각 서비스가 특정 도메인의 테이블만 액세스하도록 하는 등의 부가적인 일들이 동반되기 때문입니다.

 

아래는 위 데이터 모델을 기반으로 마이크로서비스로 분해했을때의 전체적인 그림입니다.

 

 

 

마이크로 서비스 아키텍처를 구축 시 세분화는 아래와 같은 개념을 이용할 수 있습니다.

 

  1. 큰 마이크로서비스에서 시작해 작게 리팩토링
  2. 서비스간 교류하는 방식에 먼저 집중
  3. 문제 영역에 대한 이해가 깊어짐에 따라 서비스 책임도 계속 변함.

 

3) 서비스 인터페이스의 정의

 

서비스 인터페이스는 마이크로 서비스가 대화하는 방식을 정의하는 것입니다.

 

서비스 인터페이스를 설계할땐, 아래와 같은 지침을 사용할 수 있습니다.

 

  1. REST 철학을 수용
  2. URI를 사용해 의도를 전달
  3. 요청과 응답에 JSON을 사용
  4. HTTP 상태코드 결과를 전달

 

 

반응형

 

 

3. 마이크로서비스를 사용하지 않아야 할 때

 

마이크로서비스가 만능은 아닙니다. 오히려 마이크로서비스를 적용하는것이 독이 될때가 있습니다.

 

아래는 마이크로서비스를 적용하지 않아야하는 할 때 입니다.

 

  1. 분산 시스템 구축의 복잡성
  2. 가상 서버/컨테이너의 스프롤
  3. 어플리케이션 유형
  4. 데이터 변환과 일관성

 

1) 분산 시스템 구축의 복잡성

 

마이크로서비스는 모놀리식에 비해 복잡성이 증가하게 됩니다.

 

때문에, 마이크로서비스에서 필요한 자동화와 운영작업에 투자할 의사가 없는 조직이라면 적용하지 않는데 낫습니다.

 

 

2) 가상 서버 / 컨테이너의 스프롤

 

마이크로 서비스는 클라우드 조합으로 많이 사용합니다.

 

클라우드에서 서비스들을 실행하는데 드는 비용은 저렴하지만 서버를 관리하고 모니터링하는 운영작업은 엄청나게 복잡해질수 있습니다.

 

 

3) 어플리케이션 유형

 

소수 사용자를 위한 어플리케이션을 개발할 때 마이크로서비스와 같은 분산 모델로 구축하는것은 더욱 복잡성을 증대시키며, 오버 스펙입니다.

 

 

4) 데이터 변환과 일관성

 

마이크로서비스 환경에서는 데이터를 변환하고 취합하는 작업이 분산환경으로 인해 어려울 수 있습니다.

 

또한, 각 서비스는 분리가 되어있어 업데이트한 데이터가 즉시 나타나지 않을 수 도 있습니다.

 

4. 개발자 이야기: 스프링 부트와 자바로 마이크로서비스 생성

마이크로서비스에서 구현단계에서는 일관된 방식으로 코드가 배치되도록 하는 것이 중요합니다.

 

아래는 간단히 위 EagleEye의 라이선스 서비스의 골격인 프로젝트 예제입니다.

 

https://github.com/klimtever/spmia-chapter2

 

5. 데브옵스 이야기: 혹독한 런타임 구축

 

마이크로서비스는 데브옵스 관점에서 관리할 프로젝트들이 늘어 힘들어 집니다.

 

때문에, 데브옵스 관점에서 아래 4가지 원칙을 사용하여 빌드 배포에 대해서 일반화를 시켜야합니다.

 

  1. 서비스 어셈블리
  2. 서비스 부트스트래핑
  3. 서비스 등록 및 디스커버리
  4. 서비스 모니터링

 

1) 서비스 어셈블리

 

서비스 어셈블리란 일관된 구축, 패키징 및 배포하는 과정을 의미합니다.

 

아래는 서비스 어셈블리를 도식화한 그림입니다.

 

 

 

기존에는 웹서버 사용시 외부 톰캣을 이용하기 때문에 스프링 버전과 톰캣버전등의 구성편차가 어쩔수 없이 존재했습니다.

하지만, 스프링 부트에서부터는 내장 톰캣이 있어 단일로 소스와 웹서버를 관리 및 배포가 가능해졌습니다.

 

 

2) 서비스 부트스트래핑

 

서비스 부트스트래핑은 마이크로서비스가 처음 가동할 때 시작하며 어플리케이션 구성 정보를 로드합니다.

 

아래는 서비스 부트스트래핑을 도식화한 그림입니다.

 

 

일반적으로, 구성 정보는 구조가 단순하며, 조회는 자주 있지만 변경은 자주 없는것이 특징입니다.

 

때문에, 구성 정보를 저장하기 위해 별도의 데이터베이스를 이용하는것은 오버스펙이 될 수 있습니다.

 

스프링 클라우드에서는 이를 위해 컨피그 서버라는 것을 제공하고 있습니다.

 

 

3) 서비스 등록 및 디스커버리

 

마이크로서비스는 위치 투명성을 가져야 합니다.

 

위치 투명성을 제공하기 위해서는 서비스 인스턴스들을 관리하고 자유롭게 추가 삭제가 되어야 합니다.

 

아래는 이러한 서비스 등록과 관리를 하는것을 도식화한 그림입니다.

 

 

그림에서 보는것과 같이 서비스 디스커버리 에이전트는 각 서비스 인스턴스들을 관리하며,

서비스 인스턴스는 시작 시 이 에이전트에게 자신을 등록시킵니다.

 

서비스 디스커버리는 URL 을 통해 서비스 인스턴스들의 그룹을 만들며, 클라이언트는 이 URL을 통해 서비스를 제공받을 수 있습니다.

 

 

4) 서비스 모니터링

 

서비스 인스턴스 중에 장애가 있는 것들이 있고, 클라이언트는 그로인해 응답을 받지 못할 수 있습니다.

때문에, 서비스 디스커버리는 각 서비스 인스턴스들을 모니터링 해야합니다.

 

아래는 서비스 모니터링을 도식화한 그림입니다.

 

 

그림에서 보는것처럼 서비스 디스커버리는 장애가 난 서비스 인스턴스를 자신의 라우팅 테이블에서 제거하여

클라이언트의 요청이 해당 인스턴스에 가지 않도록 합니다.

 

이러한 상태 관리 모니터링은 스프링에서 제공하는 스프링 액추에이터를 사용할 수 있습니다.

 

스프링 액추에이터는 기본적으로 /actuator URL 엔드포인트를 통해 상태를 확인 할 수 있습니다.

 

아래는 /actuator/health URL을 통해 위 4번 예제의 상태를 확인한 그림입니다.

 

 

6. 마무리

이번 포스팅에서는 스프링 부트로 마이크로서비스 구축에 대해 알아보았습니다.

다음에는 스프링 클라우드 컨피그 서버로 구성 관리에 대해 포스팅하겠습니다.

반응형
반응형

1. 서론

이번 포스팅에서는 스프링 마이크로서비스 코딩 공작소의 1장인 스프링, 클라우드와 만나다 에 대해 알아보도록 하겠습니다.

 

2. 마이크로 서비스란?

마이크로서비스 개념이 발전하기 전, 대부분의 프로젝트들은 모놀리식 아키텍처 형태였습니다.

 

모놀리식의 단점으로는 크고 복잡해질수록 프로젝트를 담당하는 각 팀의 의사소통과 조정 비용이 증가한다는 점이 있습니다.

 

이 단점을, 극복하기 위해 마이크로서비스라는 아키텍처 개념이 나왔고, 특징은 아래와 같습니다.

마이크로서비스는 느슨히 결합된 작은 분산 서비스라고 이해하시면 좋습니다.

 

  1. 어플리케이션 로직을 각자 작은 컴포넌트들로 분해하고 이들을 조합하여 사용
  2. 각 컴포넌트는 작은 책임 영역을 담당하고 완전히 상호 독립적으로 배포되며, 재사용이 가능.
  3. 각 컴포넌트간의 데이터 교환을 위해 HTTP와 JSON 같은 경량 통신 프로토콜을 사용.
  4. 마이크로서비스 기반의 어플리케이션을 다양한 언어와 기술로 구축 가능
  5. 명확히 정의된 책임 영역을 담당하는 조직 운영 가능

 

위 특징들로 인해 어플리케이션은 높은 확장성과 회복성을 얻을 수 있습니다.

 

3. 스프링은 무엇이고 마이크로서비스와 어떤 관련이 있을까?

스프링은 자바 객체간의 의존성 관리를 제공하는 프레임워크입니다.

 

스프링은 현재도 꾸준히 활발한 커뮤니티와 발전이 되고 있으며, 모놀리식에서 마이크로서비스라는 변화에도 맞춰

아래 2개의 프로젝트를 제공합니다.

 

  • 스프링 부트
  • 스프링 클라우드

 

이번 포스팅 책은 프로젝트를 마이크로서비스로 만들때, 위 스프링 부트와 스프링 클라우드를 기반으로 만드는 것을 소개합니다.

 

4. 스프링 부트로 마이크로서비스 구축

위에서 말씀드린것과 같이 스프링 부트와 스프링 클라우드를 기반으로 만들기 때문에 간단히 스프링 부트기반의 간단한 프로젝트를 예제로 살펴보겠습니다.

각자 ide를 통해 spring boot 프로젝트 생성까지 가능하다고 생각하고 진행하겠습니다.

 

예제는 간단하게 http 요청을 받아 문자열 응답을 주는 예제입니다.

 

먼저, spring boot 프로젝트를 생성하셨다면, 'org.springframework.boot:spring-boot-starter-web' 의존성을 추가합니다.

gradle 이라면 build,gradle 파일에 dependencies에 implementation 'org.springframework.boot:spring-boot-starter-web' 를 추가합니다.

 

아래와 같은 자바 클래스를 하나 만듭니다.

간단히, hello/{firstName}/{lastNam} URI로 엔드포인트를 하나 만들어 반환하는 어플리케이션입니다.

 

@SpringBootApplication
@RestController
@RequestMapping(value = "hello")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping(value = "/{firstName}/{lastName}", method = RequestMethod.GET)
    public String hello(@PathVariable("firstName") String firstName, @PathVariable("lastName") String lastName) {
        return String.format("{\"message\":\"Hello %s %s\"}", firstName, lastName);
    }
}

 

  • @SpringBootApplication 은 스프링 부트 서비스의 진입점인 클래스에 지정합니다.
  • @RestController 는 엔드포인트를 만드는 클래스에 지정합니다.
  • @RequestMapping 은 http 요청 uri를 해당 클래스로 매핑하는 역할을 합니다.
  • @PathVariable 은 URL에 전달된 매개변수를 자바 타입으로 매핑해주는 역할을 합니다.

 

위 main을 수행하게되면, 8080 포트로 spring-boot-starter-web이 가지고 있는 내장 톰캣이 구동됩니다.

 

확인을 위해 browser를 열어 아래와 같이 URL을 입력하면 올바르게 응답이 오는것을 확인할 수 있습니다.

 

 

 

 

 

 

 

반응형

 

 

 

 

 

 

 

5. 애플리케이션 구축 방식을 바꾸는 이유

요즈음 어플리케이션을 바라보는 방식이 아래 현상에 영향을 받고 있습니다.

 

  • 복잡성 증가: 어플리케이션은 여러 서비스와 데이터베이스 뿐만이 아닌 외부 서비스와도 통신이 필요합니다.
  • 고객은 더 빠른 출시를 원함
  • 성능 및 확장성 : 어플리케이션은 처리해야할 양에 따라 확장성있게 증가 및 감소해야합니다.
  • 고객은 어플리케이션을 항상 사용할 수 있길 기대: 어플리케이션은 항상 사용이 가능해야 하기 때문에 회복성이 높아야합니다.

 

위 현상으로 인해, 어플리케이션을 마이크로서비스로 구축 시 아래와 같은 장점을 취할 수 있습니다.

 

  1. 유연성 : 코드 단위가 작아져 변경에 대해서 복잡성이 낮아집니다.
  2. 회복성 : 여러 어플리케이션으로 운용되기 때문에 하나의 에러가 전체 어플리케이션에 영향이 가지 않습니다.
  3. 확장성 : 서비스들이 작아져 확장하기에 용이합니다.

 

때문에, 잘 만든 마이크로서비스 어플리케이션은 아래와 같이 정의할 수 있습니다.

 

마이크로 서비스는 확장가능하고 회복적이며 유연한 어플리케이션

 

6. 왜 클라우드와 마이크로서비스인가?

 

마이크로서비스 기반 아키텍처는 각 서비스를 독립된 개별 산출물로 패키징하고 배포함을 의미합니다.

 

때문에, 어플리케이션은 경량화되며 서로 독립적이고 이러한 장점을 극대화하기에는 클라우드와 찰떡궁합입니다.

 

왜냐하면, 클라우드를 통해 어플리케이션의 확장성이 용이해지고

경량화 된 어플리케이션이기 때문에 고성능의 물리적 서버까지는 필요가 없어졌기 때문입니다.

 

 

 

7. 마이크로 서비스는 코드 작성 이상을 의미

마이크로서비스 작성 시에는 아래와 같은 항목들을 고려해야합니다.

 

  1. 적정 크기 : 마이크로서비스가 과도한 책임을 맡지 않도록 적정한 크기로 만드는 방법
  2. 위치 투명성 : 서비스 클라이언트에 영향을 주지 않고 서비스 인스턴스를 추가/삭제할 수 있는 방법
  3. 회복성 : 서비스에 문제가 있을 때, 서비스 클라이언트는 '빨리 실패'하는 방법
  4. 반복성 : 새로운 서비스 인스턴스가 시작될 때마다 기존 인스턴스와 동일한 코드와 구성으로 유지할 수 있는 방법
  5. 확장성 : 서비스 간 의존성을 최소화하여 확장할 수 있는 방법

 

포스팅하는 책에서는 위 항목들을 아래와 같은 패턴 기반으로 접근하며, 사용하는 기술로는 스프링 부트와 스프링 클라우드입니다.

 

  • 핵심 개발 패턴
  • 라우팅 패턴
  • 클라이언트 회복성 패턴
  • 보안 패턴
  • 로깅 및 추적 패턴
  • 빌드 및 배포 패턴

 

1) 핵심 개발 패턴

 

핵심 개발 패턴은 아래 사항들을 중점적으로 다룹니다.

 

  1. 서비스 세분성 : 각 마이크로서비스가 정적 수준의 책임을 갖게 하는 방법
  2. 통신 프로토콜 : 마이크로서비스간의 데이터 교환
  3. 인터페이스 설계 : 개발자가 서비스 호출에 사용하는 실제 서비스 인터페이스를 설계
  4. 서비스간 이벤트 프로세싱 : 서비스간 의존성을 최소화하고 어플리케이션 회복성을 높이기 위한 방법

 

2) 라우팅 패턴

 

클라우드 기반 어플리케이션들은 수백 개의 마이크로 인스턴스가 실행 중일 수 있습니다.

때문에, 서비스의 물리적 IP 주소를 추상화하고 서비스 호출에 대한 단일 진입점을 만들어야 하는 필요성이 있습니다.

 

이를 위해, 서비스 디스커버리와 서비스 라우팅이라는 기능을 사용합니다.

 

  • 서비스 디스커버리 : 클라이언트에서 서비스의 물리적 위치를 추상화하여 투명하게 새 인스턴스를 추가하고 제거하는것을 제공
  • 서비스 라우팅 : 마이크로서비스 클라이언트에 인가 및 인증, 콘텐츠 검사 등 정책 시행 지점으로 사용되는 논리적 단일 URL을 제공

 

3) 클라이언트 회복성 패턴 

 

마이크로서비스 아키텍처는 한개의 서비스 문제가 서비스 소비자에게 연쇄적으로 발생하지 않도록 아래와 같은 4가지 클라이언트 회복성 패턴을 다룹니다.

 

  1. 클라이언트 측 부하 분산 : 서비스 클라이언트는 서비스 디스커버리에서 검색한 엔트포인트를 캐싱하고 인스턴스 간 서비스의 호출 부하를 분산합니다.
  2. 회로 차단기 패턴 : 서비스 클라이언트가 실패한 서비스를 반복적으로 호출하지 않게 하기 위해, 회로 차단기가 '빨리 실패'하도록 합니다.
  3. 폴백 패턴 : 클라이언트가 실패할 때, 데이터를 검색 등의 대체 방법을 제공합니다.
  4. 벌크헤드 패턴 : 오작동하는 서비스 하나가 클라이언트의 모든 리소스를 차지하지 않도록 클라이언트에서 다른 서비스 호출을 격리합니다.

 

4) 보안 패턴

 

해당 포스팅 책에서는 아래 3가지 보안패턴을 다룹니다.

 

  1. 인증
  2. 인가
  3. 자격 증명 관리와 전파 : 서비스 클라이언트가 여러 서비스 호출에서 자격 증명을 항상 제시하지 않아도 되는 방법

 

5) 로깅 및 추적 패턴

 

마이크로서비스의 단점은 어플리케이션과 서비스안에서 어떤일이 있어났는지 디버깅과 추적이 어렵다는 점입니다.

 

때문에, 아래와 같은 로깅 및 추적관련 패턴이 있습니다.

 

  • 로그 상관관계: 단일 트랜잭션에 대해 여러 서비스간 생성된 모든 로그를 상관관계를 매기는 방법
  • 로그 수집 : 개별 서비스 인스턴스에서 생성된 모든 로그를 질의 가능한 단일 데이터베이스로 취합하는 방법
  • 마이크로서비스 추적 : 트랜잭션과 연관된 모든 서비스에서 클라이언트 트랜잭션 흐름을 시각화하는 방법

 

6) 빌드 및 배포 패턴

 

마이크로서비스 아키텍처는 각 인스턴스가 모두 동일해야 한다는 점입니다.

이 말은, 서버가 배포된 이후의 변경으로 발생하는 '구성 편차'가 없어야 한다는 의미입니다.

 

8. 스프링 클라우드로 마이크로서비스 구축

 

위 패턴들에 사용할 기술들은 아래 그림으로 한눈에 볼 수 있습니다.

스프링 클라우드와 넷플릭스가 있습니다.

 

 

9. 마무리

 

이번 포스팅에서는 스프링, 클라우드와 만나다에 대해 간단한 소개와 설명을 했습니다.

다음에는 스프링 부트로 마이크로서비스 구축에 대해 포스팅하겠습니다.

반응형

+ Recent posts