brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Apr 29. 2018

마이크로서비스 분산 모니터링

- Spring Cloud Sleuth, Zipkin 활용 예시

Overview

"마이크로서비스 분산 환경 모니터링" 이라는 주제로, 마이크로서비스 아키텍처에서 분산 환경 모니터링 필요성에  대해서 설명하고,  Spring Cloud Sleuth 와 Zipkin 을 연동하여 분산 환경에서 이벤트 로그 모니터링 방법에 대하여 소개한다. 

by Eddy.Kim


1. 관련연구

 - 마이크로서비스 분산 환경 모니터링

 - Zipkin(Dapper, 네이버 Pinpoint)

 - Spring Cloud Sleuth

2. 기본 연동 과정

 - Zipkin설치

 - Spring Cloud Sleuth 설정

3. 예제 구축

 - 서비스 지연 발생

 - 서비스 장애 발생

4. 기타 Tips 정리

5. 글 마무리


1.관련연구


1.1 마이크로서비스 분산 환경 모니터링

마이크로서비스 아키텍처의 분산 시스템에서는 하나의 요청에 서로 다른 시스템에서 애플리케이션이 실행되어야 하는 경우가 많다. 분산 아키텍처에서 특정 이벤트 또는 작업에 대해서 오류가 발생했다고 가정하다. 어떤 애플리케이션에서 문제가 발생하였는지 아는 것이 중요하고, 해당 이벤트에 대한 로그 추적이 중요하다. 이벤트에 대한 직접적인 오류 분석을 위해서는 각 애플리케이션에서의 작업을 추적ID 로 연결해야 하고, 이 추적ID를 중앙에서 수집하여 분석하는 시스템이 필요하다. 


1.2 Zipkin

Zipkin 시스템 구성도

Zipkin 은 트위터에서 사용하는 분산 환경 추적 시스템 오픈소스이다. 기본적인 아키텍처는 Google Drapper 에서 발전하였고, 분산환경에서의 시스템 병목 현상을 파악할 수 있다. Zipkin은 아래 구성도와 같이 Collector, Query Service, DataBase, WebUI 로 구성된다.

시스템 구성도 예시

Trace, TraceID, Span

Zipkin 은 3개 마이크로서비스의 요청을 Trace로 간주하고 각 Trace는 유니크한 TraceID가 부여된다. 각 Trace에는 몇개의 Span을 포함하며, Span 에는 명시적으로 이름을 부여할 수도 있다. Span는 작업 단위라고 생각하면 된다. 3개의 마이크로서비스가 있다고 가정하자. 

마이크로서비스 A : 클라이언트 애플리케이션

마이크로서비스 B : 외부 External API 

마이크로서비스 C : 내부 API

A-서비스에서 특정 이벤트를 요청하였다. A-서비스는 B-API 를 호출하고, B-API 는 C-API 를 호출한다. 리턴은 반대 방향으로 응답한다. 각각의 마이크로서비스에서는 실행 로그를 Zipkin Collector 에 전달해야 하는데, 동일한 TraceID 를 포함해서 로그를 전달해야 한다. 그렇기 때문에, 마이크로서비스는 이전에 실행되었던 마이크로서비스의 TraceID 를 알아야 하고, 그 TraceID 는 각 서비스로 운반이 되어야 한다. TraceID 는 HTTP Header 를 사용하여 전달한다. 요청/응답에 대한 Flow는 아래 그림을 참고하자. 


네이버 Pinpoint

네이버 Pinpoint 는 Dapper 기반으로 만들어진 오픈소스 분산 모니터링 툴이다. 정말 좋은 오픈소스인것 같다.

http://d2.naver.com/helloworld/1194202


1.3 Spring Cloud Sleuth

1.2 Zipkin에 대해서 설명한 Zipkin 에 이벤트 로그를 보내는 작업은 직접 코드를 짜서 구현해도 되지만, 이 글에서는 Spring Cloud Sleuth 를 연동하는 방법으로 진행한다.


Spring Cloud Sleuth

http://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/1.3.3.RELEASE/single/spring-cloud-sleuth.html



2.기본 연동 과정


2.1 Zipkin 설치

방법1 - Jar 파일 실행


curl -sSL https://zipkin.io/quickstart.sh | bash -s

java -jar zipkin.jar


방법2 - 도커 이미지 실행


docker run -d -p 9411:9411 openzipkin/zipkin


방법3 - 스프링 @EnableZipkinServer


//디펜던시 추가

<dependency>
<groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
    <scope>runtime</scope>
</dependency>


//Enabling Zipkin Server

@SpringBootApplication

@EnableZipkinServer

public

class

ZipkinApplication {...}


필자는 방법1로 진행하였다. 


실행 후 포트 확인 및 WebUI 접속

Zipkin 이 실행하면 9411 포트가 실행될 것이다. .http://zipkin도메인:9411 로 접속해보면 Zipkin WebUI 에 접속할 수 있다. 하지만, 아무 작업도 안했기 때문에 Trace 정보가 쌓이진 않았다. 또한, 외부 DB 를 연동하지 않았기 때문에 해당 Zipkin 서버는 재부팅하면 쌓였던 데이터는 사라질 것이다. 간단하게 테스하는거라서 일단 이렇게 진행한다. 여유가 된다면, 나중에 외부 퍼시스턴스 DB 로 연동하는게 좋을 것이다. 


2.2 Spring Cloud Slueth 연동


//build.gradle

dependencies {  

   compile('org.springframework.cloud:spring-cloud-starter-sleuth')  

   compile('org.springframework.cloud:spring-cloud-starter-zipkin')  


//application.properties

spring.application.name=애플리케이션이름

spring.zipkin.baseUrl=http://zipkin서버:9411/  

spring.sleuth.sampler.probability=1


baseUrl 는 zipkin 서버와 포트를 지정한다. 따로 변경하지 않는다면 9411포트일 것이다. probability 는 로그 수집의 퍼센트를 설정한다. 1로 하면 100프로의 확률로 전송한다. 즉, 무조건 zipkin 으로 로그를 전송한다. 0.1 로 설정한다면 1/10의 확률로 Zipkin 서버로 로그를 전송한다.  테스트를 위해서 100프로 전송한다고 가정한다.



3.예제 구축


샘플 예제는 아래와 같이 3개의 애플리케이션으로 구성한다.

마이크로서비스-1 : 클라이언트단 애플리케이션 8081 포트

마이크로서비스-2 : External API 8082 포트

마이크로서비스-3 : Internal API  8083 포트

참고로, Zipkin UI 에서는 마이크로서비스 간의 호출 방향이 Dependencies 라는 메뉴에 표시된다. 


3.1 강제로 서비스 지연을 발생시켜 보자.

마이크로서비스-1

첫번째 마이크로서비스는 클라이언트단에서 External API 를 호출하는 애플리케이션이다. 


//Controller 클래스

@Autowired
HomeService homeService;

@RequestMapping("/")
public String home() {
    return homeService.coffeeList();
}



//Service 클래스

@Service
public class HomeService {

    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    public String coffeeList(){
        return restTemplate.getForObject("http://localhost:8082/external/coffees", String.class);
    }
}


//application.properties

spring.application.name=microservice-1
server.port=8081
spring.zipkin.baseUrl=http://zipkin서버:9411/
spring.sleuth.sampler.probability=1


마이크로서비스-2

마이크로서비스-2 는 

마이크로서비스-1 에 요청을 받음 --> 마이크로서비스-3에 요청 --> 마이크로서비스-3으로부터 응답을 받음 --> 마이크로서비스-1 에 응답을 보냄


단, 이때 강제로 딜레이 지연 타임 7초를 주었다. 


//Controller

@RequestMapping("/external/coffees")
public String delay(@RequestHeader(value="X-B3-TraceId") String traceId) {

    try {
        //강제로 지연 오류 발생
        if(true) {
            Thread.sleep(7000);
            log.info("딜레이 오류 발생");
            return restTemplate.getForObject("http://localhost:8083/internal/coffees", String.class);
        }
    } catch (HttpServerErrorException e) {
        return "HttpServerErrorException";
    } catch (InterruptedException e) {
        e.printStackTrace();
        return "InterruptedException";
    }
    
    return null;
}


//application.properties

spring.application.name=microservice-2
server.port=8082
spring.zipkin.baseUrl=http://zipkin서버:9411/
spring.sleuth.sampler.probability=1


이때 전달 된 TraceID 를 확인하고 싶다면 아래와 같이 RequestHeder 에서 확인할 수 있다.

public String delay(@RequestHeader(value="X-B3-TraceId") String traceId) {



마이크로서비스-3

마이크로서비스-3 은 간단하게 String 만 리턴한다.


@RequestMapping("/internal/coffees")
public String coffeeList(@RequestHeader(value="X-B3-TraceId") String traceId) {

    return "아메리카노, 라떼, 모카";
}


//applicatin.properties

spring.application.name=microservice-3
server.port=8083
spring.zipkin.baseUrl=http://zipkin서버:9411/
spring.sleuth.sampler.probability=1



3.2 Zipkin WebUI 확인

마이크로서비스-1의 localhost:8081 을 호출하면 마이크로서비스-2,3 에 요청/응답으로 화면에 조회된 응답리스트가 화면에 노출된다.

응답 화면

Zipkin WebUI 에서 Trace 로그 리스트를 확인할 수 있다. 

해당 Trace를 클릭하면 아래와 같이 Trace 추적을 확인할 수 있는데 microservice-2에서 7초의 지연 시간이 발생한 것을 아래 캡처 화면과 같이 확인할 수 있다. 


3.3 강제로 오류를 발생시켜 보자.

이번에는 오류를 발생시켜 보자. 마이크로서비스-3 에 아래와 같이 Exception 에러를 발생시켰다. 


@RequestMapping("/internal/coffees")
public String coffeeList(@RequestHeader(value="X-B3-TraceId") String traceId) throws Exception {
    //임의로 장애를 발생시켜보자.
    if(true){
        throw new Exception("장애 발생");
    }

    return "아메리카노, 라떼, 모카";
}


마이크로서비스-2 에서는 HttpServerErrorException를 try/catch에 잡히도록 설정하였다. 자, 아래와 같이 빨간색 장애 포인트를 확인할 수 있다. 

장애 포인트를 확인할 수 있다. 

상세 화면을 아래와 같이 확인할 수 있다. 


마이크로서비스-1 에서는 error 메시지만 노출되기 때문에 실제로 마이크로서비스-2 또는 3 어디에서 오류가 발생했는지 확인할수는 없지만, Zipkin 에 수집된 정보를 바탕으로 마이크로서비스-3 에서 장애가 발생했다는 것을 확인할 수 있다. 



4.기타 Tips 정리


1. @SpanName 어노테이션을 사용하면 Span 이름을 지정할 수 있다. 

2. 수동으로 Span 을 생성할 수 있다. 
지금까지는 Spring Cloud Sleuth 라이브러리에서 TraceID, Span 등을 알아서 잘 생성하지만, 필요에 의해서는 수동으로 SPAN 을 생성할 수 있다. 


@Autowired
private Tracer tracer;

생략...

//신규 SPAN 생성 테스트
Span newSpan = tracer.newTrace().name("New-SPAN").start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(newSpan.start())) {
}finally {
    newSpan.finish();
}


3. 공부 중...

오늘은 이정도로만 보고, 

나중에 실제로 프로젝트에 연동할 기회가 생기면 그때 다시 공부해서 정리할 예정이다. 


상세하게 공부하고 싶은데, 오늘도 수박겉핥기... 수준에서 글을 마무리할 것 같다.



5.글 마무리


간단하게 마이크로서비스 환경에서의 분산 로그 모니터링을 위한 Zipkin 및 Spring Cloud Sleuth 연동에 대해서 정리하였다. 개인적으로 마이크로서비스 아키텍처에서 가장 중요하고 어려운 일이 바로 모니터링이라고 생각한다. 마이크로서비스아키텍처에서 시간이 지날수록 복잡해지는 시스템 구성에서 어떻게 하면 장애 포인트를 빠르게 찾을 수 있는지에 대해서 고민이 필요할 것이다. 


레퍼런스

https://static.googleusercontent.com/media/research.google.com/ko//pubs/archive/36356.pdf 

http://www.baeldung.com/spring-cloud-sleuth-single-application

http://d2.naver.com/helloworld/1194202

https://docs.microsoft.com/ko-kr/azure/architecture/microservices/logging-monitoring

https://docs.microsoft.com/ko-kr/azure/architecture/best-practices/monitoring

http://microservices.io/patterns/observability/distributed-tracing.html

http://www.baeldung.com/tracing-services-with-zipkin

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari