1. ๋ฌธ์ ์ํฉ
์ฌ์ฉ์ ์๊ฐ ๋ง์ง ์์ง๋ง ๋ฐฐํฌํ๋ ค๋ ๋ฌผ๋ฆฌ ์๋ฒ ์ปดํจํฐ์ ์ฑ๋ฅ์ด ๊ทธ๋ฅ ์ข์ง ์๊ธฐ์ ํน์๋ ์๋ชป๋๋ฉด ์ด๋กํ๋๋ผ๋ ๊ฑฑ์ ์ด ๊ณ์ ์์๊ณ CPU, ๋ฉ๋ชจ๋ฆฌ, Thread Pool์ ๊ณ์ ๋ชจ๋ํฐ๋ง ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์๊น? ๋ผ๊ณ ์๊ฐํ์ฌ ์ฐพ์๋ณด๋ค๊ฐ
Spring Actuator, Micrometer, Prometheus, Grafana์ ๊ฐ์ ํค์๋๋ฅผ ์๊ฒ๋์ด ๊ณต๋ถํ๊ณ ๊ตฌ์ถํ๊ฒ ๋์๋ค.
2. ๋ฌธ์ ํด๊ฒฐ
โ๏ธ Spring Actuator
Production์ ์ด์ํ๊ฒฝ์ ๋ฐฐํฌํ ๋ Metric(์งํ), Trace(์ถ์ ), Auditing(๊ฐ์ฌ), ๋ชจ๋ํฐ๋ง๊ณผ ๊ฐ์ ํ์ํ ๊ธฐ๋ฅ์ ๋งค์ฐ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
Micrometer, Prometheus, Grafana๋ฅผ ๋งค์ฐ ์ฝ๊ฒ ์ฐ๋ํ ์ ์๊ฒ ํด์ค๋ค.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
Spring Actuator๋ฅผ ์ฌ์ฉํ๋ฉด ์ดํ๋ฆฌ์ผ์ด์
๋ด๋ถ๋ฅผ ๋ค์ฌ๋ค ๋ณผ ์ ์๊ณ , ์ผ๋ถ๋ถ ๋ก๊ทธ์ ๊ฐ์ ์ดํ๋ฆฌ์ผ์ด์
์๋ ๋ฐฉ๋ฒ์ ์ ์ดํ ์ ์๋ค.
dependency๋ฅผ ์ถ๊ฐํ๊ณ ์ถ๊ฐ ์ค์ ์ ํด์ค์ผ ์น ํ๊ฒฝ์์ ์ก์ธ์์ดํฐ์ ๋ง์ ๊ธฐ๋ฅ์ ๋ณผ ์ ์๊ฒ ๋๋ค.
# application.yml
management:
endpoints:
web:
exposure:
include: "*"
http://์ฃผ์/actuator ๋ฅผ ์คํํ๋ฉด ์๋์ ๊ฐ์ ๋ง์ ๊ธฐ๋ฅ์ ํ์ธํ ์ ์๋๋ฐ, ์ด ๊ธฐ๋ฅ์ ๊ฐ๊ฐ ์๋ํฌ์ธํธ๋ผ๊ณ ํ๋ค.
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"configprops-prefix": {
"href": "http://localhost:8080/actuator/configprops/{prefix}",
"templated": true
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
..........etc
https://์ฃผ์/actuator/health์ ๊ฐ์ด ์ ๊ทผํ์ฌ ์ดํ๋ฆฌ์ผ์ด์
์ ์คํ ์ ๋ณด๋ฅผ ๋ณผ ์๋ ์๊ณ
beans๋ฅผ ํตํด Spring Bean ์ ๋ณด, httpexchanges๋ฅผ ํตํด HTTP ํธ์ถ ์๋ต ์ ๋ณด๋ฅผ ๋ณผ ์๋ ์๋ค.
https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints ๋ฅผ ํ์ธ!
http://์ฃผ์/actuator/metrics์ ์ ๊ทผํ๋ฉด ์ฌ๋ฌ ๊ธฐ๋ณธ Metric์ ํ์ธํ ์ ์๋ค.
metrics ์๋ํฌ์ธํธ๋ ๋จ์ํ๊ฒ /actuator/metrics/system.cpu.usage์ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ ๊ทผํ ์ ์๋ค.
Spring Actuator๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฃผ ํธ๋ฆฌํ์ง๋ง ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ง์ ๋ด๋ถ ์ ๋ณด๋ฅผ ๋ ธ์ถํ๊ฒ ๋๋ฏ๋ก ์ธ๋ถ๋ง์์ ์ ๊ทผํ์ง ๋ชปํ๋๋ก ํ๊ณ ๋ด๋ถ๋ง์์๋ง ์ ์ํ๋๋ก ํ๋๊ฒ์ด ์ข๋ค.
โ๏ธ Micrometer
Micrometer์ ๋ํด์ ์ค๋ช
ํ๊ธฐ ์ ์
๋จผ์ ์ดํ๋ฆฌ์ผ์ด์
์ ๋ชจ๋ํฐ๋ง ํ๊ธฐ ์ํด JMX ๋ชจ๋ํฐ๋ง ํด์ ์ฌ์ฉํ์๋ค๊ณ ๊ฐ์ ํด๋ณด์!
๊ทธ๋ ๋ค๋ฉด ์ดํ๋ฆฌ์ผ์ด์
์์๋ JMX ๋ฐฉ์์ ๋ง์ถฐ ์ธก์ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ๊ฒ์ด๋ค.
ํ์ง๋ง, ๋ชจ๋ํฐ๋ง ํด์ ๊ฐ์๊ธฐ Prometheus๋ก ๋ณ๊ฒฝํ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
์ญ์๋ ์ธก์ ๋ถํฐ ๋ค์ ๋ชจ๋ Prometheus์ ๋ง๊ฒ ๋ณ๊ฒฝํด์ผ ํ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ Micrometer๋ผ๋ ์ถ์ํ๋ ํ์ค์ ์ด์ฉํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
๋ง์น JPA๊ฐ ํ์ค์ด๊ณ Hibernate๊ฐ JPA์ ๊ตฌํ์ฒด์ด๋ฏ ๋น์ทํ ๋๋์ผ๋ก
micromer๊ฐ ํ์ค ์ธก์ ๋ฐฉ์์ด๊ณ ๊ทธ๊ฒ์ ๊ตฌํ์ฒด์ธ Prometheus ๊ตฌํ์ฒด์ JMX ๊ตฌํ์ฒด๊ฐ ์กด์ฌํ๊ฒ ๋๋ค.
Micrometer ํ์ค์ผ๋ก ์ธก์ ๋ ๊ฐ์ JMX๋ Prometheus์ ๋ง๊ฒ ๋ณํํด์ ์ ๋ฌํ๋ฏ๋ก ์ค๊ฐ์ ๋ชจ๋ํฐ๋ง ํ๊ฒฝ์ด ๋ณ๊ฒฝ๋๋๋ผ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
Micrometer์ Actuator๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ๋ฌ Metric์ ์ ๊ณตํ์ง๋ง ๋ช๊ฐ๋ง ์ดํด๋ณด์๋ฉด
JVM, System, Application, jdbc, http, hikaricp, disk, tomcat๊ณผ ๊ฐ์ ๋ฉํธ๋ฆญ์ ์ ๊ณตํ๋ค.
๋ํ ์ฌ์ฉ์๊ฐ ์ง์ ๋ฉํธ๋ฆญ์ ์ ์ํ ์ ์๋ค.
๋๋ ์ด๋ฅผ ์ด์ฉํด์ ๊ธ ๋ง์ง๋ง ๋ถ๋ถ์์ ์ค๋ช
ํ๊ฒ ์ง๋ง ํน์ ์์ฝ ๋๋ ๋ก๊ทธ์ธ api๊ฐ ์๋ฒ์ ํธ์ถ์ด ๋ค์ด์จ ์๊ฐ๋ถํฐ ์๋ตํ๊ธฐ๊น์ง์ ์คํ์๊ฐ์ ์ธก์ ํ๊ณ ํธ์ถ ํ์๋ฅผ ์ธก์ ํ์ฌ ๊ด๋ฆฌํ ์ ์๋ ๋ฉํธ๋ฆญ์ ์ถ๊ฐํ์๋ค.
โ๏ธ Prometheus
Micrometer๊ฐ ์ง์ํ๋ ๋ชจ๋ํฐ๋ง ํด์ ๋ํ์ ์ผ๋ก Prometheus, CloudWatch, JMX, Elastic ๋ฑ์ด ์์ง๋ง ๋๋ Prometheus๋ฅผ ์ ํํ์๋ค.
์ฒ์ ๋ชจ๋ํฐ๋ง ํ๊ฒฝ์ ๋์ ํ๋ ๊ฒ์ด๊ธฐ์ ๋ฌด์์ด ์ข๊ณ ๋์๊ณ ๋ฅผ ๋น๊ตํ๊ธฐ๋ณด๋ค ์ค์น ์ฉ์ด์ฑ๊ณผ ์ฐธ๊ณ ํ ์๋ฃ๊ฐ ๋ง์์ง์ ๋ฌด๊ฒ๋ฅผ ๋์๋ค.
Prometheus๋ ๋จ์ํ๊ฒ ๋งํ์๋ฉด Metric์ ๋ณด๊ดํ๊ธฐ ์ํ Database ๋ผ๊ณ ์๊ฐํ๋ฉด ์ฝ๋ค.
๊ณผ๊ฑฐ๋ ํ์ฌ์ ์ด๋ ฅ์ ํจ๊ป ํ์ธํ๊ธฐ ์ํด์๋ ๋ฉํธ๋ฆญ์ ๋ณด๊ดํ ์ฅ์๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์Prometheus๋ Metric์ ๊ณ์ํด์ ์ ๋ฌ๋ฐ์ ์ ์ฅํ๊ณ ํ์ํ ๋ ๊บผ๋ด์ธ ์ ์๊ฒ ํด์ฃผ๋ ์ญํ ์ ํ๋ค.
sum, count ๋ฑ๊ณผ ๊ฐ์ ์ฐ์ฐ์ ํ ์ ์๊ณ
ex) count(http_server_request_seconds_count)
Counter, Guage ํํ๋ก ๊ทธ๋ํ๋ฅผ ์ถ๋ ฅํ ์๋ ์๋ค.
Prometheus์์๋ GUI๋ก ํ์ธํ ์ ์์ง๋ง ๋์๋ณด๋ ํํ๋ ์๋๋ฉฐ ๋ณด๊ธฐ ํ๋ค๋ค ๊ทธ๋์ Grafana๋ฅผ ์ด์ฉํ๋ค.
โ๏ธ Grafana
Prometheus์ ์ ์ฅ๋์ด์๋ Metric์ ๋ถ๋ฌ์ ๋์๋ณด๋ GUI๋ฅผ ํตํด ์ฌ์ฉ์์๊ฒ ํธ๋ฆฌํ๊ฒ ๊ทธ๋ํ๋ ์์ค๋ฅผ ์ ๊ณตํ๋ค.
์ง์ ํ๋ํ๋ ๋ค ์ถ๊ฐํ ์๋ ์์ง๋ง
์ ๋ง ์ข์๊ฒ Grafana ์ฌ์ดํธ์ ์ ์ํ์ฌ ๊ณต์ Dashboard๋ฅผ ๊ฒ์ํด๋ณด๋ฉด Spring์ ์ต์ ํ๋ ๋์๋ณด๋๋ค์ ์์ฝ๊ฒ ๋ถ๋ฌ์ ์ฌ์ฉํ ์ ์๋ค.
์ด๋ฏธ ๋ง๋ค์ด์ ธ์๋ ๊ณต์ ๋์๋ณด๋๋ฅผ ๋ถ๋ฌ์์ ์์ ํ๊ฑฐ๋ ์ถ๊ฐํ์ฌ ์ฌ์ฉํ๋ค๋ฉด ์๊ฐ์ ์ ์ฝํ ์ ์๋ค.
https://grafana.com/grafana/dashboards/11378-justai-system-monitor/
โ๏ธ Custom Metric์ ๋ฑ๋กํด๋ณด์
MeterRegistry๋ ๋ง์ดํฌ๋ก๋ฏธํฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํต์ฌ ์ปดํฌ๋ํธ์ด๋ฉฐ, ์คํ๋ง์ ํตํด์ ์ฃผ์
๋ฐ์ ์นด์ดํฐ, ๊ฒ์ด์ง ๋ฑ์ ๋ฑ๋กํ ์ ์๋ค.
ํ์ง๋ง ์ด๋ฏธ ์คํ๋ง์์๋ ํ์ํ ์์๋ค์ AOP๋ก ๋ค ๋ง๋ค์ด์ ธ์์ผ๋ฏ๋ก AOP๋ฅผ ๋ฑ๋กํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋ค.
๋ฐ๋ผ์ @Counted, @Timed ์ ๊ฐ์ด ์ธก์ ํ๊ณ ์ ํ๋ ์์์ ์ด๋
ธํ
์ด์
์ ๋ถ์ฌ์ฃผ๋ฉด Metric์ด ์ธก์ ๋๋ค.
Counter์ ๊ฒฝ์ฐ ์ผ์ ํ๊ฒ ์ฆ๊ฐํ๋ ๋์ ์ธก์ ์ ํ ๋ ์ฌ์ฉํ๋ค. ex) HTTP ์์ฒญ์
@Counted("example.reserve")
public Response reserve(...) {
...
}
์ ๊ฐ์ด ์ด๋ ธํ ์ด์ ๋ด์ Metric์ ์ด๋ฆ์ผ๋ก ์ฌ์ฉํ ๋ฌธ์์ด์ ๋ฃ์ด ์ฌ์ฉํ ์ ์๋ค.
Timer์ ๊ฒฝ์ฐ ์คํ ์๊ฐ์ ์ธก์ ํ๋ ๊ฒฝ์ฐ ์ฌ์ฉ๋๋ฉฐ ์คํ ํ์, ์ด ์คํ์๊ฐ, ์ต๋ ์คํ ์๊ฐ 3๊ฐ์ง ๋ฉํธ๋ฆญ์ ์ธก์ ํ ์ ์๋ค.
์ด ์คํ์๊ฐ๊ณผ ์ด ์คํ์๊ฐ์ ๋๋๋ฉด ํ๊ท ์คํ์๊ฐ์ ๊ตฌํ ์ ์๋ฏ์ด ๊ธฐ์กด ๋ฉํธ๋ฆญ์ ํ์ฉํ์ฌ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์ป์ ์๋ ์๋ค.
@Timed("example.login")
public Response login(...) {
...
}
์ด๋ ๊ฒ ๋ชจ๋ํฐ๋ง ํ๊ฒฝ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ๊ฐ๋ตํ๊ฒ๋๋ง ๊ณต๋ถํ์๊ณ ํ์ฌ ํ์ํ๋ค๊ณ ์๊ฐํ๋ ๊ฒ ์์ฃผ๋ก๋ง ๊ณต๋ถํ์ฌ ์ค์ ๊ตฌ์ถํ๊ฒ ๋์๋ค.