SpringBoot Application의 monitoring 시스템 구축하기

system
Spring Boot를 사용하고 있는 애플리케이션에서 이전에 살펴본 Micrometer를 이용해서 metric을 생성하고 Prometheus를 이용해 수집, 그리고 Grafana로 시각화하는 시스템을 만들어보자.

Prometheus는 metric을 수집하고 모니터링 및 알람에 사용되는 오픈소스 애플리케이션이다. time series database를 사용해 metric을 저장하고 flexible한 query를 사용하 metric을 조회할 수 있다.

Grafana는 데이터 시각화, 모니터링 및 분석을 위한 오픈소스 플랫폼이다. 사용자는 Grafana에서 패널(panel)을 사용해 설정된 기간 동안 특정 metric을 나타내는 dashboard를 만들 수 있다.
Grafana는 그래프, 테이블 같은 것들을 지원할 뿐만 아니라 시각화를 위한 별도의 플러그인을 추가해서 사용할 수도 있다.

Spring Boot Dependency

Spring Boot 2.0 이상부터는 애플리케이션의 metric 측정을 위해서 Micrometer를 제공한다. Micrometer는 Spring Boot 2의 Actuator에 포함되어 있기 때문에 spring-boot-starter-actuator를 dependency에 추가해주면 쉽게 사용할 수 있다.

추가적으로 micrometer-registry-prometheus dependency가 필요하다. 이 dependency는 Micrometer가 만들어내는 metric을 Prometheus 서버에서 사용할 수 있는 metric format으로 변경한다.

1
2
3
4
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}

Actuator Endpoint 설정

Actuator는 Spring MVC 혹은 Spring WebFlux를 사용하는 경우, Micrometer를 통해 생성된 애플리케이션의 metric을 Prometheus 서버에서 가져갈(Pull)수 있도록 추가적인 endpoint를 제공해준다.

Spring Boot 2.0 이상부터 사용하는 Actuator는 1.x 버전에서 사용하던 것과는 달리 대부분의 endpoint가 disabled로 설정되어 있다. 기본적으로 /health/info 2가지 endpoint만 default로 사용 가능하다. 따라서 /Prometheus endpoint를 사용할 수 있도록 다음과 같이 application.yml에서 설정이 필요하다.

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: health, info, prometheus

애플리케이션을 실행하고 http://localhost:8080/actuator 를 통해 Actuator가 제공하는 endpoint들을 확인할 수 있다.
endpoint

http://localhost:8080/actuator/prometheus 에서는 Micrometer를 통해 수집된 metric들을 확인할 수 있다.

prometheus_endpoint

Spring Boot 2는 기본적으로 다음과 같은 metric들을 제공하고 있다.

  • JVM, report utilization of:
    • Various memory and buffer pools
    • Statistics related to garbage collection
    • Thread utilization
    • Number of classes loaded/unloaded
  • CPU usage
  • Spring MVC and WebFlux request latencies
  • RestTemplate latencies
  • Cache utilization
  • Datasource utilization, including HikariCP pool metrics
  • RabbitMQ connection factories
  • File descriptor usage
  • Logback: record the number of events logged to Logback at each level
  • Uptime: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
  • Tomcat usage

Prometheus 설치 및 설정

애플리케이션에서의 설정은 끝났으니 애플리케이션에서 생성하는 metric을 수집하기 위한 Prometheus Server를 준비해보자.

테스트를 할 때는 역시나 docker(prometheus image)를 이용하면 간편하다. 만약 실제 로컬 환경 혹은 별도의 서버 환경에서 설치해서 사용하고 싶다면 https://prometheus.io/download/#prometheus 에서 다운받아 설치하자.

Prometheus Server는 기동시 /etc/prometheus/prometheus.yml 설정 파일을 사용한다. docker volume mount를 이용해 Prometheus Server에서 사용할 설정 prometheus.yml 파일을 만들어보자.

1
2
3
4
5
6
7
8
global:
scrape_interval: 10s # 10초 마다 Metric을 Pulling
evaluation_interval: 10s
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus' # Application prometheus endpoint
static_configs:
- targets: ['host.docker.internal:8080'] # Application host:port

docker에서 host.docker.internal은 특별한 DNS name으로 사용되며 docker를 실행하는 host를 가리킨다. 개발용으로만 사용해야 하며, Docker Desktop(Mac) 외부의 환경에서는 동작하지 않는다.

파일 생성을 완료했다면 prom/prometheus 이미지를 이용해 docker로 prometheus를 실행한다.

1
2
3
4
$ pwd
/Users/user/work/prometheus

$ docker run -p 9090:9090 -v /Users/user/work/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus -d prom/prometheus --config.file=/etc/prometheus/prometheus.yml

문제 없이 실행되었다면 http://localhost:9090 에 접속해보자. 다음과 같이 Prometheus main 화면을 볼 수 있다.
prometheus_main.png

docker로 Prometheus를 실행하면서 설정 파일이 잘 적용되었는지도 확인해보자.
prometheus_configuration.png

Status -> Targets 메뉴에서는 Application의 상태를 확인할 수 있다.
prometheus_targets.png

Application의 상태(Status)가 DOWN인 경우에는 Application이 현재 기동중인지, prometheus.yml에서 targets의 값이 제대로 되어있는지 확인이 필요하다.
prometheus_targets_2.png

여기까지 문제가 없다면 아래와 같이 수집된 metric 중 하나를 선택해 값이 잘 나오는지 확인해보자.
prometheus_graph.png

Prometheus에서 수집한 metric을 Grafana로 시각화하기

Prometheus의 웹 페이지에서 쿼리를 실행해 원하는 metric을 그래프로 시각화할 수 있다. 하지만 매번 모니터링을 위해 수동으로 쿼리를 실행하는 것은 비효율적이고 기본적으로 제공하는 대시보드 또한 간단하게 그래프를 볼 수 있는 정도이다.
Prometheus가 제공하는 것만으로는 시각화하는데 한계가 있기 때문에 보통 별도의 시각화 도구를 이용해서 metric들을 모니터링한다.

이번에는 별도의 시각화 도구로 Grafana를 사용해보자. 역시나 docker(grafana/grafana)를 사용한다.

1
$ docker run -d --name=grafana -p 3000:3000 grafana/grafana

실행 후 http://localhost:3000 에 접속해보자. 다음과 같이 Grafana login 화면을 볼 수 있다.
기본 설정된 ID/PW인 admin/admin 으로 로그인할 수 있다.
grafana_login.png

Home Dashboard에서 Add data source를 클릭해 Data Source 추가하자.
grafana_add_datasource.png

Grafana에서 시각화할 데이터로서 Prometheus에 수집되고 있는 metric을 사용할 것이기 때문에 Prometheus를 선택한다.
grafana_add_datasource_2.png

Name과 URL(Prometheus Server)을 설정하고 Save & Test를 클릭한다.
grafana_add_datasource_3.png

Prometheus가 Data Source로 추가되었다.
grafana_add_datasource_4.png

다음으로는 Data Source를 이용해 Dashboard를 생성해보자.
grafana_new_dashboard.png

그래프를 이용해볼 것이기 때문에 Choose Visualization을 클릭한다.
grafana_new_dashboard_2.png

Metrics로 이전에 Prometheus에서도 확인해보았던 jvm_memoruy_used_bytes를 선택한다.
grafana_new_dashboard_3.png

작업한 Dashboard를 저장한다.
grafana_new_dashboard_4.png

추가된 Dashboard를 확인할 수 있다.
grafana_new_dashboard_5.png

Spring Boot Application에서 생성하는 metric을 Prometheus를 통해 수집하고, Grafana로 시각화하는 것까지 마무리했다.
실제로는 애플리케이션에서 기본적으로 제공하는 Metric 뿐만 아니라 Micrometer를 이용해 직접 필요한 Metric을 추가할 수도 있다.
또한 Grafana에는 소개하지 않은 더 많은 유용한 기능들이 있다. 필요한 기능은 문서를 통해 찾아가며 사용해보자.

Micrometer

Micrometer란?

micrometer.io에서는 Micrometer에 대해서 다음과 같이 소개하고 있다.

Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems, allowing you to instrument your JVM-based application code without vendor lock-in. Think SLF4J, but for metrics

Micrometer는 JVM 기반의 애플리케이션에서 다양한 모니터링 도구가 제공하는 클라이언트 라이브러리에 대한 facade를 제공한다. 로깅 관련된 시스템에서는 SLF4J가 있다면 모니터링(metric) 시스템에서는 Micrometer가 있는 것이다.

즉, 모니터링 시스템을 만드는 vendor들은 Micrometer 인터페이스를 따르기 때문에 Micrometer를 사용하면 애플리케이션 내의 코드 상에서는 모니터링 시스템 클라이언트로 어떤 것을 사용할지에 대한 고민에서 벗어나 Micrometer를 이용해 애플리케이션 metric을 수집하기만 하면 된다.
(모니터링 시스템을 선택하는 것은 런타임 시점에 정해진다고 생각하면 된다.)

예를 들어, 모니터링 시스템으로 Prometheus를 사용한다고 하면 아래와 같이 표현할 수 있다.
micrometer

Micrometer는 다음과 같은 모니터링 시스템을 지원한다.

  • AppOptics, Azure Monitor, Netflix Atlas, CloudWatch, Datadog, Dynatrace, Elastic, Ganglia, Graphite, Humio, Influx/Telegraf, JMX, KairosDB, New Relic, Prometheus, SignalFx, Google Stackdriver, StatsD, Wavefront

Micrometer에 의해서 기록된 애플리케이션의 metric 정보는 시스템의 이상 유뮤룰 판단하기 위한 모니터링(알람) 용도로 사용된다.

Supported monitoring systems

Micrometer는 코어 모듈과 측정 SPI(Service Provider Interface)를 포함하고 있다. (각각 Registry라고 부르는 다양한 모니터링 시스템에 대한 구현을 포함하고 있다.)
모니터링 시스템에는 중요한 3가지 특징이 있다.

Dimensionality

Dimensionality는 수집하는 metric 이름에 tag(key-value)를 붙일 수 있도록 지원하는 것을 말한다. 반면 일부 모니터링 시스템의 경우에는 tag 형태가 아닌, flat한 metric name만 사용이 가능하다. 이를 hierarchical system이라고 한다.
Micrometer는 hierarchical system에 metric을 보낼 때는 tag를 metric name에 추가한다.

  • Dimensional: AppOptics, Atlas, Azure Monitor, Cloudwatch, Datadog, Datadog StatsD, Dynatrace, Elastic, Humio, Influx, KairosDB, New Relic, Prometheus, SignalFx, Sysdig StatsD, Telegraf StatsD, Wavefront
  • Hierarchical: Graphite, Ganglia, JMX, Etsy StatsD

Rate aggregation

모니터링 시스템 사용자들은 일정 시간 간격 동안의 특정 지표에 대한 평균값을 필요로 하는 경우가 많다. 어떤 모니터링 시스템은 애플리케이션에서 평균값을 직접 구해서 보내주기를 기대한다. 반면에 어떤 모니터링 시스템은 애플리케이션이 누적된 값을 보내주기를 기대하며, 모니터링 시스템이 직접 평균값을 구하는 경우도 있다.

  • Client-side: AppOptics, Atlas, Azure Monitor, Datadog, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, all StatsD flavors, SignalFx
  • Server-side: Prometheus, Wavefront

Publishing

일부 모니터링 시스템은 metric 정보를 애플리케이션으로부터 polling한다. 반면 일부 모니터링 시스템은 애플리케이션이 일정한 간격으로 metric 정보를 push하는 방식으로 사용된다.

  • Client pushes: AppOptics, Atlas, Azure Monitor, Datadog, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, SignalFx, Wavefront
  • Server polls: Prometheus, all StatsD flavors

Micrometer 어떤 Registry를 사용하는지에 따라, 위와 같은 요구사항을 충족하도록 metric 정보를 커스터마이징한다.

Registry

Meter는 애플리케이션의 metric을 수집하기 위한 인터페이스이다. Meter는 MeterRegistry에 의해 생성되어 등록된다. 지원되는 각 모니터링 시스템은 MeterRegistry 구현체를 갖고 있다.

SimpleMeterRegistry는 각 meter의 최신 값을 메모리에 저장한다. 그리고 metric 정보를 다른 시스템으로 내보내지 않는다. 따라서 만약 어떤 모니터링 시스템을 사용할지 결정하지 못했다면 SimpleMegerRegistry를 사용하면 된다.

1
MeterRegistry registry = new SimpleMeterRegistry();

Composite registries

Micrometer는 여러 Registry를 추가할 수 있는 CompositeMeterRegistry를 제공한다. 따라서 CompositeMeterRegistry를 사용하면 둘 이상의 모니터링 시스템에서 동시에 metric을 사용할 수 있다.

1
2
3
4
5
6
CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry();
AtlasMeterRegistry atlasMeterRegistry = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);

compositeRegistry.add(oneSimpleMeter);
compositeRegistry.add(atlasMeterRegistry);

Global registry

Micrometer는 static 변수로 Global MeterRegistry를 제공한다. Metrics.globalRegistry를 통해 static 변수에 접근할 수 있으며 Metrics 클래스에는 글로벌 MeterRegistry를 기반으로 Meter를 생성하는 정적 빌더 메소드가 있다.
Global MeterRegistry는 CompositeMeterRegistry 객체이다.

Meters

Micrometer는 Meter의 구현체로 다음의 것들을 지원한다. Meter의 type 별로 수집되는 metric 수가 다르다.

  • Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer, TimeGauge

Meter는 이름(name)태그(tag)로 고유하게 식별된다.

Naming

Micrometer는 소문자 단어를 ‘.’으로 구분하는 naming 규칙을 사용한다. 각각의 모니터링 시스템은 naming 규칙과 관련해 권장 사항을 갖고 있으며 일부 모니터링 시스템은 서로의 naming 규칙이 달라 호환되지 않을 수도 있다.

따라서 모니터링 시스템의 각 Micrometer 구현체는 소문자 단어와 ‘.’으로 구분된 이름을 각자의 모니터링 시스템 naming 규칙으로 변환하는 기능을 제공한다.

예를 들면 아래의 timer meter는 각각의 모니터링에서 다음과 같이 변경된다.

1
registry.timer("http.server.requests");

  • Prometheus: http_server_requests_duration_seconds
  • Atlas: httpServerRequests
  • Graphite: http.server.requests
  • InfluxDB: http_server_requests

그러므로 Micrometer의 소문자 단어를 ‘.’으로 구분하는 naming 규칙을 사용하면 모니터링 시스템 종류에 상관없이 metric name에 대해 이식성을 보장할 수 있다.

Counter

Counter는 애플리케이션에서 특정 속성에 대한 카운트를 기록한다. Build method 혹은 MetricRegistry의 helper method를 통해 custom counter를 생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Counter counter = Counter
.builder("instance")
.description("indicates instance count of the object")
.tags("dev", "performance")
.register(registry);

counter.increment(2.0);

assertTrue(counter.count() == 2);

counter.increment(-1); // 카운트는 증가만 가능하다.

assertTrue(counter.count() == 2);

Timers

시스템의 latency(지연 시간) 혹은 이벤트 빈도를 측정하기 위해서 Timers를 사용할 수 있다. Timer는 이벤트가 발생한 수와 총 시간을 기록한다.

1
2
3
4
5
6
7
8
9
10
11
12
SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException ignored) { }
});

timer.record(3000, MILLISECONDS);

assertTrue(2 == timer.count());
assertTrue(4510 > timer.totalTime(MILLISECONDS) && 4500 <= timer.totalTime(MILLISECONDS));

Gauge

Gauge는 Meter의 현재 값을 보여준다. 다른 Meter와 다르게 Guage는 데이터의 변경이 관찰 된 경우에만 데이터를 기록한다. 캐시 등의 통계를 모니터링 할 때 유용하다.

1
2
3
4
5
6
7
8
9
10
11
12
SimpleMeterRegistry registry = new SimpleMeterRegistry();
List<String> list = new ArrayList<>(4);

Gauge gauge = Gauge
.builder("cache.size", list, List::size)
.register(registry);

assertTrue(gauge.value() == 0.0);

list.add("1");

assertTrue(gauge.value() == 1.0);

Binders

Micrometer에는 JVM, 캐시, ExecutorService 및 로깅 서비스를 모니터링하기 위한 여러 내장 Binder가 있다.

  • JVM 및 시스템 모니터링: ClassLoaderMetrics
  • JVM memory pool: JvmMemoryMetrics
  • GC metrics: JvmGcMetrics
  • Thread 및 CPU 사용률: JvmThreadMetrics, ProcessorMetrics
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×