티스토리 뷰

서비스 개발 및 장기적인 운영에 있어 애플리케이션을 모니터링하는 것은 중요한 작업이다. 애플리케이션 모니터링이란 애플리케이션에서 발생하는 동작들에 대한 메트릭을 수집하여 애플리케이션 성능을 분석하는 분야를 말한다. 모니터링을 통해 서비스 개발 과정에서는 동작을 확인할 수 있고, 서비스 오픈 직전에는 성능 테스트를 할 수 있고, 서비스 운영 과정에서는 문제를 해결할 수 있다.

프로메테우스, 그라파나를 통해 스프링 부트 애플리케이션의 메트릭을 수집하고 이를 시각화하는 환경을 구성해보고자 한다.

개요

Metric

메트릭(metric)이란 측정 가능한 양이나 특성을 나타내는 척도 또는 지표를 말한다. 데이터 분석이나 평가, 성능 측정 등 다양한 분야에서 사용된다. 메트릭을 잘 수집하면 시스템의 현재 상태를 손쉽게 파악할 수 있다. 메트릭은 주어진 목표나 문제에 따라 다양한 형태로 정의될 수 있다.

  • 호스트 단위 메트릭 : CPU, 메모리, 디스크 I/O 등
  • 종합 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능 등
  • 핵심 비즈니스 메트릭 : 일별 사용자, 수익, 재방문 등

Prometheus

프로메테우스(Prometheus)는 오픈 소스 모니터링 도구로 메트릭 데이터를 수집하여 데이터베이스에 저장하고, 이를 사용하여 애플리케이션의 상태를 모니터링하고 분석한다. 애플리케이션에서는 메트릭 데이터를 프로메테우스로 노출시켜줘야 하는데, 스프링 부트에서 프로메테우스를 사용하기 위해서는 스프링 부트 액추에이터(Spring Boot Actuator)를 사용하여 메트릭 수집을 위한 엔드포인트를 노출시킨다.

 

모니터링 시스템에는 Pull/Push 2가지 방식이 존재한다.

  • Push : 메트릭이 발생하는 곳에서 메트릭을 수집하는 곳으로 보낸다.
  • Pull : 메트릭을 수집하는 곳에서 주기적으로 데이터를 수집해 간다.

Prometheus는 직접 주기적으로 메트릭을 Pull 해오는 방식으로 동작한다.

Actuator

액추에이터(Actuator)는 상태, 메트릭, 환경 등 실행 중인 애플리케이션에 대한 운영 정보를 노출하는 데 사용된다. 해당 모듈을 추가함으로 해당 기능을 직접 구현하지 않아도 사용할 수 있으며 다양한 설정을 편리하게 할 수 있게 도와준다. 엔드포인트를 통해 메트릭을 노출시키는데 기본적으로 제공하는 엔드포인트 외에도 사용자가 직접 정의한 엔드포인트도 사용할 수 있다. 제공하는 엔드포인트는 해당 링크를 통해 확인할 수 있다.

Grafana

그라파나(Grafana)는 오픈 소스 시각화 및 대시보드 도구로 다양한 데이터 소스와 통합되어 다양한 유형의 데이터를 시각화하고 사용자가 쉽게 대시보드를 구성할 수 있는 기능을 제공한다. 가장 일반적으로 사용되는 데이터 소스가 프로메테우스이다. 그 외에도 InfluxDB, ElasticSearch, MySQL, PostgreSQL, AWS CloudWatch 등 다양한 데이터 소스와 통합할 수 있다. 이러한 데이터 소스로부터 그라파나는 데이터를 쿼리하고, 그래프, 차트, 테이블 등 다양한 시각화 요소를 사용하여 데이터를 시각적으로 표현한다.

구성

메트릭 설정

스프링 부트 애플리케이션, MySQL 데이터베이스, 프로메테우스, 그라파나를 도커를 이용해 실행하기에 도커 환경이 구성되어 있는 상태임을 가정한다.

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

스프링 부트 애플리케이션에 웹 의존성, 메트릭 노출을 위한 엑추에이터와 메트릭 수집을 위한 Micrometer 의존성을 추가한다.

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8080/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:8080/actuator/health/{*path}",
      "templated": true
    }
  }
}

애플리케이션 실행 후 http://localhost:8080/actuator에 접속하면 현재 액추에이터가 제공하는 엔드포인트 목록을 확인할 수 있다. 필요로 하는 메트릭을 제공하는 엔드포인트에 접속함으로 원하는 메트릭 데이터를 확인할 수 있다. 메트릭 수집 도구인 프로메테우스에 메트릭을 제공하기 위한 엔드포인트도 만들 수 있다.

//application.yml

management:
  endpoints:
    web:
      exposure:
        include: prometheus, health, info
  metrics:
    tags:
      application: ${spring.application.name}

메트릭 관련 설정을 위해 application.yml 파일에 위와 같은 코드를 추가하였다. 각 설정의 동작은 아래와 같다.

  • management.endpoints.web.exposure.include : 외부에 노출할 엔드포인트를 지정한다. health, info와 같이 기본으로 제공하는 엔드포인트 외에도 prometheus 엔드포인트를 지정하여 프로메테우스에서 해당 엔드포인트를 통해 메트릭을 수집하도록 한다. exclude를 통해서는 외부에 노출시키고 싶지 않은 엔드포인트를 지정할 수 있다.
  • management.metrics.tags.application : 메트릭 데이터에 태그를 추가하는 역할을 한다. 여기서는 application이라는 태그를 추가하고 해당 태그의 값으로 application.yml 파일에 정의되어 있는 spring.application.name 값을 사용하도록 하였다. 태그 추가를 통해 메트릭 데이터를 구체적으로 분석하고 필터링할 수 있다.

설정 후 노출된 엔드포인트인 http://localhost:8080/actuator/prometheus에 접속하면 커넥션, 세션, 스레드 등과 같은 애플리케이션 메트릭 정보를 확인할 수 있다. 설정한 태그도 올바르게 나타나고 있는 것을 확인할 수 있다.

프로메테우스 설정

//prometheus.yaml

global:
  scrape_interval: 15s
  scrape_timeout: 15s
  evaluation_interval: 2m

  external_labels:
    monitor: 'system-monitor'
  query_log_file: query_log_file.log

rule_files:
  - "rule.yaml"

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets:
          - "prometheus:9090"
  - job_name: "springboot"
    metrics_path: "/actuator/prometheus"
    scheme: 'http'
    scrape_interval: 5s
    static_configs:
      - targets:
          - "app:8080"

prometheus.yml 파일을 통해 프로메테우스에 관한 설정도 할 수 있다. 각 설정의 동작은 아래와 같다.

  • global.scrape_interval : 주기적으로 스크랩하는 간격을 나타낸다. 기본값은 1분이다.
  • global.scrape_timeout : 스크랩 작업의 타임아웃을 나타낸다. 기본값은 10초이다.
  • global.evaluation_interval : 규칙을 검증하는 간격을 나타낸다. 기본값은 1분이다.
  • global.external_labels : 수집된 모든 메트릭에 추가될 전역 레이블을 정의한다. monitor이라는 레이블을 추가하고 그 값을 system-monitor로 지정한다.
  • global.query_log_file : 쿼리 로그를 저장할 파일 경로를 지정한다.
  • rule_files : 적용할 규칙 파일의 경로를 지정한다.
  • scrape_configs.job_name : 스크랩할 작업의 이름을 지정한다.
  • scrape_configs.static_configs.targets : 스크랩할 정적인 타깃을 정의한다. prometheus:9090은 프로메테우스가 동작할 경로이기에 자신을 스크랩하게 된다.
  • scrape_configs.metrics_path : 애플리케이션의 메트릭을 얻기 위한 엔드포인트 경로를 지정한다.
  • scrape_configs.scheme : 스크랩할 대상의 프로토콜을 지정한다.
  • scrape_configs.scrape_interval : 스크랩 간격을 지정한다.

해당 설정을 통해 프로메테우스는 prometheus, application의 메트릭을 스크랩하는 작업을 수행하게 된다.

//rule.yaml

groups:
  - name: system-monitor
    rules:
      - alert: InstanceDown
        expr: up == 0
        for: 5m
        labels:
          severity: page
        annotations:
          summary: "Instance {{ $labels.instance }} down"
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes."

      - alert: APIHighRequestLatency
        expr: api_http_request_latencies_second{quantile="0.5"} > 1
        for: 10m
        annotations:
          summary: "High request latency on {{ $labels.instance }}"
          description: "{{ $labels.instance }} has a median request latency above 1s (current value: {{ $value }}s)"

프로메테우스에서는 메트릭 스크랩 설정 외에도 알람에 대한 설정도 가능하다. 프로메테우스에서 알람은 시스템 또는 서비스의 이상 상태를 감지하고 이에 대한 경고 또는 통지를 생성하는 기능이다. 알람은 메트릭 데이터를 기반으로 정의되며 특정 조건이 충족될 때 발생한다. 각 설정의 동작은 아래와 같다.

  • groups.name : 해당 그룹의 이름을 지정한다.
  • groups.rules : 그룹에 속하는 알람 규칙들을 정의한다.
  • groups.rules.alert : 알람의 이름을 지정한다.
  • groups.rules.expr : 알람이 발생하는 표현식을 정의한다. up == 0은 메트릭이 0인 경우를 평가하는 표현식이다.
  • groups.rules.for : 조건이 유지되어야 하는 시간을 지정한다.
  • groups.rules.labels : 알람에 대한 추가 레이블을 지정한다.
  • groups.rules.annotations : 알람에 대한 추가 설명을 제공하는 어노테이션을 지정한다.

프로메테우스 컨테이너 실행 시 볼륨을 통해 해당 설정들이 적용될 수 있도록 한다.

컨테이너 실행

//docker-compose.yml

version: "3"
services:
  app:
    image: arm64v8/amazoncorretto:17-alpine-jdk
    container_name: app
    volumes:
      - ./src:/app/src
      - ./gradle:/app/gradle
      - ./build.gradle:/app/build.gradle
      - ./gradlew:/app/gradlew
      - ./gradlew.bat:/app/gradlew.bat
      - ./settings.gradle:/app/settings.gradle
    working_dir: /app
    command: [ "./gradlew", "bootrun" ]
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db/monitoring
      SPRING_DATASOURCE_USERNAME: "root"
      SPRING_DATASOURCE_PASSWORD: "1234"
    ports:
      - "8080:8080"
    restart: always
    depends_on:
      - db

  db:
    image: arm64v8/mysql
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: "1234"
      MYSQL_DATABASE: "monitoring"
    ports:
      - "3307:3306"
    restart: always

  prometheus:
    image: prom/prometheus:v2.37.6
    container_name: prometheus
    volumes:
      - ./prometheus/config:/etc/prometheus
      - ./prometheus/volume:/prometheus
    ports:
      - "9090:9090"
    command:
      - '--web.enable-lifecycle'
      - '--config.file=/etc/prometheus/prometheus.yaml'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
    restart: always

  grafana:
    image: grafana/grafana:9.4.7
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/volume:/var/lib/grafana
    restart: always

application, mysql, prometheus, grafana 컨테이너를 하나의 네트워크로 동작시키기 위한 도커 컴포즈 파일은 위와 같다. 도커 컴포즈 파일을 실행시킴으로 모니터링에 필요한 여러 애플리케이션을 실행시킬 수 있다.

프로메테우스 컨테이너

컨테이너를 정상적으로 띄웠다면 http://localhost:9090에 접속하면 볼 수 있는 화면은 위와 같다. 프로메테우스 대시보드 화면으로 검색바에 메트릭 정보를 입력해서 조회할 수 있다.

jvm_threads_live_threads 메트릭 정보를 확인하기 위해 검색해 보았더니 정상적으로 데이터를 가져오고 있다는 것을 확인할 수 있다. 애플리케이션에 요청 상황이 발생하지 않기 때문에 데이터가 변하지 않고 일정하게 유지되고 있다. 프로메테우스에서도 메트릭 데이터를 그래프로 시각화할 수 있지만 그라파나를 통해 더욱 효과적으로 시각화할 수 있다.

그라파나 컨테이너

http://localhost:3000에서 동작하고 있는 그라파나에 접속하면 위와 같은 화면을 볼 수 있다. 초기 아이디와 비밀번호는 admin이기에 입력하여 접속하면 아래와 같은 화면이 나타난다.

그라파나에서 메트릭 데이터를 시각화하기 위해서는 2가지 설정이 필요하다.

[ 데이터 소스 추가 ]

URL을 보면 host.docker.internal이라는 호스트명을 사용하고 있다. host.docker.internal은 컨테이너 내부에서 호스트 머신의 IP 주소를 가리키는 특별한 호스트명이다. 컨테이너 내부에서 host.docker.internal을 사용하면 호스트 머신의 IP 주소로 해석된다.

 

현재 상황을 바탕으로 조금 더 자세히 알아보자. 도커 컨테이너를 실행하고 있는 컴퓨터 입장에서는 프로메테우스, 그라파나가 각각 localhost:9090, localhost:3000에서 실행되고 있다. 하지만 그라파나 컨테이너 입장에서는 localhost가 컨테이너 내부가 되기 때문에 localhost:9090으로 프로메테우스 컨테이너에 접근할 수 없다. 따라서 컨테이너 내부에서 호스트에 접근하기 위해 host.docker.internal이라는 옵션을 사용하는 것이다.

 

추가적으로 http://prometheus:9090와 같이 프로메테우스 컨테이너의 이름을 통해서도 컨테이너 내부에서 외부 컨테이너에 접근이 가능하다. 도커는 컨테이너 간 가상 네트워크를 생성하고 각 컨테이너에 고유한 IP 주소를 할당하게 된다. 이렇게 연결된 컨테이너들은 서로의 IP 주소를 알 수 있기에 해당 IP 주소를 사용하여 통신할 수 있다. 컨테이너 이름을 통한 통신이 가능한 이유는 도커는 내부 DNS 기능을 제공하기에 컨테이너 이름을 IP 주소로 해석할 수 있다.

[ 대시보드 생성 ]

그라파나 대시보드를 직접 만들어서 커스텀하는 방법도 있지만 잘 만들어져 있는 그라파나 서비스를 이용하기 위해 대시보드를 Import 하는 방법을 선택했다. 4701은 스프링 부트 메트릭을 보여주는 유명한 대시보드의 ID를 뜻한다. 대시보드를 통해 I/O, JVM Memory, CPU, GC, Thread 등의 메트릭 데이터를 시각화해서 볼 수 있다.

JMeter

현재 애플리케이션에 아무런 요청이 발생하지 않기 때문에 대시보드에 특별한 변화가 발생하지 않는다. 애플리케이션에 트래픽을 발생시켜 대시보드의 변화를 확인하기 위해 부하 테스트 도구를 사용할 수 있다. 웹 애플리케이션 서비스의 성능을 분석하고 측정하기 위한 부하 테스트 도구인 JMeter를 사용해 트래픽을 발생시켜 보았다.

# jmeter install
brew install jmeter

# jmeter execute
open /usr/local/bin/jmeter

Mac 사용자 기준으로 터미널의 위의 명령어를 입력하여 JMeter 설치 및 실행이 가능하다.

정상적으로 실행이 되었다면 위의 사진과 같이 JMeter 애플리케이션이 동작한다. HTTP 요청을 보내기 위해 몇 가지 플러그인 설치가 필요하다.

Options -> Plugins Manager에 들어가서 3 Basic Graphs, Custom Thread Groups를 설치한다.

부하를 발생시키기 위한 Thread Group을 생성하고 HTTP 요청을 위한 HTTP Request와 부하에 따른 결과 확인을 위한 View Result Tree, Summary Report, Transactional per Second를 Thread Group에 추가한다.

Thread Group에 발생시킬 부하에 대한 설정을 한다. 각 설정에 대한 설명은 아래와 같다.

  • Number of Threads : 총 몇 개의 스레드를 생성할 것인지 설정한다.
  • Ramp-up period : 스레드 수를 점진적으로 증가시키는 시간을 의미한다. 1초로 지정한 경우 1초 동안 점진적으로 스레드를 생성하여 트래픽을 발생시키게 된다.
  • Loop Count : 스레드 그룹의 반복 실행 횟수를 의미한다. 10으로 설정한 경우 전체 스레드가 설정된 동작을 10번 반복하게 된다. Infinite를 체크하면 설정된 동작을 멈출 때까지 끊임없이 반복한다.

HTTP Request 화면에서 요청을 보낼 서버의 정보를 입력한다. 위의 설정은 http://localhost:8080/health-check경로에 GET 메서드를 사용하여 HTTP 요청을 보내게 되는 것이다. 설정을 마치고 JMeter를 동작시키면 설정한 동작대로 요청이 진행된다.

서버의 로그를 통해 요청이 끊임없이 들어오는 것을 확인할 수 있다.

요청이 발생했을 때 그라파나 대시보드에서도 변화를 확인할 수 있다. I/O, CPU 사용량, Thread 상태 등이 실시간으로 변화는 것을 직접 확인할 수 있다.

참고

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday