Prometheus
ํ๋ก๋ฉํ ์ฐ์ค๋ ๋์ ์์คํ ์ผ๋ก๋ถํฐ ๊ฐ์ข ๋ชจ๋ํฐ๋ง ์งํ๋ฅผ ์์งํ์ฌ ์ ์ฅํ๊ณ ๊ฒ์ํ ์ ์๋ ์์คํ ์ด๋ค.
ํ๋ก๋ฉํ ์ฐ์ค ํน์ง
- ๋ฉํธ๋ฆญ ๊ธฐ๋ฐ์ ์คํ์์ค ๋ชจ๋ํฐ๋ง ์์คํ ์ด๋ค.
- ์ด๋ฒคํธ ๋ชจ๋ํฐ๋ง ๋ฐ ๊ฒฝ๊ณ ์ ์ฌ์ฉ๋๋ ๋ฌด๋ฃ ์ํํธ์จ์ด ์์ฉ ํ๋ก๊ทธ๋จ์ด๋ค.
- ์ ์ฐํ ์ฟผ๋ฆฌ(PromQL) ๋ฐ ์ค์๊ฐ ๊ฒฝ๊ณ ๊ฐ ๊ฐ๋ฅํ๋ค.
- ๊ตฌ์กฐ๊ฐ ๊ฐ๋จํด์ ์ด์์ด ์ฝ๊ณ , ๊ฐ๋ ฅํ ์ฟผ๋ฆฌ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ๊ทธ๋ผํ๋(Grafana)๋ฅผ ํตํ ์๊ฐํ๋ฅผ ์ง์ํ๋ค.
- ELK์ ๊ฐ์ ๋ก๊น ์ด ์๋๋ผ, ๋์ ์์คํ ์ผ๋ก๋ถํฐ ๊ฐ์ข ๋ชจ๋ํฐ๋ง ์งํ๋ฅผ ์์งํ์ฌ ์ ์ฅํ๊ณ ๊ฒ์ํ ์ ์๋ ์์คํ ์ด๋ค.
- ํ๋ก๋ฉํ ์ฐ์ค๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก exporter(๋ชจ๋ํฐ๋ง ๋์ ์์คํ )๋ก๋ถํฐ pulling ๋ฐฉ์์ผ๋ก ๋ฉํธ๋ฆญ์ ์ฝ์ด์ ์์งํ๋ค.
๋ฉํธ๋ฆญ์ด๋?
์์งํ๋ ์๊ณ์ด ๋ฐ์ดํฐ๋ฅผ ๋งํ๋ค.
ํ๋ก๋ฉํ ์ฐ์ค์ ๋ฉํธ๋ฆญ์ "๋ฉํธ๋ฆญ๋ช {ํ๋1=๊ฐ, ํ๋2=๊ฐ} ์ํ๋ง๋ฐ์ดํฐ" ์ ๊ฐ์ด ์์ง๋๋ค.
์๊ณ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋?
์๊ณ์ด DB๋ ์๊ฐ์ ์ถ(ํค)์ผ๋ก ์๊ฐ์ ํ๋ฆ์ ๋ฐ๋ผ ๋ฐ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฐ ์ต์ ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ด๋ค.
๊ธฐ๋ฅ ๋ฐ ๊ตฌ์ฑ
- ๋ฉํธ๋ฆญ ์์ง, ์๊ณ์ด ๋ฐ์ดํฐ ์ ์ฅ
- ์ ์ฐํ ์ฟผ๋ฆฌ ์ธ์ด์ธ PromQL์ ํตํ ์ฑ๋ฅ ๋ถ์
- ๊ทธ๋ผํ๋๋ฅผ ํตํ ๋ฐ์ดํฐ ์๊ฐํ
- alertmanager๋ฅผ ํตํ ์๋ฆผ ์์ฑ
Grafana
- ํ๋ก๋ฉํ ์ฐ์ค๋ฅผ ๋น๋กฏํ ์ฌ๋ฌ ๋ฐ์ดํฐ๋ค์ ์๊ฐํํด์ฃผ๋ ๋ชจ๋ํฐ๋ง ํด
- ์์คํ ๊ด์ (CPU, ๋ฉ๋ชจ๋ฆฌ, ๋์คํฌ)์ ๋ฉํธ๋ฆญ ์งํ๋ฅผ ์๊ฐํํ๋๋ฐ ํนํ
- ์๋๊ธฐ๋ฅ์ ๋ฌด๋ฃ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
Architecture
์ด์ Spring boot ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ํฐ๋ง ํด๋ณด์
Spring Boot Actuator๋ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ํฐ๋งํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. Actuator๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ํ ์ํ์ ๋ฉํธ๋ฆญ์ ๋ํ ์ ๋ณด๋ฅผ ๋ ธ์ถํ ์ ์๋ค. ์ฆ expoter ์ญํ ์ ํ๋ ๊ฒ์ด๋ค.
acutator๊ฐ ์๋๋ผ๋ฉด ํต์ ๋ง์ด ์ฌ์ฉ๋๋ expoter๋ 'Node Expoter', 'CAdvisors' ๋ฅผ ์ฌ์ฉํด์ ๋ชจ๋ํฐ๋ง ํ ์ ์๋ค. Prometheus์ Grafana์ ๊ฐ์ ๋ชจ๋ํฐ๋ง ๋๊ตฌ๋ ์ด๋ฌํ Actuator ์๋ํฌ์ธํธ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋ฐ ์ฑ๋ฅ ๋ฐ์ดํฐ๋ฅผ ์์งํ ์ ์๋ค.
์ฐ์ build.gradle์ actuaor์ prometheus๋ฅผ ์ถ๊ฐํด์ฃผ์
// actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// prometheus
implementation 'io.micrometer:micrometer-registry-prometheus'
๊ทธ๋ฌ๊ณ application.yml์ ์๋ํฌ์ธํธ๋ฅผ ์ค์ ํด์ฃผ๋ฉด ๋๋ค.
management:
endpoints:
web:
base-path: /jikgong
exposure:
include: prometheus
[์ฃผ์]:8080/jikgong/prometheus ์ผ๋ก ์ ์ํ๋ฉด ์๋์ ๊ฐ์ ํ๋ฉด์ ๋ง๋๋ณผ ์ ์์ ๊ฒ์ด๋ค.
base-path๋ฅผ ๋ฐ๋ก ์ง์ ํ์ง ์์ผ๋ฉด 800:/acutaor/prometheus ์ผ ๊ฒ์ด๋ค.
์ด์ Prometheus์ Grafana์ ๋์ธ docker-composeํ์ผ์ ์์ฑํด์ค๋ค.
์ด๋ prometheus์ volume์ ์ก์ ์ธ๋ถ์์ prometheus์ค์ ์ ํ ์ ์๋๋ก ํด์ฃผ์๋ค.
version: "3.8"
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- /home/kevin/prometheus/conf/prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
restart: always
grafana:
image: "grafana/grafana"
ports:
- "3000:3000"
volumes:
- /home/kevin/grafana/conf_grafana:/config_files
restart: always
depends_on:
- prometheus
privileged: true
volume์ผ๋ก ์ก์๋ prometheus.yml์ ์์ฑํด์ฃผ์.
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["localhost:9090"]
- job_name: "java_application"
metrics_path: '/jikgong/prometheus'
scrape_interval: 5s
static_configs:
- targets: ["์๋ฒIP:8080"]
์์์ ์์ฑํ๋ prometheus, grafana docker compose ํ์ผ์ ์คํ์์ผ์ฃผ๊ณ
9090ํฌํธ๋ก ์ ์ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ status -> targets ๋ฉ๋ด๋ก ๋ค์ด๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ์์ ์ผ๋ก ๋ก๋ ๋๋์ง ํ์ธํด์ฃผ๋ฉด ๋๋ค.
์ฌ๊ธฐ ์ ๋๋ก ๋จ์ง ์๋๋ค๋ฉด prometheus.yml์ targets ์ฃผ์๋ฅผ ์ ๋ชป ์ ์์ ๊ฐ๋ฅ์ฑ์ด ์๋ค.
๋๊ฐ์ ๊ฒฝ์ฐ์ spring boot๋ฅผ ๋์ด ๋คํธ์ํฌ์ prometheus, grafana๋ฅผ ๋์ด ๋คํธ์ํฌ๊ฐ ๋ฌ๋ผ host.docker.internal:8080 ์ผ๋ก ์ค์ ํ์ ๋ ์ ์์ด ๋์ง ์์์๋ค.
๊ทธ๋ผํ๋๋ 3000๋ฒ ํฌํธ๋ก ์ก์๊ธฐ ๋๋ฌธ์
[ip์ฃผ์]:3000 ์ผ๋ก ์ ์ํด์ค๋ค. ์๋์ ๊ฐ์ ํ๋ฉด์ด ๋ฐํ ๋ฐ ๋ฐ๋ก ์ค์ ํ์ง ์์์ผ๋ฉด admin/admin์ด๋ค.
๋ฐ๋ก ์ค์ ํ๊ณ ์ถ๋ค๋ฉด docker-compose ํ์ผ์์ ๋ฐ๋ก ์ค์ ํ ์ ์๋ค.
datasource๋ฅผ ์ถ๊ฐํด์ค๋ค.
Prometheus๋ฅผ ํด๋ฆญํ๊ณ connetion์ ์ฃผ์๋ฅผ ์ ๋ ฅํด์ค๋ค.
์ ๋๋ก ์ฃผ์๋ฅผ ์ ๋ ฅํ๋ค๋ฉด ์๋ ๊ทธ๋ฆผ์ฒ๋ผ ๋ฐ ๊ฒ์ด๋ค.
๋ง๋ค์ด์ง datasource์ dashboard๋ฅผ buildํด์ค๋ค.
๋ณธ์ธ์ด ์ง์ ๋์ฌ๋ณด๋๋ฅผ ๊ตฌ์ฑํด๋ ๋์ง๋ง ์ด๋ฏธ ์ ๋ง๋ค์ด์ง ๋์๋ณด๋๋ค์ด ์๊ธฐ ๋๋ฌธ์ ์ทจํฅ๊ฒ ์ ํํ๋ฉด ๋๋ค.
๋๋ ์ด๋ฏธ ๋ง๋ค์ด์ง ๊ฑธ ์ฌ์ฉํ๊ธฐ ์ํด import๋ฅผ ํด์ฃผ์๋ค.
์ด์ ๊ทธ๋ผํ๋๋ฅผ ํตํด Spring boot์ ๋ฉํธ๋ฆญ์ ํ์ธํ ์ ์๊ฒ๋๋ค.
์ธํ ์ ์ด๋ ต์ง์์ผ๋, ์ด ์งํ๋ฅผ ๊ฐ์ง๊ณ ์ผ๋ง๋ ์ ์ด์์ ํ๋๊ฐ ์ค์ํ ๊ฒ ๊ฐ๋ค.
์ถํ์ 5xx์๋ฌ๊ฐ ๋ฐ์, ๋ฉ๋ชจ๋ฆฌ ์ผ์ ๋ ์ด์ ์ฌ์ฉ, CPU ์ผ์ ๋ ์ด์ ์ฌ์ฉ ๋ฑ ์กฐ๊ฑด์ ๊ฑธ์ด slack์ผ๋ก ์๋์ ๋ฐ์ ๋ณผ ์ ์๊ฒ๋ ๊ตฌ์ถํ ์์ ์ด๋ค.
๊ทธ๋ผํ๋์ alerting์ ์ฌ์ฉํ์ฌ slack์ผ๋ก ์๋ ๋ฐ๊ธฐ๋ฅผ ๋์ .. ํ์ง๋ง ์ ๋ง ์ด๋ ค์ ๋ค. ์ ํ๋ธ์ ๋น๊ทผ๋ง์ผ ๊ฐ๋ฐ์๋์ด ์ค๋ช ํด์ฃผ์๋ ์์์ ์ฐธ๊ณ ํ์ฌ ๊ตฌํํด๋ณด๋ ค ํ์ง๋ง, ๋ฒ์ ๋ ๋ค๋ฅด๊ณ ๋ง์๊ฐ์ด ์ ๋์ง ์์๋ค.
๊ทธ๋์ ์ฐ์ ์ AOP๋ฅผ ์ ์ฉํด ์๋ต๊น์ง x์ด ์ด์ ๋์ด๊ฐ api ์์ฒญ์ ๋ํด์ slack์ผ๋ก ์๋ฆผ์ ๋ณด๋ด๋ ๊ฒ์ผ๋ก ์์ ๋์ฒด ํ์๋ค.
์๋๋ ํด๋น ์ฝ๋์ด๋ค.
@Around("pointcut()") // ์ด๋๋ฐ์ด์ค + ํฌ์ธํธ์ปท ์ค์
public Object advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
long start = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();// ํ๊น ๋ฉ์๋ ํธ์ถ
long end = System.currentTimeMillis();
long runningTime = end - start;
if (runningTime <= 1000) {
log.info("[์ ์ ์คํ] method = {}, ์คํ์๊ฐ = {} ms", methodName, runningTime);
} else {
String message = "[!๊ฒฝ๊ณ ] [๊ธฐ์ค ์คํ ์๊ฐ์ ์ด๊ณผํ์์ต๋๋ค] method = " + methodName + ", ์คํ์๊ฐ = " + runningTime + "ms";
log.error(message);
// Slack ๋ฉ์์ง ์ ์ก
HashMap<String, String> data = new HashMap<>();
data.put("๊ฒฝ๊ณ ๋ด์ฉ", message);
slackService.sendMessage("๊ฒฝ๊ณ : ์คํ ์๊ฐ ์ด๊ณผ", data);
}
return result;
}