어렸을 때부터 지속되어 온 생활습관들이 세월이 흘러 중년이 되면 몸에서 질병으로 나타난다. 프로젝트의 요구사항을 기반으로 개발자가 탄생시킨 프로그램도 마찬가지로 에러 메시지로 상황을 알려준다.
지속해서 눈감고 모른 체하게 되면 프로그램이 중단되는 사태가 발생하는데 질병을 무시했다가 큰 병을 얻는 경우와 같은 상황이다.
사람은 정기적으로 건강검진을 받는 것처럼, 프로그램도 정기적으로 관리를 해야 한다.
프로그래밍 언어마다 감시하는 방법이 존재하지만, 예시는 Java 프레임워크 Spring Boot 기준으로 하겠다.
Spring Boot는 Actuator를 사용하면 프로그램의 상황을 감시할 수 있는데, Http Endpoint, JMX, 원격 SSH 3가지 방법으로 확인할 수 있다.
Actuator를 활성화하려면 모듈을 추가해야 한다.
dependencies {
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.boot:spring-boot-starter-remote-shell'
}
모듈이 정상적으로 컴파일되면 Http Endpoint, 원격 SSH가 활성화된다. port는 설정하지 않으면 기본값 2000이다.
Http Endpoint의 중복을 방지하기 위해 추가 설정이 필요하다. application.properties에 추가한다.
management:
context-path: /manage
security:
enabled: false
shell:
ssh:
enabled: true
port: 18080
health:
db:
enabled: true
diskspace:
enabled: true
endpoints:
beans:
sensitive: false
Http Endpoint 목록은 /manage/mappings로 요청하면 확인할 수 있다.
{
"{[/manage/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)"
},
"{[/manage/metrics || /manage/metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/auditevents || /manage/auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)"
},
"{[/manage/configprops || /manage/configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/mappings || /manage/mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/application || /application.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public org.springframework.hateoas.ResourceSupport org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint.links()"
},
"{[/manage/trace || /manage/trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/heapdump || /manage/heapdump.json],methods=[GET],produces=[application/octet-stream]}": {
"bean": "endpointHandlerMapping",
"method": "public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException"
},
"{[/manage/autoconfig || /manage/autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/beans || /manage/beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)"
},
"{[/manage/env || /manage/env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/health || /manage/health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)"
},
"{[/manage/dump || /manage/dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/info || /manage/info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/manage/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)"
},
"{[/manage/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)"
},
"{[/manage/loggers || /manage/loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
}
}
대표적으로 많이 쓰이는 것은 2가지이다.
상태 값 확인은 /manage/health를 사용하면 되고, 추가 설정을 하게 되면 redis, mysql 저장소도 확인할 수 있다.
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 214734073856,
"free": 195815051264,
"threshold": 10485760
}
}
Cpu, Memory, Threads, GC, Datasource를 감시하기 위해서는 /manage/metrics를 사용한다.
{
"mem": 12496579,
"mem.free": 8820382,
"processors": 8,
"instance.uptime": 81630586,
"uptime": 81641671,
"systemload.average": 0.05,
"heap.committed": 12340992,
"heap.init": 12582912,
"heap.used": 3520609,
"heap": 12340992,
"nonheap.committed": 159528,
"nonheap.init": 2496,
"nonheap.used": 155587,
"nonheap": 0,
"threads.peak": 342,
"threads.daemon": 234,
"threads.totalStarted": 3086,
"threads": 287,
"classes": 14031,
"classes.loaded": 14031,
"classes.unloaded": 0,
"gc.parnew.count": 522,
"gc.parnew.time": 9718,
"gc.concurrentmarksweep.count": 2,
"gc.concurrentmarksweep.time": 605,
"httpsessions.max": -1,
"httpsessions.active": 0,
"datasource.primary.active": 1,
"datasource.primary.usage": 0.033333335,
"datasource.slave.active": 0,
"datasource.slave.usage": 0.0,
}
원격 SSH는 Spring Boot 2.0에서는 제거되었다. 하지만 기능이 매우 괜찮아서 아쉽기도 하다.
별도로 유저, 비밀번호 설정을 하지 않으면, 최초 시작 시 로그에서 무작위로 생성되는 비밀번호를 확인해야 한다.
Using default security password: 6a37007e-01c9-465c-b9d8-95a7c88afd38
정상적으로 접속되면 help를 입력하여 확인하자.
$ ssh -p 18080 user@localhost
Password authentication
Password:
:: Spring Boot :: (v1.5.8.RELEASE) on localhost
> help
Try one of these commands with the -h or --help switch:
NAME DESCRIPTION
autoconfig Display auto configuration report from ApplicationContext
beans Display beans in ApplicationContext
cron manages the cron plugin
dashboard a monitoring dashboard
egrep search file(s) for lines that match a pattern
endpoint Invoke actuator endpoints
env display the term env
filter a filter for a stream of map
java various java language commands
jul java.util.logging commands
jvm JVM informations
less opposite of more
mail interact with emails
man format and display the on-line manual pages
metrics Display metrics provided by Spring Boot
shell shell related command
sleep sleep for some time
sort sort a map
system vm system properties commands
thread JVM thread commands
help provides basic help
repl list the repl or change the current repl
>
dashboard를 입력하면 Thread, Memory 현황을 실시간으로 확인할 수 있다.
Java에서 제공하는 jmap, jstat등을 이용한다면 기본적인 감시는 가능하겠지만, Spring Boot Actuator는 기능이 확장되어 좀 더 쉽게 상황을 진단할 수 있다.
프로젝트 일정에 너무 치이다 보면 실수를 인지하지 못한 상태로 운영단계에 들어가게 되는데, 반드시 검수 일정을 확보하여 Spring Boot Actuator 기능을 기반으로 실수를 사전에 감시하여 제거했으면 한다.
참고 사이트
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready