애플리케이션 모니터링
애플리케이션에서 발생하는 동작들에 대한 메트릭을 수집하여 애플리케이션 성능을 분석하는 분야로 모니터링을 통해 서비스 개발 과정에서는 동작을 확인할 수 있고, 서비스 오픈 직전에는 성능 테스트를 할 수 있고, 서비스 운영 과정에서는 문제를 해결할 수 있다.
Actuator, Prometheus, Grafana를 사용해 스프링부트 애플리케이션의 메트릭을 수집하고 이를 시작화하는 환경을 구성할 수 있다.
Metric
메트릭(metric)이란 측정 가능한 양이나 특성을 나타내는 척도 또는 지표를 말한다. 데이터 분석이나 평가, 성능 측정 등 다양한 분야에서 사용되며, 메트릭을 잘 수집하면 시스템의 현재 상태를 손쉽게 파악할 수 있다.
메트릭은 주어진 목표나 문제에 따라 다양한 형태로 정의될 수 있다.
- 호스트 단위 메트릭: CPU, 메모리, 디스크 I/O 등
- 종합 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능 등
- 핵심 비즈니스 메트릭: 일별 사용자, 수익, 재방문 등
Prometheus
프로메테우스(Prometheus)는 오픈 소스 모니터링 도구로 메트릭 데이터를 수집하여 데이터베이스에 저장하고, 이를 사용하여 애플리케이션의 상태를 모니터링하고 분석한다.
애플리케이션에서는 메트릭 데이터를 프로메테우스로 노출시켜줘야 하는데, 스프링 부트에서 프로메테우스를 사용하기 위해서는 스프링 부트 액추에이터(Spring boot Actuator)를 사용하여 메트릭 수집을 위한 엔드포인트를 노출시켜야 한다.
모니터링 시스템에는 Pull/Push 2 가지 방식이 존재한다.
- Push: 메트릭이 발생하는 곳에서 메트릭을 수집하는 곳으로 보낸다.
- Pull: 메트릭을 수집하는 곳에서 주기적으로 데이터를 수집해 간다.
Prometheus는 직접 주기적으로 메틀릭을 Pull 해오는 방식으로 동작한다.
Actuator
액추에이터(Actuator)는 상태, 메트릭, 환경 등 실행 중인 애플리케이션에 대한 운영 정보를 노출하는 데 사용된다. 해당 모듈을 추가함으로 해당 기능을 직접 구현하지 않아도 사용할 수 있으며 다양한 설정을 편리하게할 수 있게 도와준다.
엔드포인트를 통해 메트릭을 노출시키는데 기본적으로 제공하는 엔드포인트 외에도 사용자가 직접 정의한 엔드포인트도 사용할 수 있다.
- 제공하는 엔드 포인트 : 스프링 공식 문서
- Actuator 관련 정리글 - 보안 관련 사항 문서 하단
Grafana
그라파나(Grafana)는 오픈 소스 시각화 및 대시보드 도구로 다양한 데이터 소스와 통합되어 다양한 유형의 데이터를 시각화하고 사용자가 쉽게 대시보드를 구성할 수 있는 기능을 제공한다.
가장 일반적으로 사용되는 데이터 소스가 프로메테우스이다. 그 외에도 InfluxDB, ElasticSearch, MySQL, PostgreSQL, AWS CloudWatch 등 다양한 데이터 소스와 통합할 수 있다.
이러한 데이터 소스로부터 그라파나는 데이터를 쿼리하고, 그래프, 차트, 테이블 등 다양한 시각화 요소를 사용하여 데이터를 시각적으로 표현한다.
설정방법
메트릭 설정
Spring boot application, MySQL, Prometheus, Grafana를 Docker 환경에서 구성된 상태로 가정하고 진행한다.
스프링 부트에 메트릭 노출을 위한 Actuator와 메트릭 수집을 위한 Micrometer의 prometheus 의존성을 추가한다.
// Application Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
//implementation 'io.micrometer:micrometer-registry-prometheus'
// runtimeOnly, implementation 둘 중 하나를 선택해 사용하면 된다.
- runtimeOnly: Gradle 빌드 설정에서 라이브러리가 컴파일 타임이 아닌 런타임에만 필요할 때 사용한다, 이 방식의 이점은 애플리케이션의 빌드 과정에서 불필요한 라이브러리를 제외함으로써 컴파일 시간을 단축하고, 애플리케이션의 전체 패키지 크기를 줄일 수 있다는 것이다.
- 컴파일 시간 최적화: 라이브러리가 컴파일 타임에 필요하지 않고, 런타임에 동적으로 로드되어 컴파일 프로세스가 더 빨라질 수 있다.
- 애플리케이션 패키징 최적화: 애플리케이션의 WAR 또는 JAR 파일 크기에만 영향을 미치고, 컴파일된 코드의 크기에는 영향을 미치지 않는다.
- 의존성 충돌 방지: 컴파일 클래스 패스에서 제외되기 때문에, 다른 라이브러리와 의존성 충돌 가능성을 줄일 수 있다(여러 버전의 같은 라이브러리를 다룰 때 유용할 수 있다).
- 보안 및 유지관리: 개발 중에는 사용되지 않는 코드가 프로덕션 환경에서만 포함되므로, 개발환경과 프로덕션 환경 사이의 차이를 명확히 할 수 있다(보안상 리스크를 줄이고, 유지관리를 더 명확하게 할 수 있도록 돕는다).
애플리케이션 실행 후 http://localhost:8080/actuator 에 접속하면 현재 액추에이터가 제공하는 엔드포인트 목록을 확인할 수 있다.
메트릭 관련 설정 및 패스 설정을 위해 application properties(yml)에 관련 코드를 추가해야 한다.
# 모니터링 관련 설정
management:
server:
port: 9090 # Actuator 를 위한 별도 포트
endpoints:
web:
exposure:
include: info, health, prometheus # info, health, prometheus endpoint 만 enable
base-path: /custom # 사용하려는 커스텀 path 입력
jmx:
exposure:
exclude: '*' # jmx 비활성화, 보안을 위한 조치
metrics:
tags:
application: 'application name' # tag 이름 설정
prometheus:
metrics:
export:
enabled: true
- Actuator 서버 포트 설정: management.server.port: 9090
- Actuator 엔드포인트가 제공될 서버 포트를 '9090'으로 설정한다.
- Actuator 관리 엔드포인트가 애플리케이션의 주 서비스 포트와 분리되어 독립적으로 운영될 수 있도록 하는데, 이는 보안상의 이유로 일반 애플리케이션 트래픽과 관리 트래픽을 분리하는 데 유용하다.
- 엔드포인트 노출 설정: management.web.exposure.include : info, health, prometheus / management.web.base-path: /custom
- include: info, health, prometheus 엔드포인트만 활성화하여 노출한다. 이는 보안을 강화하고 필요한 정보만 제출하기 위한 선택적 노출이다.
- base-path: 모든 엔드포인트의 기본 경로를 /custom으로 설정한다. 이는 기본 경로 /actuator 대신 사용자 정의 경로를 사용하여 보안을 강화하는 방법이다.
- JMX 엔드포인트 노출 제외: management.endpoints.jmx.exposure.exclude: '*'
- JMX를 통한 모든 Actuator 엔드포인트 노출을 제외한다. JMX는 원격 관리를 위한 Java의 표준 기술이지만 보안 취약점이 될 수 있으므로, 사용하지 않는 경우 비활성화 하는 것이 좋다.
- 메트릭 태그 설정: management.metrics.tags.application: 'application name'
- Prometheus와 같은 모니터링 도구에서 수집되는 메트릭에 추가적인 태그를 포함시킨다(태그는 메트릭을 필터링하거나 구분할 때 유용하게 사용될 수 있다).
- Prometheus 메트릭 수출 활성화: management.prometheus.export.enabled: true
- Prometheus 메트릭 수출 기능을 활성화한다. 이 설정은 Spring Boot 애플리케이션에서 Prometheus 형식의 메트릭을 수집하고, '/prometheus' 엔드포인트를 통해 이 메트릭들을 제공할 수 있도록 한다.
이 외에도 /prometheus Custom Endpoint 정의나 사용자에 대한 인증 및 권한이 있는 사용자만 접근할 수 있도록 설정하는 것도 가능하다.
예시 - 서버 및 사용 환경에 맞게 수정 필요
커스텀 엔드 포인트
@Endpoint(id = "custom-metrics")
public class CustomPrometheusEndpoint {
private final PrometheusScrapeEndpoint prometheusScrapeEndpoint;
public CustomPrometheusEndpoint(PrometheusScrapeEndpoint prometheusScrapeEndpoint) {
this.prometheusScrapeEndpoint = prometheusScrapeEndpoint;
}
@ReadOperation
public String scrape() {
return prometheusScrapeEndpoint.scrape();
}
}
권한 있는 사용자만 접근
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 기본 설정
.authorizeRequests()
// 특정 엔드포인트에 대해 IP 제한 설정
.antMatchers("/actuator/custom-metrics").hasIpAddress("192.168.1.0/24") // 예시 IP 범위
.anyRequest().authenticated()
.and()
// 기타 필요한 보안 설정 추가
.httpBasic(); // HTTP Basic 인증 사용
}
}
@Endpoint(id = "custom-metrics")
public class CustomPrometheusEndpoint {
// ... 생략 ...
@ReadOperation
public ResponseEntity<String> scrape(HttpServletRequest request) {
// 여기서 request 정보를 이용하여 추가적인 접근 제어 로직을 구현
// 예를 들어, 특정 헤더가 있는지 확인
if (request.getHeader("X-Custom-Header") != null) {
return ResponseEntity.ok(prometheusScrapeEndpoint.scrape());
} else {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
}
}
프로메테우스 설정
prometheus.yml
#prometheus.yml
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: "service"
metrics_path: "/custom/prometheus" # 커스텀 base-path 반영
scheme: 'https' # http, https 선택
scrape_interval: 5s # 스크레이프 주기, 필요에 따라 조정
static_configs:
- targets: ['localhost:9090'] # 여기서 호스트 주소를 실제 서버 주소로 바꾸어야 합니다.
prometheus.yml 파일을 통해 프로메테우스에 관한 설정도 할 수 있다. 각 설정의 동작은 아래와 같다.
- global.scrape_interval: 주기적으로 스크랩하는 간격을 나타낸다, 기본값은 1분이다.
- global.ccrape_timeout: 스크랩 작업의 타임아웃을 나타낸다. 기본값은 10초이다.
- global.evaluation_interval: 규칙을 검증하는 간격을 나타낸다. 기본값은 1분이다.
- global.external_lables: 수집된 모든 메트릭에 추가될 전역 레이블을 정의한다. monitor이라는 레이블을 추가하고 그 값을 system-monitor로 지정한다.
- global.query_log_file: 쿼리 로그를 저장할 파일 경로를 지정한다.
- rule_files: 적용할 규칙 파일의 경로를 지정한다.
- scrape_configs.job_name: 스크랩할 작업의 이름을 지정한다.
- scrape_configs.static_configs.targets: 스크랩할 정적인 타깃을 정의한다.
- scrape_configs.metrics_path: 애플리케이션의 메트릭을 얻기 위한 엔드포인트를 지정한다.
- scrape_configs.schema: 스크랩할 대상의 프로토콜을 지정한다.
- scrape_configs.scrape_interval: 스크랩 간격을 지정한다.
해당 설정을 통해 프로메테우스는 메트릭을 스크랩하는 작업을 수행하게 된다.
rule.yml
//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)"
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Disk space is low on {{ $labels.instance }}"
description: "{{ $labels.instance }} has less than 10% free disk space."
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 80
for: 10m
labels:
severity: warning
annotations:
summary: "High memory usage on {{ $labels.instance }}"
description: "{{ $labels.instance }} is using more than 80% of its memory capacity."
- alert: HighCpuUsage
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 10m
labels:
severity: warning
annotations:
summary: "High CPU usage on {{ $labels.instance }}"
description: "{{ $labels.instance }} CPU usage is above 90%."
- alert: HighNetworkTraffic
expr: rate(node_network_receive_bytes_total[5m]) > 10000000
for: 5m
labels:
severity: warning
annotations:
summary: "High network traffic on {{ $labels.instance }}"
description: "{{ $labels.instance }} is experiencing unusually high network traffic."
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 10m
labels:
severity: critical
annotations:
summary: "High error rate detected in {{ $labels.app }}"
description: "More than 5% of requests are resulting in 5xx status codes for {{ $labels.app }}."
프로메테우스에서는 메트릭 스크랩 설정 외에도 알람에 대한 설정도 가능하다.
프로메테우스에서 알람은 시스템 또는 서비스의 이상 상태를 감지하고 이에 대한 경고 또는 통지를 생성하는 기능이다.
알람은 메트릭 데이터를 기반으로 정의되며 특정 조건이 충족될 때 발생한다.
각 설정의 동작은 아래와 같다.
- groups.name: 해당 그룹의 이름을 지정한다.
- groups.rules: 그룹에 속하는 알람 규칙들을 정의한다.
- groups.rules.alert: 알람의 이름을 지정한다.
- groups.rules.expr: 알람이 발생하는 표현식을 정의한다. up == 0은 메트릭이 0인 경우를 평가하는 표현식이다.
- groups.rules.for: 조건이 유지되어야 하는 시간을 지정한다.
- groups.rules.labels: 알람에 대한 추가 레이블을 지정한다.
- groups.rules.annotations: 알람에 대한 추가 설명을 제공하는 어노테이션을 지정한다.
alertmanager.yml
#alertmanger.yml
global:
resolve_timeout: 5m
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10m
repeat_interval: 1h
receiver: 'slack-notifications'
receivers:
- name: 'slack-notifications'
slack_configs:
- api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' #실제 Slack webhook URL입니다. 이 URL은 Slack API에서 제공받아야 합니다.
channel: '#alerts'
send_resolved: true
text: |
{{ range .Alerts }}
*Alert:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Details:*
{{ range .Labels.SortedPairs }} {{ .Name }}: {{ .Value }}
{{ end }}
{{ end }}
컨테이너 실행
//docker-compose.yml
version: '3.7'
# volumes:
# prometheus_data:
# external: true
# grafana_data:
# external: true
services:
prometheus:
image: prom/prometheus
container_name: prometheus
volumes:
- ~/prometheus/config/:/etc/prometheus/
- ~/prometheus/prometheus-volume:/prometheus # volumes 설정한다면 이부분 주석처리
# - prometheus_data:/prometheus
ports:
- 9090:9090
command:
- '--web.enable-lifecycle'
- '--config.file=/etc/prometheus/prometheus.yml'
restart: always
networks:
- promnet
grafana:
image: grafana/grafana
container_name: grafana
depends_on:
- prometheus
ports:
- 3000:3000
volumes:
- ~/prometheus/grafana-volume:/var/lib/grafana # volumes 설정한다면 이부분 주석처리
- ./config/grafana-init.ini:/etc/grafana/grafana.ini
# - grafana_data:/var/lib/grafana
restart: always
networks:
- promnet
alertmanager:
image: prom/alertmanager
container_name: alertmanager
user: root
ports:
- 9093:9093
volumes:
- ~/prometheus/alertmanager/config/:/etc/alertmanager/
networks:
- promnet
restart: always
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
networks:
promnet:
driver: bridg
application, mysql, prometheus, grafana 컨테이너를 하나의 네트워크로 동작시키기 위한 도커 컴포즈 파일의 예시이다.
도커 컴포즈 파일을 실행시킴으로써 모니터링에 필요한 여러 애플리케이션을 실행시킬 수 있다.
프로메테우스 컨테이너
컨테이너를 정상적으로 띄웠다면 설정한 주소(path)를 통해 프로메테우스 대시보드에 접속할 수 있다.
프로메테우스에서도 메트릭 데이터를 그래프로 시각화할 수 있지만, 그라파나를 통해 더욱 효과적으로 시각화할 수 있다.
그라파나 컨테이너
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 주소로 해석할 수 있다.
대시보드 생성
그라파나 대시보드를 직접 만들어서 커스텀하는 방법도 있지만, 만들어져 있는 대시보드를 임포트하는 방법도 있다.
4701은 스프링 부트 메트릭을 보여주는 유명한 대시보드의 ID를 뜻한다. 대시보드를 통해 I/O, JVM Memory, CPU, GC, Thread 등의 메트릭 데이터를 시각화해서 볼 수 있다.
JMeter
웹 애플리케이션 서비스의 성능을 분석하고 측정하기 위한 부하 테스트 도구인 JMeter를 사용해 트래픽을 발생시켜 모니터링 할 수도 있다.
'항해 99 > Spring' 카테고리의 다른 글
Reflection API (0) | 2024.04.23 |
---|---|
POJO (0) | 2024.04.22 |
낙관적 락 & 비관적 락 (0) | 2024.04.19 |
Spring Actuator (1) | 2024.04.18 |
즉시로딩, 지연로딩, N+1 문제 (0) | 2024.04.13 |