๐Ÿ‘จ‍๐Ÿ‘ฉ‍๐Ÿ‘ง‍๐Ÿ‘ฆ Project/๐Ÿ“บ KIOSEK

๋ชจ๋‹ˆํ„ฐ๋งํ™˜๊ฒฝ ๊ตฌ์ถ•ํ•˜๊ธฐ with Spring Actuator, Micrometer, Prometheus, Grafana

DevPoong 2023. 5. 15. 21:04

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 ํ˜ธ์ถœ ์‘๋‹ต ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜๋„ ์žˆ๋‹ค.

Spring ๊ณต์‹ ๋ฌธ์„œ

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/

Grafana๋ฅผ ์ด์šฉํ•œ ๋Œ€์‹œ๋ณด๋“œ

 

โœ๏ธ 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(...) {
	...
}

 

์ด๋ ‡๊ฒŒ ๋ชจ๋‹ˆํ„ฐ๋ง ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฐ„๋žตํ•˜๊ฒŒ๋‚˜๋งˆ ๊ณต๋ถ€ํ•˜์˜€๊ณ  ํ˜„์žฌ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ ์œ„์ฃผ๋กœ๋งŒ ๊ณต๋ถ€ํ•˜์—ฌ ์‹ค์ œ ๊ตฌ์ถ•ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.