WEB/Spring Boot

마이크로미터, 프로메테우스, 그라파나

Tony Lim 2023. 5. 18. 19:33
728x90

마이크로미터 소개

앱의 데이터를 측정한 데이터를 API 스펙에 맞게 넘겨줘야할 때 툴을 바꾸게 되면 호환이 맞지 않아서 문제가 된다.

마이크로미터를 통해 하나의 표준방식으로 여러 모니터링툴 구현체를 사용할 수 있게 도와준다. 마이크로미터가 지원하는 툴을 사용해야하긴 하지만 지원하는것들이 엄청 많다.


메트릭 확인하기

미이크로미터에 이미 다양한 지표 수집기능이 만들어져있고 스프링에서는 @AutoConfiguration에서 해당 기능들을 사용할 수 있도록 빈으로 등록한다.

actuator/metrics 를 확인하면 jvm.memory.used 처럼 여러 메트릭들이 이미 존재한다.

actuator/metrics/jvm.memory.used?tag=area:heap
처럼 tag를 이용하여 한번더 필터링을 할 수 있다.

uri중에 /log에 대한 정보중에 200만 확인하고싶으면

http.server.request?tag=uri:/log&tag=status:200 와 같이 tag 를 키 값으로 하고 해당되는 value를 추가적으로 적어주면 필터링 된다.

 


프로메테우스와 그라파나 

프로메테우스 = 앱에서 발생한 메트릭을 그 순간만 확인하는 것이 아니라 과거 이력까지 함께 확인하려면 메트릭을 보관하는 DB역할을 한다.

그라파나 = 프로메테우스를 포함한 다양한 데이터소스를 지원하는 대시보드 툴이다.

  1. 스프링 부트 엑츄에이터와 마이크로미터를 사용하면 수 많은 메트릭들이 자동으로 생성이 된다.
  2. 프로메테우스에서 주기적으로 메트릭 데이터를 수집하고 db에 저장을 한다.
  3. 그라파나도 주기적으로 프로메테우스 db에서 데이터를 수집해서 대시보드에 뿌려준다.

 

 

앱단에서 build.gradle에 다음 dependency를 추가해주면

implementation 'io.micrometer:micrometer-registry-prometheus'

/actuator/prometheus로 엑츄에이터에 프로메테우스 메트릭 엔드포인트가 자동으로 추가된다.

http.server.request 의 actuatordml metric 은 json format으로 요청수 , 시간 합, 최대 시간 정보를 가지고 있다. 이것을 프로메테우스에서는

  • http_server_request_seconds_count
  • http_server_request_seconds_sum
  • http_server_request_seconds_max

이런식으로 포맷들이 변경된다.

 scrape_configs:
   - job_name: "prometheus"
     
     static_configs:
       - targets: ["192.168.31.32:19090"]

   - job_name: "spring-actuator"
     metrics_path: '/actuator/prometheus'
     scrape_interval: 1s
     static_configs:
       - targets: ['192.168.31.32:8080']

docker로 띄우는데 -p 19090:9090 으로 띄웠기 때문에 static_configs에 19090 포트를 적어줘야한다.

docker run --name prometheus -p 19090:9090 -v /home/tony/vscode/boot-source-20230228/start/prometheus-grafana/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

 


프로메테우스 - 게이지와 카운터

Gauge = 게이지는 오르고 내리는 값, 현재 상태값을 출력하게 된다. = CPU 사용량

Counter = 단일 누적값 = HTTP 요청수 (http_server_requests_seconds_count{uri="/log"})

increase(http_server_requests_seconds_count{uri="/log"}[1m]) = 함수로 지정한 시간 단위별로 증가를 확인 할 수 있다.

rate(http_server_requests_seconds_count{uri="/log"}[1m]) = increase 는 숫자이지만 rate는 초당 평균을 나누어 계산한 값인 비율을 y축에 보여주게 된다.

rate는 1m로 하면 (지금 값 - 1분전 값) / 1분 으로 계산하여 1분동안 60개의 요청이 왔으면 값이 1 이된다.
increase는 rate * 1m = 60 으로 실제 분당 요청이 얼마 들어왔는지 나타내게 된다.

계속증가되는값은 특정시간에 얼마나 고객의 요청이 들어왔는지 한 눈에 확인하기 어려우니 increase, rate를 쓴다.


그라파나

grafana를 docker로 띄울시 좀 기다려야한다 

logger=http.server t=2023-05-19T02:08:35.841231555Z level=info msg="HTTP Server Listen" address=[::]:3000 protocol=http subUrl= socket=

요 로그가 나올 때까지

https://grafana.com/grafana/dashboards/?search=spring 

 

Dashboards | Grafana Labs

 

grafana.com

여기서 이미 완성된 dashboard들을 가져다 쓰는것이 좋다.


메트릭 등록

공통적인것들은 이미 모니터링을 잘 할 수 있게 마련되어있다. 필요한것 각 비즈니스의 특화된 부분의 메트릭이 필요하다.

예를 들어서 취소수가 갑자기 급증하거나 재고 수량이 임계치 이상으로 쌓이는 부분들은 기술적인 메트릭으로 확인 할 수 없는 우리 시스템 비즈니스 문제를 빠르게 파악하는데 도움을 준다.

 

등록할 메트릭

주문수, 취소수 (카운터, 계속증가함으로)

  • 상품을 주문하면 주문수가 증가한다.
  • 상품을 취소해도 주문수는 유지한다. 대신에 취소수를 증가시킨다.

재고 수량 (게이지 , 증감이 있음으로)

  • 상품을 주문하면 재고 수량이 감소한다.
  • 상품을 취소하면 재고 수량이 증가한다.
  • 재고 물량이 들어오면 재고 수량이 증가한다.

메트릭 등록1 - 카운터

@RequiredArgsConstructor
public class OrderServiceV1 implements OrderService {

    private final MeterRegistry registry;

    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        System.out.println("주문");
        stock.decrementAndGet();

        Counter counter = Counter.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "order")
                .description("order")
                .register(registry);
        counter.increment();

    }

나만의 비지니스 메트릭을 actuator에 추가할 수 있다.

# HELP my_order_total order
# TYPE my_order_total counter
my_order_total{class="hello.order.V1.OrderServiceV1",method="order",} 1.0
my_order_total{class="hello.order.V1.OrderServiceV1",method="cancel",} 1.0

actuator/prometheus 에서 my_order_total 로 변경된것을 확인할 수 있다. counter이기 때문에 끝에 total이 자동으로 붙는다.

grafana에도 custom한 것을 쉽게 등록할 수 있다.


메트릭 등록2 - @Counted

위에서는 service메소드를 호출해야하만 Counter가 등록되게 되었다. 비지니스 로직에 모니터링 기능이 침투하게 된것이다.

AOP를 사용해서 분리하자

@Configuration
public class OrderConfigV2 {

    @Bean
    OrderService orderService() {
        return new OrderServiceV2();
    }
    
    @Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
        return new CountedAspect(registry);
    }
}
@RequiredArgsConstructor
public class OrderServiceV2 implements OrderService {


    private AtomicInteger stock = new AtomicInteger(100);

    @Counted("my.order")
    @Override
    public void order() {
        System.out.println("주문");
        stock.decrementAndGet();
    }

@Counted 를 사용하려면 CountedAspect를 빈으로 등록을 해줘야 동작하게 된다.


메트릭 등록3 - Timer

카운터와 유사함, 실행시간도 함께 측정해준다.

  • seconds_count = 누적 실행수 == 카운터
  • seconds_sum = 실행시간의 합
  • seconds_max = 최대 실행시간( 가장 오래 걸린 시간 ) 

 

@RequiredArgsConstructor
public class OrderServiceV3 implements OrderService {

    private final MeterRegistry registry;
    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        Timer timer = Timer.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "order")
                .description("order")
                .register(registry);
        
        timer.record(() -> {
            System.out.println("주문");
            stock.decrementAndGet();
        });
    }

특정 로직이 시간이 걸리는지 측정하게 도와준다.

@Timed(value = "my.order")
public class OrderServiceV4 implements OrderService {
    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        System.out.println("주문");
        stock.decrementAndGet();
    }
@Configuration
public class OrderConfigV4 {

    @Bean
    OrderService orderService() {
        return new OrderServiceV2();
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

@Counted와 동일하게 @Timed, TimedAspect를 사용하면 위와 동일하게 동작을 하게 된다.

 


메트릭 등록5 - 게이지

임의로 오르내릴수 있는 단일 숫자 값을 나타내는 메트릭

@PostConstruct
public void init() {
    Gauge.builder("my.stock",orderService, service -> {
        System.out.println("stock gauge call");
        int stock = service.getStock().get();
        return stock;
    }).register(registry);
}

my.stock metric을 확인할 때마다 전달한 람다가 호출된다.

actuator/prometheus 를 프로메테우스가 1초에 한번씩 호출하기 떄문에 1초마다 위 람다가 호출이 되게된다.

@Configuration
public class StockConfigV2 {

    @Bean
    public MeterBinder stockSize(OrderService orderService) {
        return registry -> Gauge.builder("my.stock",orderService, service -> {
            System.out.println("stock gauge call");
            return service.getStock().get();
        }).register(registry);
    }
}

좀 더 간편하게 등록하는 방법

 

 

728x90

'WEB > Spring Boot' 카테고리의 다른 글

액츄에이터  (0) 2023.05.16
외부설정과 프로필  (0) 2023.05.14
자동 구성(Auto Configuration)  (0) 2023.05.13
스프링 부트 스타터와 라이브러리 관리  (0) 2023.05.11
스프링 부트와 내장 톰캣  (0) 2023.05.10