Home 프로메테우스, 그라파나
Post
Cancel

프로메테우스, 그라파나

애플리케이션에서 발생한 메트릭을 그 순간만 확인하는 것이 아닌, 과거 이력까지 함께 확인하려면 메트릭을 보관하는 DB가 필요하다.

이렇게 하려면 어디선가 메트릭을 지속해서 수집하고 DB에 저장해야 한다.

이 역할을 해주는 것이 프로메테우스이다.

그리고 프로메테우스가 DB라고 하면, DB에 있는 데이터를 불러 사용자가 보기 편하도록 보여주는 대시보드가 필요하다.

그라파나는 매우 유연한, 데이터를 그래프로 보여주는 툴이다. 수 많은 그래프를 제공하고 프로메테우스를 포함해 다양한 데이터소스를 지원한다.

프로메테우스와 그라파나에 대해 알아보자.

프로메테우스와 그라파나

  • 스프링 부트 액츄에이터와 마이크로미터를 사용하면 수 많은 메트릭이 자동으로 생성된다.
    • 마이크로미터 프로메테우스 구현체는 프로메테우스가 읽을 수 있는 포맷으로 메트릭을 생성해준다.
  • 프로메테우스는 이렇게 만들어진 메트릭을 지속해서 수집한다.
  • 프로메테우스는 수집한 메트릭을 내부 DB에 저장한다.
  • 사용자는 그라파나 대시보드 툴을 통해 그래프로 편리하게 메트릭을 조회할 수 있다.
    • 이 때 필요한 데이터를 프로메테우스를 통해 조회한다.

프로메테우스 설치

https://prometheus.io/download/

OS에 맞는 압축파일 설치 후 압축 해제.

그리고 Window 기준 prometheus.exe를 실행해주면 된다.

localhost:9090으로 접속하면 실행을 확인해볼 수 있다.

프로메테우스 메트릭 수집

프로메테우스가 애플리케이션의 메트릭을 수집하도록 연동해본다.

  • 애플리케이션 설정: 프로메테우스가 애플리케이션의 메트릭을 가져갈 수 있도록 애플리케이션에서 프로메테우스 포맷에 맞추어 메트릭을 만들어야 한다.
  • 프로메테우스 설정: 프로메테우스가 애플리케이션의 메트릭을 주기적으로 수집하도록 설정해야 한다.

애플리케이션 설정

프로메테우스는 /actuator/metrics 에서 보았던 포맷(JSON)을 이해하지 못한다.

하지만 마이크로미터가 이런 부분을 해결해준다.

각각의 메트릭들은 내부에서 마이크로미터 표준 방식으로 측정되고 있어 어떤 구현체를 사용할지 지정해주기만 하면 된다고 했다.

  • build.gradle
1
implementation 'io.micrometer:micrometer-registry-prometheus'

위 설정을 추가해주면 스프링 부트와 액츄에이터가 자동으로 마이크로미터 프로메테우스 구현체를 등록해 동작하도록 해준다.

액츄에이터에 프로메테우스 메트릭 수집 엔드포인트가 자동으로 추가된다.

http://localhost:8080/actuator/prometheus 를 실행해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# HELP tomcat_threads_config_max_threads
# TYPE tomcat_threads_config_max_threads gauge
tomcat_threads_config_max_threads{name="http-nio-8080",} 200.0
# HELP tomcat_sessions_alive_max_seconds
# TYPE tomcat_sessions_alive_max_seconds gauge
tomcat_sessions_alive_max_seconds 0.0
# HELP tomcat_cache_access_total
# TYPE tomcat_cache_access_total counter
tomcat_cache_access_total 0.0
# HELP jvm_info JVM version info
# TYPE jvm_info gauge
jvm_info{runtime="OpenJDK Runtime Environment",vendor="JetBrains
s.r.o.",version="17.0.3+7-b469.37",} 1.0
# HELP logback_events_total Number of events that made it to the logs
# TYPE logback_events_total counter
logback_events_total{level="warn",} 0.0
logback_events_total{level="debug",} 0.0
logback_events_total{level="error",} 2.0
logback_events_total{level="trace",} 0.0
logback_events_total{level="info",} 47.0
...

그간 보았던 메트릭이 프로메테우스 포맷으로 만들어진 것을 확인할 수 있다.

  • 차이점
    • jvm.info -> jvm_info: 프로메테우스는 . 대신 _ 포맷을 사용한다.
    • logback.events -> logback_events_total: 로그 수 처럼 지속해 숫자가 증가하는 메트릭을 카운터라고 하는데, 프로메테우스는 카운터 메트릭의 마지막에 _total을 붙인다.
    • http.server.requests -> 내부에 요청 수, 시간 합, 최대 시간 정보를 가지고 있었다.
      • http_server_requests_seconds_count: 요청 수
      • http_server_requests_seconds_sum: 시간 합
      • http_server_requests_seconds_max: 최대 시간

이런 방식으로 포맷들이 변경되는 것을 알 수 있다.

프로메테우스 설정

프로메테우스가 애플리케이션의 /actuator/prometheus 를 호출해 메트릭을 주기적으로 수집하도록 설정해야 한다.

프로메테우스 폴더에 있는 prometheus.yml 파일을 수정한다.

1
2
3
4
5
6
7
8
9
10
11
rule_files:
scrape_configs:
 - job_name: "prometheus"
 static_configs:
 - targets: ["localhost:9090"]
 #추가
 - job_name: "spring-actuator"
   metrics_path: '/actuator/prometheus'
   scrape_interval: 1s
   static_configs:
	 - targets: ['localhost:8080']

추가 주석 아래에 있는 부분을 추가해주면 된다. 띄어쓰기에 주의해야 한다.

  • job_name: 자유
  • metrics_path: 메트릭 경로
  • scrape_interval: 1s (보통 10초, 15초, 1분 정도로 설정함)
  • targets: 타겟 서버의 주소

위와 같이 설정했을 때 localhost:8080/actuator/prometheus 를 1초에 한 번씩 호출해 애플리케이션의 메트릭들을 수집한다.

수집 주기의 기본 값은 1분이고 수집 주기가 너무 짧으면 애플리케이션 성능에 영향을 줄 수 있어 운영에서는 10초 ~ 1분을 권장한다.

설정 완료 후 프로메테우스 서버를 종료하고 다시 실행해보자.

프로메테우스 연동 확인

  • 프로메테우스 메뉴 -> Status -> Configuration
    • prometheus.yml에 입력한 부분이 추가되어 있는지 체크.
  • 프로메테우스 메뉴 -> Status -> Targets
    • 연동이 잘 되었는지 확인

연동이 잘 되었다면 위와 같은 화면을 볼 수 있다.

  • prometheus: 프로메테우스 자체에서 제공하는 메트릭 정보
  • spring-actuator: 연동한 애플리케이션의 메트릭 정보
  • State가 UP으로 되어있는 경우 정상이고, DOWN이라면 정상적으로 연동이 되지 않은 것이다.

프로메테우스를 통한 데이터 조회

이렇게 검색창에 원하는 정보의 검색어를 입력하면 수집한 메트릭을 조회할 수 있다.

프로메테우스의 기본 기능

  • 태그, 레이블: error, exception, instance, job, method, outcome, status, uri가 있는 것을 볼 수 있다.
    • 마이크로미터에서는 이것을 태그라고 부르고, 프로메테우스에서는 레이블이라고 부른다.
    • 각각의 메트릭 정보를 구분해 사용하기 위한 정보이다.
  • 숫자: 우측 끝에 354 같은 값들이 보이는데 이 숫자가 메트릭의 값이다.
    • 즉 /actuator/prometheus 는 354번 호출되었다는 것이다. 1초마다 호출되기 때문.

기본 기능

  • Table -> Evaluation time을 수정해 과거 시간 조회 가능
  • Graph -> 메트릭을 그래프로 조회할 수 있다.

필터

레이블을 기준으로 필터를 사용할 수 있다. 필터는 중괄호 문법을 사용한다.

  • 레이블 일치 연산자
    • = : 제공된 문자열과 정확히 동일한 레이블 선택
    • != : 제공된 문자열과 같지 않은 레이블 선택
    • =~ : 제공된 문자열과 정규식 일치하는 레이블 선택
    • !~ : 제공된 문자열과 정규식 일치하지 않는 레이블 선택
1
2
3
4
5
6
7
8
9
10
11
// uri=/log, method=GET 조건 필터
http_server_requests_seconds_count{uri="/log", method="GET"}

// /actuator/prometheus는 제외한 조건으로 필터
http_server_requests_seconds_count{uri!="/actuator/prometheus"} 

// method 가 GET , POST 인 경우를 포함해서 필터
http_server_requests_seconds_count{method=~"GET|POST"}

// /actuator 로 시작하는 uri 는 제외한 조건으로 필터
http_server_requests_seconds_count{uri!~"/actuator.*"}

연산자 쿼리와 함수

+, -, *, /, %, ^ 연산자를 지원한다.

  • sum
    • 값의 합계를 구한다.
    • sum(http_server_requests_seconds_count)
  • sum by
    • SQL group by 기능과 유사하다.
    • sum by(method, status)(http_server_requests_seconds_count)
    • ex) (method=”GET”, status=”200”) = 120
  • count
    • 메트릭 자체의 수 카운트
    • count(http_server_requests_seconds_count)
  • topk
    • 상위 3개 메트릭을 조회한다.
    • topk(3, http_server_requests_seconds_count)
  • 오프셋 수정자
    • http_server_requests_seconds_count offset 10m
    • offset 10m 과 같이 나타낸다. 현재를 기준으로 특정 과거 시점의 데이터를 반환한다.
  • 범위 벡터 선택기
    • http_server_requests_seconds_count[1m]
    • 지난 1분간의 모든 기록값을 선택한다.
    • 범위 벡터 선택기는 차트에 바로 표현할 수 없고 데이터로는 확인할 수 있다.
    • 차트에 표현하기 위해서는 가공이 필요하다.

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

메트릭은 크게 게이지와 카운터 2가지로 분류할 수 있다.

  • 게이지
    • 임의로 오르내릴 수 있는 값
    • ex) CPU 사용량, 메모리 사용량, 사용중인 커넥션
  • 카운터
    • 단순하게 증가하는 단일 누적 값
    • ex) HTTP 요청 수, 로그 발생 수

게이지

게이지는 오르내리는 값이다. 게이지는 현재 상태를 그대로 출력하면 된다.

CPU 사용량을 예시로 보면 CPU 사용량은 현재 상태를 계속 측정하고 그 값을 그대로 그래프에 출력하면 과거부터 지금까지의 CPU 사용량을 알 수 있다.

게이지는 가장 단순하고 사용하기 쉬운 메트릭이다. 크게 고민하지 않고 있는 그대로를 사용하면 된다.

카운터

당연히 그래프로 보면 계속 증가하는 그래프만 보게 된다.

증가만 하는 그래프는 특정 시간에 얼마나 고객의 요청이 들어왔는지 한 눈에 확인하기 매우 어렵다.

이런 문제를 해결하기 위해 increase(), rate() 같은 함수를 지원한다.

increase()

지정한 시간 단위 별로 증가를 확인할 수 있다.

1
increase(http_server_requests_seconds_count{uri="/log"}[1m])

마지막에 범위 벡터를 선택해야 한다.

고객의 요청이 어느정도 증가했는지 한 눈에 파악할 수 있다.

rate()

범위 벡터에서 초당 평균 증가율을 계산한다.

increase()가 숫자를 직접 카운트하는 방법이라면 rate()는 여기에 초당 평균을 나누어 계산한다.

1
rate(http_server_requests_seconds_count{uri="/log"}[1m])

irate()

rate와 유사하지만 범위 벡터에서 초당 순간 증가율을 계산한다.

급격하게 증가한 내용을 확인하기 좋다.

1
irate(http_server_requests_seconds_count{uri="/log"}[1m])

그라파나 설치

https://grafana.com/grafana/download

링크에서 프로메테우스와 마찬가지로 압축 파일을 다운 받고 압축을 해제해준다.

압축을 푼 폴더의 bin 폴더에서 grafana-server.exe를 실행해주면 된다.

이후 localhost:3000으로 접근하면 로그인 페이지가 나타난다.

  • 초기 email or username: admin
  • 초기 password: admin

admin을 입력하고 접근하면 비밀번호를 수정하라고 나오는데, 실제 사용할 때 세팅해주면 된다. 일단 SKIP해도 된다.

그라파나 연동

애플리케이션과 프로메테우스 모두 실행상태여야 한다.

애플리케이션의 메트릭 정보를 프로메테우스가 수집하고 프로메테우스의 정보를 그라파나가 가져와서 써야하기 때문이다.

우선 그라파나에서 프로메테우스를 데이터소스로 사용해 데이터를 읽어오는 부분을 설정해본다.

  • 메뉴 > Connections에 접근해 Prometheus로 들어간다.
  • add를 누르면 아래와 같은 화면이 나타나는데 Connection에 프로메테우스 서버 url을 입력해주면 된다.
  • 그리고 하단의 Save&Test를 누르면 활성화된다.

그라파나 대시보드 만들기

  • DashBoards 메뉴 선택
  • New Dashboard 들어가서 우측 상단의 save dashboard

그리고 다시 DashBoards 메뉴의 메인으로 가면 저장한 DashBoard를 볼 수 있다.

  • DashBoards에 들어가서 Add visualization
  • DataSource는 프로메테우스 선택하면 패널을 편집하는 창이 나타난다.

  • 그리고 하단의 Builder로 되어있는 부분을 Code로 바꾸고 프로메테우스 쿼리를 선택하면 그래프로 나타나게 된다.
  • 다른 쿼리를 같이 추가하고 싶다면 Add query를 하고 Run queries를 해주면 된다.

  • 그리고 쿼리의 Options에서 그래프의 이름을 바꿀 수 있다.
  • Panel Title도 바꿀 수 있다.
  • 최종적으로 우측 상단의 저장을 누르면 아래와 같이 CPU 사용량에 대한 대시보드를 만들어 한 눈에 볼 수 있는 것을 볼 수 있다.

  • 같은 방식으로 대시보드를 만들어 볼 수 있다.

그런데 이렇게 하나하나 대시보드를 입력하는 것도 귀찮은 일이다. 그라파나는 이를 위한 기능도 제공한다.

그라파나 공유 대시보드 활용

https://grafana.com/grafana/dashboards/

위 사이트에 들어가보면 누군가 만들어놓은 수 많은 대시보드가 공개되어 있다.

우리가 필요한 대시보드는 스프링 부트와 마이크로미터를 사용해 만든 대시보드이다. 검색창에 spring이라고 검색하면 다양한 대시보드를 확인할 수 있다.

https://grafana.com/grafana/dashboards/11378-justai-system-monitor/

  • 위 사이트에 접속해 ID를 저장한다.
  • 그라파나의 Dashboards 메뉴에서 New -> Import를 선택한다.
  • 불러올 대시보드 숫자를 입력하고 Load.
  • 프로메테우스 데이터소스를 선택 후 Import 버튼을 선택한다.

불러와진 대시보드를 확인하면 거의 대부분의 메트릭을 대시보드에 표현해놓았다.

각각의 항목들을 참고해 나만의 대시보드를 어떻게 구성해야 하는지 참고할 수도 있다.

일부 데이터의 경우 스프링부트나 기타 라이브러리 버전이 변함에 따라 동작하지 않을 수 있는데, 그 부분들은 수정해 사용해야 한다.

공유 대시보드 수정

우측 상단의 Make editable을 눌러주고 Settings를 선택하면 수정이 가능하다.

현재 대시보드는 톰캣이 아닌 Jetty 웹 서버를 기준으로 통계를 수집하기 때문에 이 부분을 수정해야 한다.

  • 우선 Title을 수정한다. (Jetty Statistics -> Tomcat Statistics)
  • Thread Config Max 패널을 수정한다.
    • 코드 jetty_threads_config_max -> executor_pool_max_threads 로 변경한다.
    • jetty_threads_current -> executor_pool_size_threads
    • jetty_threads_busy -> executor_active_threads
    • jetty_threads_idle -> 제거
    • jetty_threads_jobs -> 제거

추가적으로 https://grafana.com/grafana/dashboards/4701-jvm-micrometer/ 이 대시보드도 추가해서 사용하면 좋다.

유용한 많은 정보를 제공해준다.

그라파나 메트릭을 통한 문제 확인

애플리케이션에 문제를 발생시켜 그라파나를 통해 문제를 어떻게 모니터링 하는지 확인해보자.

실무에서 주로 발생하는 대표적인 예시로 확인해본다.

  • CPU 사용량 초과
  • JVM 메모리 사용량 초과
  • 커넥션 풀 고갈
  • 에러 로그 급증

CPU 사용량 초과

CPU에 간단하게 부하를 주는 코드를 작성해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@RestController
public class TrafficController {
    @GetMapping("/cpu")
    public String cpu() {
        log.info("cpu");
        long value =0;
        for (long i = 0; i < 100000000000000L; i++) {
            value++;
        }
        return "ok value=" + value;
    }
}

컴퓨터 성능에 따라 루프 횟수를 바꾸어 부하를 주어야 한다.

위에서 작성했던 hellodashboard에서 CPU 사용량을 보면 CPU 사용량이 급격하게 증가하는 그래프를 확인해볼 수 있다.

JVM 메모리 사용량 초과

메모리 사용을 누적하는 코드를 추가해본다.

1
2
3
4
5
6
7
8
9
private List<String> list = new ArrayList<>();
@GetMapping("/jvm")
public String jvm() {
    log.info("jvm");
    for (int i = 0; i < 1000000; i++) {
        list.add("hello jvm!" + i);
    }
    return "ok";
}

여러번 요청 후 JVM 메모리 사용량을 체크한다.

이렇게 메모리 사용량이 올라가는 것을 볼 수 있고 OutOfMemory 에러가 나타날 때까지 API를 호출해주면 그래프가 천장을 향해 가는 것을 볼 수 있다.

커넥션 풀 고갈

1
2
3
4
5
6
7
8
9
10
11
@Autowired
DataSource dataSource;
    
@GetMapping("/jdbc")
public String jdbc() throws SQLException {
    log.info("jdbc");
    Connection conn = dataSource.getConnection();
    log.info("connection info={}", conn);
    //conn.close(); //커넥션을 닫지 않는다.
    return "ok";
}

커넥션을 반환하지 않아 커넥션 풀의 최대 숫자를 넘어가면 커넥션을 획득하기 위해 대기하게 된다.

그러면 커넥션 획득 부분에서 쓰레드가 대기하게 되고 결과적으로 HTTP 요청에 응답을 하지 못한다.

10번을 요청하면 Active가 10이 된 것을 볼 수 있다.

다시 요청하면 Pending이 1 늘어난 것을 볼 수 있고 대기하다가 아래 오류를 나타내게 된다.

1
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30016ms.

에러 로그 급증

1
2
3
4
5
@GetMapping("/error-log") 
public String errorLog() { 
	log.error("error log"); 
	return "error"; 
}

에러 로그의 추세를 볼 수 있다. 당연히 급증하면 문제가 발생했다고 볼 수 있는 것이다.

This post is licensed under CC BY 4.0 by the author.

마이크로미터, 메트릭

대용량 트래픽 대응 (선착순 이벤트 응모 시스템 구현기)