Java

[Spring Boot Actuator] 기초, 엔드포인트 설정

ride-dev 2024. 3. 1. 15:49

Spring Boot Actuator는 Spring 모듈 중 하나입니다.

상태 점검, 메트릭 수집, HTTP 추적 등과 같은 프로덕션에 사용 가능한 기능을 제공하여,

Spring Boot 애플리케이션을 모니터링하고 관리하는 데 도움을 줍니다.

JMX 또는 HTTP 엔드포인트를 통해 액세스할 수 있고,

외부 애플리케이션 모니터링 시스템과 통합할 수도 있습니다.

 

프로젝트 생성

먼저 Spring Web, Spring Boot Actuator 의존성을 추가한 프로젝트를 생성합니다.

https://start.spring.io/

엔드포인트

이후 프로젝트를 가동시키면,

http://localhost:8080/actuator

에서 아래와 같은 데이터를 확인할 수 있습니다.

http://localhost:8080/actuator/health

에서 프로젝트가 정상적으로 가동중인지 확인할 수도 있습니다.

UP은 정상적인 상태를, DOWN은 비정상적인 상태를 나타냅니다.

이 외에도 다양한 엔드포인트가 존재하며,

공식 문서에서 엔드포인트 목록을 확인할 수 있습니다.

https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints

 

Production-ready Features

You can enable recording of HTTP exchanges by providing a bean of type HttpExchangeRepository in your application’s configuration. For convenience, Spring Boot offers InMemoryHttpExchangeRepository, which, by default, stores the last 100 request-response

docs.spring.io

엔드포인트 활성화

엔드포인트는 기본적으로 JMX를 통해,

(HTTP에선 기본적으로 health와 info만 활성화됩니다)

shutdown 엔드포인트를 제외하고 노출되며,

(shutdown은 jar에서만 구동하며, 애플리케이션을 종료하는 엔드포인트입니다)

 

application.properties설정을 통해 여러 엔드포인트를 활성화할 수 있습니다.

manage.endpoint.아이디.enabled=true 와 같은 형식으로 활성화합니다.

management.endpoint.shutdown.enabled=true

 

그밖에도 application.properties에서 여러 설정을 할 수 있습니다.

특정(혹은 모든)엔드포인트를 포함, 제외시킬 수 있습니다.

HTTP를 통한 엔드포인트 노출

management.endpoints.web.exposure.include=health,info
management.endpoints.web.exposure.exclude=

특정 엔드포인트를 포함시키기 위해 엔드포인트1, 엔드포인트2 와 같이 쉼표(,)를 사용합니다.

JMX를 통한 엔드포인트 노출

management.endpoints.jmx.exposure.include=*
management.endpoints.jmx.exposure.exclude=

모든 엔드포인트를 포함시키기 위해 위와 같이 별표(*)를 사용합니다.

HTTP에서도 모든 엔드포인트를 확인하기 위해 아래와 같이 application.properties를 설정하고,

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=

애플리케이션을 재가동시켜보면,

 

http://localhost:8080/actuator

에서 아래와 같은 데이터를 확인할 수 있습니다.

helath 엔드포인트

또한, 자동설정된 health 정보를 사용할 수 있습니다.

management.health.defaults.enabled=true

defaults 대신 db, cassandra, redis, ping 등 특정 health정보를 확인할 수 있습니다.

management.endpoint.health.show-details=always

show-details를 통해 아래와 같이 더 자세한 정보를 얻을 수도 있습니다.

show-details를 통해 아래와 같이 더 자세한 정보를 얻을 수도 있습니다.

 

MySQL를 연결하고 health를 확인해보겠습니다.

의존성을 설정해줍니다.

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'

편의상 docker를 활용하겠습니다.

터미널에 아래 명령어를 입력합니다.

docker run --detach --env MYSQL_ROOT_PASSWORD=mypassword --env MYSQL_USER=ride --env MYSQL_PASSWORD=mypassword --env MYSQL_DATABASE=actuator --name mysql --publish 3306:3306 mysql:latest --port 3306

db 이름은 actuator,

username은 ride

password는 mypassword로 설정했습니다.

위의 정보를 토대로 application.properties에 data source 정보를 선언하겠습니다.

spring.datasource.url=jdbc:mysql://localhost:3306/actuator
spring.datasource.username=ride
spring.datasource.password=mypassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
 

인텔리제이를 사용한다면 아래와 같이 확인할 수도 있습니다.

db에 문제가 생기면 health가 아래와 같이 변경됩니다.

사용자 정의 HealthIndicators

AbstractHealthIndicator를 확장하거나,

HealthIndicator 인터페이스를 구현하여,

health 표시기를 사용자 정의화할 수도 있습니다.

 

withDetail를 사용하면, status 외에도 추가적인 키-값 정보를 추가할 수 있습니다.

아래는 사용자 정의 HealthIndicator를 생성하는 간단한 예제입니다.

extends AbstractHealthIndicator

package com.ride.actuactordemo.healthIndicator.ImplementsHealthIndicator;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

import java.util.concurrent.ThreadLocalRandom;

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        // 이 코드는 대부분의 경우 애플리케이션을 'UP' 상태로 보고하지만,
        // 10% 확률로 'DOWN' 상태를 보고하여 임의의 실패를 시뮬레이션합니다.
        double chance = ThreadLocalRandom.current().nextDouble(); // 0.0과 1.0 사이의 랜덤 값 생성
        Health.Builder status = Health.up(); // 기본적으로 상태를 'UP'으로 설정
        if (chance > 0.9) { // 10% 확률로 상태를 'DOWN'으로 변경
            status = Health.down();
        }
        return status.build(); // 최종 헬스 상태 반환
    }
}

implements HealthIndecator

package com.ride.actuactordemo.healthIndicator.ImplementsHealthIndicator;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

import java.util.concurrent.ThreadLocalRandom;

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        // 이 코드는 대부분의 경우 애플리케이션을 'UP' 상태로 보고하지만,
        // 10% 확률로 'DOWN' 상태를 보고하여 임의의 실패를 시뮬레이션합니다.
        double chance = ThreadLocalRandom.current().nextDouble(); // 0.0과 1.0 사이의 랜덤 값 생성
        Health.Builder status = Health.up(); // 기본적으로 상태를 'UP'으로 설정
        if (chance > 0.9) { // 10% 확률로 상태를 'DOWN'으로 변경
            status = Health.down();
        }
        return status // 최종 헬스 상태 반환
                .withDetail("chance", chance)
                .withDetail("strategy", "thread-local")
                .build();
    }
}

implements ReactiveHealthIndecator

ReactiveHealthIndecator는 비동기적으로 외부 HTTP 요청을 수행하고, 응답에 따라 애플리케이션의 상태를 확인합니다.

Mono를 구현하기 위해 의존성을 추가합니다.

// https://mvnrepository.com/artifact/io.projectreactor/reactor-core
implementation group: 'io.projectreactor', name: 'reactor-core', version: '3.6.3'
package com.ride.actuactordemo.healthIndicator.reactiveHealthIndicator;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {

    // 이 특정 서비스나 애플리케이션에서 호출되는 외부 서비스의 헬스 체크를 비동기적으로 수행하고,
    // 모든 것이 정상이면 'UP' 상태를,
    // 예외가 발생하면 해당 예외와 함께 'DOWN' 상태를 반환합니다.
    // 실제 다운스트림 서비스의 상태 확인을 위해 WebClient, RestClient 와 같은 비동기 클라이언트를 사용할 수 있습니다.

    @Override
    public Mono<Health> health() {
        return checkDownstreamServiceHealth().onErrorResume(
                ex -> Mono.just(new Health.Builder().down(ex).build()) // 에러 발생 시 'DOWN' 상태로 설정
        );
    }

    private Mono<Health> checkDownstreamServiceHealth() {
        // we could use WebClient to check health reactively
        return Mono.just(new Health.Builder().up().build()); // 기본적으로 'UP' 상태 반환
    }
}

RestClient를 적용해봤습니다.

package com.ride.actuactordemo.healthIndicator.reactiveHealthIndicator;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import reactor.core.publisher.Mono;

@Component
public class RestClientServiceHealthIndicator implements ReactiveHealthIndicator {

    private final RestClient restClient;

    public RestClientServiceHealthIndicator() {
        this.restClient = RestClient.create("https://httpbin.org");
    }

    @Override
    public Mono<Health> health() {
        return Mono.fromSupplier(() -> restClient.get()
                        .uri("/status/200") // 상태 체크를 위한 URI
                        .retrieve() // HTTP GET 요청 실행
                        .toBodilessEntity()) // 응답 본문 없이 엔티티만 반환
                .map(responseEntity -> {
                    // 응답 상태 코드가 200 OK인 경우, 서비스 상태를 'UP'으로 설정
                    if (responseEntity.getStatusCode() == HttpStatus.OK) {
                        return Health.up().withDetail("status", "서비스가 정상입니다.").build();
                    } else {
                        // 그 외의 경우, 서비스 상태를 'DOWN'으로 설정
                        return Health.down().withDetail("status", "서비스에 문제가 있습니다.")
                                .withDetail("statusCode", responseEntity.getStatusCodeValue()).build();
                    }
                })
                .onErrorResume(e -> Mono.just(Health.down((Exception) e).build())); // 오류 처리
    }
}

 

@Component를 통해 빈으로 관리되는 HealthIndicator들은 아래와 같이 확인할 수 있습니다.

HealthIndicator 이름 지정하기

HealthIndicator는 기본적으로 Class 이름에서 HealthIndicator앞에 있는 문자열로 지정됩니다.

Class를,

CustomHealthIndicator 라고 만들었으면 custom으로,

ReandomHealthIndicator 라고 만들었으면 random으로  확인할 수 있습니다.

@Component("httpbin")
@ConditionalOnEnabledHealthIndicator("httpbin")
public class RestClientServiceHealthIndicator implements ReactiveHealthIndicator {

 Componet 에 위와 같이 사용하여 이름을 지정할 수도 있습니다.

ConditionalOnEnabledHealthIndicator 설정을 통해 application.properties에서 활성화 여부를 설정할 수 있습니다.

management.health.httpbin.enabled=true

물론, application.properties의 auto-configuration을 활용하지 않고, 수동으로 설정할 수도 있습니다.

@ConditionalOnEnabledHealthIndicator("management.health.downstreamService.enabled=false")
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {

아래는 그 결과입니다.

Health Group

application.properties에서 Indicator를 그룹으로 묶고,

management.endpoint.health.group.customHealth.include=diskSpace,ping

 구성관리를 일괄적으로 적용할 수 있습니다.

management.endpoint.health.group.customHealth.show-components=always

인가된 사용자에게만 세부 정보를 표시하도록 할 수 있습니다.

 

metrics 엔드포인트

metrics에는 추적할 수 있는 모든 지표를 확인할 수 있습니다.

http://localhost:8080/actuator/metrics

세부 지표를 확인하려면

http://localhost:8080/actuator/metrics/세부지표

으로 확인합니다.

logger 엔드포인트

애플리케이션 런타임 logger를 확인할 수 있습니다.

위 예제에서 POST 요청을 통해 특정 logger의 수준을 변경할 수도 있습니다.

http://localhost:8080/actuator/loggers/ROOT

로그 수준을 기본값으로 재설정하려면,

null을 전달합니다.

info 엔드포인트

애플리케이션에 대한 정보를 표시합니다.

http://localhost:8080/actuator/info

 

# INFO ENDPOINT CONFIGURATION
management.info.git.mode=simple
management.info.java.enabled=true

Spring Security로 Actuator 엔드포인트 보호하기

security 의존성을 추가합니다.

testImplementation 'org.springframework.security:spring-security-test'

application.proeprties에 사용자를 생성합니다.

spring.security.user.name=actuator
spring.security.user.password=actuator
spring.security.user.roles=ACTUATOR_ADMIN

SecurityConfiguration class 를 생성합니다.

Actuator에서 보여주는 모니터링 정보는 일반 사용자에게 노출하지 않는 것이 좋습니다.

package com.ride.actuactordemo.security;

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity // Spring Security 활성화
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http
    ) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .securityMatcher(EndpointRequest.toAnyEndpoint())
                .authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("ACTUATOR_ADMIN"))
                .httpBasic(withDefaults());
        return http.build();
    }
}

actuator의 활용방안은 다양합니다.

다음 게시글에서는 Prometheus 와 Grafana를 활용해보도록 하겠습니다.

참조

https://www.callicoder.com/spring-boot-actuator/

https://www.baeldung.com/spring-boot-actuators

https://www.baeldung.com/spring-boot-health-indicators

https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.monitoring

728x90