Table of Contents
[스프링인액션] 스프링 액추에이터 사용
스프링인액션 16장을 읽고 스프링 액추에이터 사용법에 대해 정리하였다.
- 스프링 액추에이터 활성화
- 액추에이터 엔드포인트
- 액추에이터 커스터마이징
- 액추에이터 보안 처리
에 대해 작성하였다.
액추에이터란?
엑추에이터는 스프링 부트 어플리케이션의 모니터링이나 메트릭(metric)과 같은 기능을 HTTP와 JMX 엔드포인트를 통해 제공한다. 즉, 실행 중인 어플리케이션의 내부를 볼 수 있고 어플리케이션의 작동 방법을 제어할 수 있다.
액추에이터가 노출하는 엔드포인트를 사용하여 얻을 수 있는 정보는 다음과 같다.
- 어플리케이션 환경에서 사용할 수 있는 구성 속성
- 패키지의 로깅 레벨
- 어플리케이션이 사용 중인 메모리
- 엔드포인트가 받은 요청 횟수
- 어플리케이션 건강 상태 정보
액추에이터 활성화
액추에이터를 활성화하려면 액추에이터의 스타터 의존성을 추가해야 한다.
1implementation 'org.springframework.boot:spring-boot-starter-actuator'
액추에이터를 활성화하면 어플리케이션에서 액추에이터 엔드포인트를 사용할 수 있다.
액추에이터 엔드포인트 정보는 아래의 링크에서 확인할 수 있다.
https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/html/production-ready-endpoints.html
액추에이터 설정
액추에이터 기본 경로 구성
모든 엔드포인트 경로에는 /actuator가 앞에 붙는데, 기본 경로를 변경한 예제이다.
1management:
2 endpoints:
3 web:
4 base-path: /management
기본 경로는 application.yml 파일의 management.endpoint.web.base-path
속성에서 변경할 수 있다. 이후 건강 상태 정보를 얻고 싶다면 /management/health GET 요청을 보내면 된다.
액추에이터 엔드포인트 활성화/비활성화
엔드포인트 노출 여부를 제어하는 코드이다.
1management:
2 endpoints:
3 web:
4 exposure:
5 include: health, info, beans, conditions
application.yml 파일의 management.endpoint.web.exclude
와 management.endpoint.web.include
속성을 통해 노출을 원하는 엔드포인트를 지정할 수 있다. exclude는 제외할 엔드포인트, include는 노출할 엔드포인트를 의미한다.
액추에이터 엔드포인트 사용
엑추에이터의 HTTP 엔드포인트를 제공하는데, REST API 혹은 curl 명령어를 통해 엔드포인트를 사용할 수 있다. /actuator GET 요청을 하면 엔드포인트의 HATEOAS 링크를 응답으로 받을 수 있다.
어플리케이션 기본 정보 받기
/info 엔드포인트를 통해 어플리케이션의 기본 정보를 알 수 있다.
1info:
2 contact:
3 email: email@email.com
4 phone: 010-0000-0000
/info 엔드포인트가 반환하는 정보를 설정하는 방법은 여러가지가 있는데, 위에서 설정한 바와 같이 application.yml 에서 info로 시작하는 구성 속성을 설정하는 방법도 있다.
/health 엔드포인트를 통해 어플리케이션의 건강 상태 정보를 얻을 수 있다.
1$ curl localhost:8080/actuator/health
2{"status": "UP"}
기본적으로 종합된 건강 상태 정보를 가지는 JSON 응답을 받는다.
건강 상태를 나타내는 각 지표
- 모든 건강 지표가 UP → 건강 상태 UP
- 하나의 건강 지표가 DOWN → 건강 상태 DOWN
- 하나의 건강 지표가 OUT_OF_SERVICE → 건강 상태 OUT_OF_SERVICE
- 하나의 건강 지표가 UNKNOWN → 종합 건강 상태에서 고려되지 않음
건강 지표를 자세히 보려면 다음과 같이 구성한다.
1management:
2 endpoint:
3 health:
4 show-details: always
management.endpoint.health.show-details
속성을 always로 설정하면 모든 건강 지표를 자세히 볼 수 있다. 기본값은 never이고, when-authorized로 설정 시 클라이언트가 인가된 경우에 한해 상세한 내역을 보여준다.
구성 상세 정보 보기
빈 연결 정보를 얻으려면 /beans 엔드포인트를 사용한다.
1{
2 "contexts" : {
3 "application-1": {
4 "beans": {
5 "ingredientsController": {
6 "aliases": [],
7 "scope": "singleton",
8 "type": "tacos.ingredients.IngredientsController",
9 "resource": "file [...ingredients/IngredientsController.class]",
10 "dependencies": ["ingredientRepository"]
11 },
12 },
13 "parentId": null
14 }
15 }
16}
해당 어플리케이션 컨텍스트에 있는 모든 빈의 상세 정보를 포함한다.
자동-구성 내역을 알기 위해서는 /conditions 엔드포인트를 사용한다.
1{
2 "contexts" : {
3 "application-1": {
4 "positiveMatches": {
5 "MongoDataAutoConfiguration#mongoTemplate": [
6 {
7 "condition": "OnBeanCondition",
8 "message": "..."
9 }
10 ],
11 },
12 "negativeMatches": {
13 "DispatcherServletAutoConfiguration": {
14 "notMatched": [],
15 "matched": []
16 },
17 },
18 "unconditionalClasses": []
19 }
20 }
21}
positiveMatches에서는 자동 구성이 된 빈들을 보여주고, negativeMatches에서는 자동 구성이 어떠한 이유때문에 실패했다는 것을 보여준다. unconditionalClassess는 조건 없이 자동 구성된 빈을 보여준다.
환경 속성과 구성 속성을 보기 위해서는 /env 엔드포인트를 사용한다.
1{
2 "activeProfiles": [
3 "development"
4 ],
5 "propertySources": [
6 {
7 "name": "systemEnvironment",
8 "properties": {
9 "PATH": {
10 "value": "/usr/bin:/bin:/usr/sbin:/sbin",
11 "origin": "System Environment Property \"PATH\""
12 }
13 }
14 },
15 {
16 "name": "applicationConfig: [classpath:/application.yml]",
17 "properties": {
18 "spring.application.name": {
19 "value": "ingredient-service",
20 "origin": "class path resource [application.yml]:3:11"
21 },
22 "server.port": {
23 "value": 8081,
24 "origin": "class path resource [application.yml]:9:9"
25 },
26 }
27 },
28 ]
29}
activeProfiles 필드를 통해 활성화된 프로파일을 알 수 있고, propertySources 필드에서는 스프링 어플리케이션 환경의 속성 근원들을 포함한다.
모든 HTTP 요청 핸들러 내역을 확인하려면 /mappings 엔드포인트를 사용한다.
1{
2 "contexts" : {
3 "application-1": {
4 "mappings": {
5 "dispatcherHandlers": {
6 "webHandler": [
7 {
8 "predicate": "{[/ingredients],methods=[GET]}",
9 "handler": "...",
10 "details": {
11 "handlerMethod": {
12 "className": "tacos.ingredients.IngredientsController",
13 "name": "allIngredients",
14 "descriptor": "()Lreactor/core/publisher/Flux;"
15 },
16 "handlerFunction": null,
17 "requestMappingConditions": {
18 "consumes": [],
19 "headers": [],
20 "methods": ["GET"],
21 "params": [],
22 "patterns": ["/ingredients"],
23 "produces": []
24 }
25 }
26 },
27 ]
28 }
29 },
30 "parentId": "application-1"
31 }
32 }
33}
특정 API의 요청이 어떤 메서드에서 처리되는지 확인할 수 있다.
로깅 레벨을 관리하려면 /loggers 엔드포인트를 사용한다.
1{
2 "levels": [ "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" ],
3 "loggers": {
4 "ROOT": {
5 "configuredLevel": "INFO", "effectiveLevel": "INFO"
6 },
7 "org.springframework.web": {
8 "configuredLevel": null, "effectiveLevel": "INFO"
9 }
10 }
11}
응답의 맨 앞에는 모든 로깅 레벨 내역이 노출되고, 각 패키지에 대한 로깅 상세 내역을 볼 수 있다. configuredLevel 속성은 명시적으로 구성된 로깅 레벨이고, effectiveLevel 속성은 상속받을 수 있는 유효 로깅 레벨이다.
어플리케이션 모니터링
메모리나 스레드 문제를 찾는 gzip 형태의 HPROF 힙 덤프 파일을 다운로드하려면 /heapdump 엔드포인트를 사용한다.
어플리케이션이 처리한 최근 100개의 요청을 알기 위해서는 /httptrace 엔드포인트를 사용한다.
1{
2 "traces": [
3 {
4 "timestamp": "2018-06-04T23:41:24.494Z",
5 "principal": null,
6 "session": null,
7 "request": {
8 "method": "GET",
9 "uri": "http://localhost:8081/ingredients",
10 "headers": {
11 "Host": ["localhost:8081"],
12 "User-Agent": ["curl/7.54.0"],
13 "Accept": ["*/*"]
14 },
15 "remoteAddress": null
16 },
17 "response": {
18 "status": 200,
19 "headers": {
20 "Content-Type": ["application/json;charset=UTF-8"]
21 }
22 },
23 "timeTaken": 4
24 },
25 ]
26}
스프링 부트 Admin에서는 이런 HTTP 추적 정보를 실시간으로 캡쳐하여 그래프로 보여준다.
현재 실행 중인 스레드에 관한 스냅샷을 확인하려면 /threaddump 엔드포인트를 사용한다.
1{
2 "threadName": "reactor-http-nio-8",
3 "threadId": 338,
4 "blockedTime": -1,
5 "blockedCount": 0,
6 //...
7 "stackTrace": [
8 {
9 "methodName": "kevent0",
10 "fileName": "KQueueArrayWrapper.java",
11 "lineNumber": -2,
12 "className": "sun.nio.ch.KQueueArrayWrapper",
13 "nativeMethod": true
14 },
15 ],
16 "lockedMonitors": [],
17 "lockedSynchronizers": [],
18 "lockInfo": null
19}
요청 시점의 스레드 활동에 대한 스냅샷을 확인할 수 있다. 스레드 덤프에는 스레드의 블로킹과 록킹 상태의 관련 상세 정보와 스택 기록 등을 보여준다.
메모리, 프로세스, 가비지 컬렉션, HTTP 요청 관련 메트릭을 확인하려면 /metrics 엔드포인트를 사용한다.
/metrics/{메트릭 종류} 형식으로 사용한다면 해당 종류의 메트릭에 대한 상세한 정보를 알 수 있다.
1$ curl localhost:8081/actuator/metrics/http.server.requests?tag=status:404&tag=uri:/**
2{
3 "name": "http.server.requests",
4 "measurements": [
5 { "statistic": "COUNT", "value": 30 },
6 { "statistic": "TOTAL_TIME", "value": 0.519791548 },
7 { "statistic": "MAX", "value": 0 },
8 ],
9 "availableTags": [
10 { "tag": "exception", "values": [ "ResponseStatusException" ] },
11 { "tag": "method", "values": [ "GET" ] }
12 ]
13}
tag에 stauts, url 태그 이름과 값을 지정하면 HTTP 404 응답을 받은 /** 경로에 대한 메트릭을 볼 수 있다.
액추에이터 커스터마이징
어플리케이션의 특정 요구를 충족하기 위해 커스터마이징을 할 수 있다.
/info 엔드포인트 커스터마이징
InfoContributor라는 인터페이스를 사용하여 /info 엔드포인트 응답에 커스텀 데이터를 추가할 수 있다.
1@Component
2public class TacoCountInfoContributor implements InfoContributor {
3 private TacoRepository tacoRepo;
4
5 public TacoCountInfoContributor(TacoRepository tacoRepo) {
6 this.tacoRepo = tacoRepo;
7 }
8
9 @Override
10 public void contribute(Builder builder) {
11 long tacoCount = tacoRepo.count();
12 Map<String, Object> tacoMap = new HashMap<>();
13 tacoMap.put("count", tacoCount);
14 builder.withDetail("taco-stats", tacoMap);
15 }
16}
InfoContributor 인터페이스 구현체인 contribute() 메서드 내부에 인자로 받은 Builder 객체의 withDetails() 를 호출하여 /info 엔드포인트에 정보를 추가할 수 있다.
빌드 정보를 주입하려면 build.gradle 파일에 다음과 같이 추가한다.
1springBoot {
2 buildInfo()
3}
Git 커밋 정보를 주입하려면 build.gradle 파일에 다음과 같이 추가한다.
1plugins {
2 id "com.gorylenko.gradle-git-properties" version "1.4.17"
3}
이 정보는 프로젝트가 빌드된 시점의 간단한 코드 상태만을 나타낸다.
자세한 정보를 얻기 위해서는 management.info.git.mode
속성을 full로 설정하면 된다. - application.yml
건강 지표 커스터마이징
HealthIndicator 인터페이스를 사용하여 스프링 부트에서 지원하지 않는 외부 시스템의 건강 상태 정보를 제공할 수 있다.
1@Component
2public class WackoHealthIndicator implements HealthIndicator {
3 @Override
4 public Health health() {
5 int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
6 if (hour > 12) {
7 return Health.outOfService().withDetail("reason", "out of service")
8 .withDetail("hour", hour).build();
9 }
10
11 if (Math.random() < 0.1) {
12 return Health.outOfService().withDetail("reason", "break 10% of the time")
13 .build();
14 }
15
16 return Health.up().withDetail("reason", "good").build();
17 }
18}
외부 시스템에 원격 호출을 한 후 받은 응답 기준으로 건강 상태를 결정하는 방식으로 유용하게 사용할 수 있다.
커스텀 메트릭 등록
Micrometer의 MeterRegistry를 사용하여 스프링 부트 어플리케이션에서 메트릭을 발행할 수 있다. 메트릭을 발행할 때는 필요한 곳에 MeterRegistry를 주입하면 된다.
1@Component
2public class TacoMetrics extends AbstractRepositoryEventListener<Taco> {
3 private MeterRegistry meterRegistry;
4 public TacoMetrics(MeterRegistry meterRegistry) {
5 this.meterRegistry = meterRegistry;
6 }
7
8 @Override
9 protected void onAfterCreate(Taco taco) {
10 List<Ingredient> ingredients = taco.getIngredients();
11 for (Ingredient ingredient : ingredients) {
12 meterRegistry.counter("tacocloud", "ingredient", ingredient.getId()).increment();
13 }
14 }
15}
스프링 데이터 클래스인 AbstractRepositoryEventListener의 서브 클래스를 생성하고 새로운 객체가 저장될 때마다 호출되도록 onAfterCreate() 메서드를 오버라이딩한다.
커스텀 엔드포인트 생성
액추에이터 앤드포인트를 지정하려면 @EndPoint 어노테이션을 사용한다. 또한 @GetMapping, @PostMapping 대신 @ReadOperation, @WriteOperation, @DeleteOperation 어노테이션이 지정된 메서드로 정의된다.
1@Component
2@Endpoint(id="notes", enableByDefault=true)
3public class NotesEndpoint {
4
5 private List<Note> notes = new ArrayList<>();
6
7 @ReadOperation
8 public List<Note> notes() {
9 return notes;
10 }
11
12 @WriteOperation
13 public List<Note> addNote(String text) {
14 notes.add(new Note(text));
15 return notes;
16 }
17
18 @DeleteOperation
19 public List<Note> deleteNote(int index) {
20 if (index < notes.size()) {
21 notes.remove(index);
22 }
23 return notes;
24 }
25
26 @RequiredArgsConstructor
27 private class Note {
28 @Getter
29 private Date time = new Date();
30
31 @Getter
32 private final String text;
33 }
34}
쓰기 오퍼레이션으로 데이터를 제출하고, 읽기 오퍼레이션으로 데이터를 읽을 수 있으며, 삭제 오퍼레이션으로 데이터를 삭제할 수 있다. enableByDefault=true로 지정했기 때문에 해당 엔드포인트를 구성 속성에서 활성화하지 않아도 된다.
엔드포인트를 HTTP 엔드포인트로만 제한하고 싶다면 @WebEndPoint 어노테이션을 사용한다.
1@Component
2@WebEndpoint(id="notes", enableByDefault=true)
3public class NotesEndpoint {
4 ...
5}
액추에이터 보안 처리
권한을 갖는 클라이언트만 엔드포인트를 사용하게 하기 위해서는 스프링 시큐리티를 사용해서 액추에이터의 보안을 처리해야 한다.
액추에이터 엔드포인트의 공통 기본 경로인 /actuator 경로에 대한 보안을 추가하려면 다음과 같이 설정한다.
1@Override
2protected void configure(HttpSecurity http) throws Exception {
3 http.authorizeRequests()
4 .antMatchers("/actuator/**").hasRole("ADMIN")
5 .and()
6 .httpBasic();
7}
ROLE_ADMIN 권한을 가지는 사용자만 액추에이터 엔드포인트를 사용할 수 있다.
엔드포인트 경로를 구성 속성에서 변경했다면 EndpointRequest 클래스를 통해 변경된 경로를 알 수 있다.
1@Override
2protected void configure(HttpSecurity http) throws Exception {
3 http.requestMatcher(EndpointRequest.toAnyEndpoint())
4 .authorizeRequests()
5 .anyRequest().hasRole("ADMIN")
6 .and()
7 .httpBasic();
8}
EndpointRequest.toAnyEndpoint()
메서드는 액추에이터 엔드포인트와 일치하는 요청 경로 모음을 반환한다.
일부 엔드포인트를 제외하고 싶으면 excluding() 메서드를 사용한다.
1@Override
2protected void configure(HttpSecurity http) throws Exception {
3 http.requestMatcher(EndpointRequest.toAnyEndpoint().excluding("health", "info"))
4 .authorizeRequests()
5 .anyRequest().hasRole("ADMIN")
6 .and()
7 .httpBasic();
8}
일부 액추에이터 엔드포인트에만 보안을 적용하고 싶으면 to() 메서드를 호출한다.
1@Override
2protected void configure(HttpSecurity http) throws Exception {
3 http.requestMatcher(EndpointRequest.to("health", "info"))
4 .authorizeRequests()
5 .anyRequest().hasRole("ADMIN")
6 .and()
7 .httpBasic();
8}
Posts in this Series
- [스프링인액션] JMX로 스프링 모니터링
- [스프링인액션] 스프링 관리
- [스프링인액션] 스프링 액추에이터 사용
- [스프링인액션] 실패와 지연 처리
- [스프링인액션] 클라우드 구성 관리
- [스프링인액션] 마이크로서비스 이해
- [스프링인액션] 리액티브 데이터 퍼시스턴스
- [스프링인액션] 리액티브 API 개발
- [스프링인액션] 리액터 개요
- [스프링인액션] 스프링 통합 플로우 사용
- [스프링인액션] 비동기 메시지 전송하기 - Kafka
- [스프링인액션] 비동기 메시지 전송하기 - RabbitMQ
- [스프링인액션] 비동기 메시지 전송하기 - JMS
- [스프링인액션] REST API 사용하기
- [스프링인액션] REST API 생성하기
- [스프링인액션] 구성 속성 사용
- [스프링인액션] 스프링 시큐리티
- [스프링인액션] 데이터로 작업하기
- [스프링인액션] 스프링 초기 설정