토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 부트 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차


8.2. Endpoints

액추에이터 엔드포인트를 사용하면 쉽게 애플리케이션을 모니터링하고 상호 작용할 수 있다. 스프링 부트는 여러 가지 엔드포인트를 내장하고 있으며, 자체 엔드포인트도 추가할 수 있다. 예를 들어 health 엔드포인트는 기본적인 애플리케이션의 상태 정보를 제공한다.

각 엔드포인트들은 개별적으로 활성화하거나 비활성화할 수 있으며, HTTP나 JMX를 통해 노출(원격에서도 접근 가능)할 수 있다. 엔드포인트는 활성화와 노출을 동시에 해줬을 때 사용 가능한 것으로 간주한다. 이렇게 사용이 가능할 때에만 내장 엔드포인트를 자동 설정한다. 대부분의 애플리케이션은 HTTP를 통한 노출을 선택하고 있으며, 엔드포인트의 ID에 /actuator를 프리픽스로 달아서 URL에 매핑한다. 예를 들어 health 엔드포인트는 기본적으로 /actuator/health에 매핑된다.

액추에이터의 엔드포인트와 요청, 응답 포맷을 자세히 알고 싶다면 별도 API 문서를 참고해라 (HTML, PDF).

아래 엔드포인트들은 사용하는 기술에 상관 없이 지원한다:

ID Description
auditevents 현재 애플리케이션에 대한 감사audit 이벤트들의 정보를 노출한다. AuditEventRepository 빈이 있어야 한다.
beans 애플리케이션에 있는 전체 스프링 빈들을 보여준다.
caches 사용 가능한 캐시들을 노출한다.
conditions 설정과 자동 설정 클래스들에서 평가한 조건들과 함께, 그 조건이 매칭되거나 매칭되지 않은 이유를 보여준다.
configprops 모든 @ConfigurationProperties에 있는 정보들을 모아서 보여준다.
env 스프링의 ConfigurableEnvironment에 있는 프로퍼티들을 노출한다.
flyway 적용시킨 모든 Flyway 데이터베이스 마이그레이션들을 보여준다. Flyway 빈이 하나 이상 필요하다.
health 애플리케이션의 상태 정보를 보여준다.
httptrace HTTP trace 정보를 보여준다 (기본적으로는 HTTP request-response exchange를 마지막 100개까지 표기). HttpTraceRepository 빈이 있어야 한다.
info 애플리케이션 정보를 보여주며, 애플리케이션 정보는 임의로 추가할 수 있다.
integrationgraph Spring Integration graph를 보여준다. spring-integration-core 의존성이 필요하다.
loggers 애플리케이션에 있는 로거 설정을 조회하고 변경한다.
liquibase 적용시킨 모든 Liquibase 데이터베이스 마이그레이션들을 보여준다. Liquibase 빈이 하나 이상 필요하다.
metrics 현재 애플리케이션의 ‘메트릭’ 정보를 보여준다.
mappings 모든 @RequestMapping path 정보를 모아서 보여준다.
quartz Quartz 스케줄러 job들에 관한 정보를 보여준다.
scheduledtasks 애플리케이션에 있는 스케줄링된 테스크들을 보여준다.
sessions 스프링 세션 기반 세션 저장소에서 사용자 세션을 검색하고 삭제할 수 있다. 스프링 세션을 사용하는 서블릿 웹 애플리케이션이 필요하다.
shutdown 애플리케이션을 graceful하게 종료시킬 수 있다. 기본적으로는 비활성화한다.
startup ApplicationStartup으로 수집한 startup 단계 데이터를 보여준다. SpringApplicationBufferingApplicationStartup을 설정해줘야 한다.
threaddump 스레드 덤프를 수행한다.

웹 애플리케이션에선 (스프링 MVC, 스프링 웹플럭스, Jersey) 아래와 같은 엔드포인트들도 이용할 수 있다:

ID Description
heapdump hprof 힙 덤프 파일을 반환한다. HotSpot JVM이 필요하다.
jolokia HTTP를 통해 JMX 빈을 노출한다 (클래스패스에 Jolokia가 있을 땐 웹플럭스에선 사용할 수 없다). jolokia-core 의존성이 필요하다.
logfile 로그 파일에 있는 내용들을 반환한다 (logging.file.name이나 logging.file.path 프로퍼티를 설정해줬다면). HTTP Range 헤더를 사용해서 로그 파일에 있는 내용 일부만 조회할 수도 있다.
prometheus 메트릭을 프로메테우스 서버에서 스크랩할 수 있는 포맷으로 노출한다. micrometer-registry-prometheus 의존성이 필요하다.

8.2.1. Enabling Endpoints

기본적으로 shutdown을 제외한 모든 엔드포인트를 활성화한다. 활성화할 엔드포인트를 설정해주려면 management.endpoint.<id>.enabled 프로퍼티를 사용해라. 아래 예시에선 shutdown 엔드포인트를 활성화하고 있다:

properties yaml
management.endpoint.shutdown.enabled=true
management:
  endpoint:
    shutdown:
      enabled: true

활성화를 기본으로 두기보단 옵트인 방식으로 엔드포인트를 활성화하고 싶다면 management.endpoints.enabled-by-default 프로퍼티를 false로 설정하고, 개별 엔드포인트 enabled 프로퍼티를 통해 원하는 엔드포인트를 다시 활성화해라. 아래 예시에선 info 엔드포인트를 활성화하고, 그외 다른 엔드포인트는 전부 비활성화한다:

properties yaml
management.endpoints.enabled-by-default=false
management.endpoint.info.enabled=true
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    info:
      enabled: true

비활성화한 엔드포인트는 애플리케이션 컨텍스트에서 완전히 제거된다. 엔드포인트를 노출할 기술만 따로 변경하고 싶으면 includeexclude 프로퍼티를 사용해라.

8.2.2. Exposing Endpoints

엔드포인트에는 민감한 정보가 들어있을 수 있기 때문에, 엔드포인트들은 언제 노출해줄지를 신중하게 생각해야 한다. 다음은 내장 엔드포인트별로 디폴트 노출 정책을 정리한 테이블이다:

ID JMX Web
auditevents Yes No
beans Yes No
caches Yes No
conditions Yes No
configprops Yes No
env Yes No
flyway Yes No
health Yes Yes
heapdump N/A No
httptrace Yes No
info Yes No
integrationgraph Yes No
jolokia N/A No
logfile N/A No
loggers Yes No
liquibase Yes No
metrics Yes No
mappings Yes No
prometheus N/A No
quartz Yes No
scheduledtasks Yes No
sessions Yes No
shutdown Yes No
startup Yes No
threaddump Yes No

노출할 엔드포인트를 변경하려면 아래에 있는 해당 기술 전용 include, exclude 프로퍼티를 사용해라:

Property Default
management.endpoints.jmx.exposure.exclude  
management.endpoints.jmx.exposure.include *
management.endpoints.web.exposure.exclude  
management.endpoints.web.exposure.include health

include 프로퍼티에는 노출할 엔드포인트들의 ID를 나열한다. exclude 프로퍼티로는 노출하면 안 되는 엔드포인트들의 ID를 나열한다. exclude 프로퍼티는 include 프로퍼티보다 우선순위가 높다. include, exclude 프로퍼티 모두 엔드포인트 ID들로 설정하면 된다.

예를 들어서 JMX를 통한 엔드포인트 노출을 전부 중단하고 healthinfo 엔드포인트만 노출하려면, 아래 프로퍼티를 사용해라:

properties yaml
management.endpoints.jmx.exposure.include=health,info
management:
  endpoints:
    jmx:
      exposure:
        include: "health,info"

모든 엔드포인트들을 선택할 때는 *을 사용할 수 있다. 예를 들어 env, beans 엔드포인트를 제외한 모든 항목을 HTTP로 노출해주려면 아래 프로퍼티를 사용해라:

properties yaml
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans
management:
  endpoints:
    web:
      exposure:
        include: "*"
        exclude: "env,beans"

YAML에선 *을 특별한 의미로 사용하기 때문에, 모든 엔드포인트를 포함(또는 제외)시키려면 따옴표를 추가해야 한다.

애플리케이션을 외부로 노출한다면 무조건 엔드포인트들을 보호해주는 게 좋다.

엔드포인트들을 노출할 때 자체 전략을 구현하고 싶으면 EndpointFilter 빈을 등록하면 된다.

8.2.3. Securing HTTP Endpoints

HTTP 엔드포인트는 민감한 정보를 가지고 있는 다른 URL들과 똑같이 보호해야 한다. 스프링 시큐리티가 있을 땐 기본적으로 스프링 시큐리티의 content-negotiation 전략을 통해 엔드포인트를 보호한다. HTTP 엔드포인트의 보안 설정을 커스텀하려면, 예를 들어 특정 role을 가진 사용자만 접근을 허용하고 싶다면, 스프링 부트가 제공하는 간편한 RequestMatcher 객체들을 스프링 시큐리티와 함께 사용하면 된다.

일반적인 스프링 시큐리티 설정은 아래 예시와 비슷할 거다:

@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint())
                .authorizeRequests((requests) -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
        http.httpBasic();
        return http.build();
    }

}

위 예시에선 EndpointRequest.toAnyEndpoint()를 사용해 모든 엔드포인트에 대한 요청을 매칭하고, ENDPOINT_ADMIN role을 가지고 있는지 확인한다. EndpointRequest에 있는 다른 matcher 메소드들도 활용해도 된다. 자세한 내용은 API 문서를 참고해라 (HTML, PDF).

애플리케이션 앞에 방화벽이 있다면 인증 없이도 모든 액추에이터 엔드포인트에 접근할 수 있기를 바랄 수도 있다. 이럴 땐 management.endpoints.web.exposure.include 프로퍼티를 다음과 같이 변경하면 된다:

properties yaml
management.endpoints.web.exposure.include=*
management:
  endpoints:
    web:
      exposure:
        include: "*"

스프링 시큐리티를 사용하고 있다면, 아래 예제처럼 엔드포인트에 인증 없이도 접근할 수 있도록 허용해줄 커스텀 시큐리티 설정을 추가로 넣어줘야 한다:

@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint())
                .authorizeRequests((requests) -> requests.anyRequest().permitAll());
        return http.build();
    }

}

위에 있는 두 예제에선 액추에이터 엔드포인트에만 설정을 적용하고 있다. 스프링 부트의 시큐리티 설정은 SecurityFilterChain 빈이 있을 땐 전혀 적용되지 않기 때문에, SecurityFilterChain 빈을 하나 더 설정해서 애플리케이션의 다른 부분에 적용할 규칙들을 추가해야 한다.

8.2.4. Configuring Endpoints

엔드포인트에선 파라미터를 받지 않는 읽기 작업에 대한 응답을 자동으로 캐시한다. 엔드포인트가 응답을 캐시하고 있을 시간을 설정하려면 cache.time-to-live 프로퍼티를 사용해라. 아래 예시에선 beans 엔드포인트 캐시의 TTLtime-to-live을 10초로 설정하고 있다.

properties yaml
management.endpoint.beans.cache.time-to-live=10s
management:
  endpoint:
    beans:
      cache:
        time-to-live: "10s"

management.endpoint.<name> 프리픽스로 설정 중인 엔드포인트를 고유하게 식별할 수 있다.

8.2.5. Hypermedia for Actuator Web Endpoints

모든 엔드포인트들의 링크를 가지고 있는 “디스커버리 페이지”가 하나 추가된다. “디스커버리 페이지”는 기본적으로 /actuator에서 이용할 수 있다.

이 “디스커버리 페이지”를 비활성화하려면 애플리케이션 프로퍼티에 아래 프로퍼티를 추가해라:

properties yaml
management.endpoints.web.discovery.enabled=false
management:
  endpoints:
    web:
      discovery:
        enabled: false

커스텀 management 컨텍스트 경로를 설정하면 “디스커버리 페이지”는 자동으로 /actuator에서 management 컨텍스트의 루트로 이동한다. 예를 들어 management 컨텍스트 경로가 /management일 땐 디스커버리 페이지는 /management에서 이용할 수 있다. management 컨텍스트 경로를 /로 설정하면 다른 매핑 경로와 충돌하지 않도록 디스커버리 페이지를 비활성화한다.

8.2.6. CORS Support

CORSCross-origin resource sharingW3C 사양 중 하나로, 어떤 종류의 cross-domain 요청을 승인할지를 유연하게 지정할 수 있다. 스프링 MVC나 스프링 웹플럭스를 사용하고 있다면, 액추에이터의 웹 엔드포인트에서도 이런 시나리오를 지원하도록 설정할 수 있다.

CORS 지원은 기본적으로 비활성화돼 있으며, management.endpoints.web.cors.allowed-origins 프로퍼티를 설정했을 때만 활성화된다. 아래 설정에선 example.com 도메인에서 GET, POST 호출을 허용한다:

properties yaml
management.endpoints.web.cors.allowed-origins=https://example.com
management.endpoints.web.cors.allowed-methods=GET,POST
management:
  endpoints:
    web:
      cors:
        allowed-origins: "https://example.com"
        allowed-methods: "GET,POST"

지원하는 전체 옵션들은 CorsEndpointProperties를 확인해봐라.

8.2.7. Implementing Custom Endpoints

@Endpoint 어노테이션을 선언한 @Bean을 추가하면 @ReadOperation이나 @WriteOperation, @DeleteOperation 어노테이션이 달린 메소드들은 모두 자동으로 JMX를 통해 자동으로 노출되며, 웹 애플리케이션에선 HTTP를 통해서도 노출된다. HTTP를 통한 엔드포인트 노출은 Jersey, 스프링 MVC, 스프링 웹플럭스를 통해 할 수 있다. Jersey와 스프링 MVC가 둘 다 있을 때는 스프링 MVC를 사용한다.

아래 예제에선 커스텀 객체를 반환하는 읽기 작업read operation을 노출한다:

@ReadOperation
public CustomData getData() {
    return new CustomData("test", 5);
}

@JmxEndpoint@WebEndpoint를 사용해서 기술 전용 엔드포인트를 작성할 수도 있다. 이 엔드포인트들은 해당 기술로만 제한된다. 예를 들어 @WebEndpoint는 HTTP를 통해서만 노출되며, JMX로는 노출되지 않는다.

@EndpointWebExtension@EndpointJmxExtension을 사용하면 기술 전용 익스텐션을 작성할 수 있다. 이 어노테이션으론 기존 엔드포인트에 기술 전용 작업을 추가할 수 있다.

마지막으로 웹 프레임워크 전용 기능에 접근해야 한다면 서블릿이나 스프링 @Controller, @RestController 엔드포인트를 구현할 수 있다. 대신 이때는 JMX로는 노출해줄 수 없으며, 다른 웹 프레임워크에선 사용할 수 없다.

Receiving Input

엔드포인트의 오퍼레이션은 파라미터를 통해 입력을 받는다. 웹을 통해 노출하면 이런 파라미터 값은 URL의 쿼리 파라미터와 JSON 요청 body에서 가져온다. JMX를 통해 노출하면 파라미터는 MBean operation의 파라미터에 매핑된다. 기본적으로 파라미터는 필수다. @javax.annotation.Nullable이나 @org.springframework.lang.Nullable 어노테이션을 사용하면 파라미터를 생략 가능하게 만들 수 있다.

JSON 요청 body에 있는 모든 프로퍼티는 엔드포인트의 파라미터에 매핑할 수 있다. 아래 JSON이 요청 body라고 생각해보자:

{
    "name": "test",
    "counter": 42
}

이 프로퍼티들을 사용해서 다음 예제와 같이 String nameint counter 파라미터를 받는 쓰기 작업write operation을 호출할 수 있다:

@WriteOperation
public void updateData(String name, int counter) {
    // injects "test" and 42
}

엔드포인트는 기술에 상관 없이 사용할 수 있기 때문에, 메소드 시그니처에는 간단한 타입만 지정할 수 있다. 특히 namecounter 프로퍼티를 정의하는 CustomData 타입을 단일 파라미터로는 선언할 수 없다.

입력을 오퍼레이션 메소드의 파라미터에 매핑하려면 엔드포인트를 구현하는 자바 코드는 -parameters로 컴파일해야 하며, 코틀린 코드는 -java-parameters로 컴파일해야 한다. 스프링 부트의 그래들 플러그인을 사용하거나, 메이븐과 spring-boot-starter-parent를 사용하고 있다면 자동으로 추가된다.

Input Type Conversion

엔드포인트 오퍼레이션 메소드에 전달된 파라미터는 필요한 경우 자동으로 타입을 변환한다. JMX나 HTTP 요청을 통해 받은 입력은 오퍼레이션 메소드를 호출하기 전에, @EndpointConverter로 지정한 ConverterGenericConverter 빈과 ApplicationConversionService 인스턴스를 사용해서 필요한 타입으로 변환한다.

Custom Web Endpoints

@Endpoint, @WebEndpoint, @EndpointWebExtension에 있는 작업은 자동으로 Jersey나 스프링 MVC, 스프링 웹플럭스를 사용해서 HTTP로 노출한다. Jersey와 스프링 MVC가 둘 다 있을 땐 스프링 MVC를 사용한다.

Web Endpoint Request Predicates

웹으로 노출한 엔드포인트에 있는 모든 작업에는 request predicate가 자동으로 생성된다.

Path

predicate path는 엔드포인트의 ID와 웹에 노출한 엔드포인트의 base path로 결정한다. 디폴트 base path는 /actuator다. 예를 들어 ID가 sessions인 엔드포인트에선 /actuator/sessions를 predicate path로 사용한다.

오퍼레이션 메소드에 있는 파라미터 하나 이상에 @Selector를 선언하면 path를 좀 더 커스텀할 수 있다. 이런 파라미터는 path predicate에 path 변수로 추가된다. 변수 값은 엔드포인트 작업을 호출할 때 오퍼레이션 메소드에 전달한다. 나머지 path 요소를 모두 잡아내려면 마지막 파라미터에 @Selector(match=ALL_REMAINING)을 추가하고 String[]으로 변환할 수 있는 타입으로 만들면 된다.

HTTP method

predicate의 HTTP 메소드는 아래 테이블에 정리해 둔대로 오퍼레이션 타입에 따라 결정된다:

Operation HTTP method
@ReadOperation GET
@WriteOperation POST
@DeleteOperation DELETE
Consumes

요청 body를 사용하는 @WriteOperation(HTTP POST)에선 predicate의 consumes 항목은 application/vnd.spring-boot.actuator.v2+json, application/json이다. 다른 오퍼레이션에선 모두 consumes 항목은 빈 값이다.

Produces

predicate의 produces 항목은 @DeleteOperation, @ReadOperation, @WriteOperation 어노테이션의 produces 속성으로 결정할 수 있다. 이 속성은 생략할 수 있다. 사용하지 않으면 produces 항목을 자동 결정한다.

오퍼레이션 메소드 반환 타입이 voidVoid면 produces를 비워둔다. 오퍼레이션 메소드가 org.springframework.core.io.Resource를 반환한다면 produces 항목은 application/octet-stream이 된다. 그외 다른 오퍼레이션은 모두 application/vnd.spring-boot.actuator.v2+json, application/json을 사용한다.

Web Endpoint Response Status

엔드포인트 작업에서 반환하는 디폴트 상태 코드는 오퍼레이션 타입(읽기, 쓰기, 삭제)과 오퍼레이션이 반환하는 내용(있다면)에 따라 다르다.

@ReadOperation에선 값을 반환하면 200(OK)을 돌려준다. 값을 반환하지 않으면 404 (Not Found)로 응답한다.

@WriteOperation이나 @DeleteOperation이 값을 반환하면 응답 상태는 200(OK)이 된다. 값을 반환하지 않으면 204 (No Content)로 응답한다.

오퍼레이션을 필수 파라미터 없이 실행하거나, 필수 타입으로 변환할 수 없는 파라미터로 호출하면, 오퍼레이션 메소드를 호출하지 않으며 응답 상태는 400(Bad Request)이 된다.

Web Endpoint Range Requests

HTTP range request를 사용하면 HTTP 리소스의 일부를 요청할 수 있다. 스프링 MVC나 스프링 웹플럭스를 사용할 때는 org.springframework.core.io.Resource를 반환하는 작업은 자동으로 range request가 지원된다.

Jersey를 사용할 때는 range request를 지원하지 않는다.

Web Endpoint Security

웹 엔드포인트나 웹 전용 엔드포인트 익스텐션의 작업에선 현재 java.security.Principal이나 org.springframework.boot.actuate.endpoint.SecurityContext를 메소드 파라미터로 받을 수 있다. 전자는 보통 @Nullable과 함께 사용해서 사용자의 인증 여부에 따라 다른 동작을 수행한다. 후자는 보통 isUserInRole(String) 메소드를 사용해서 권한을 검사authorization check하는 데 활용한다.

Servlet Endpoints

@ServletEndpoint 어노테이션을 달고 동시에 Supplier<EndpointServlet>을 구현하는 클래스를 만들면 Servlet을 엔드포인트로 노출할 수 있다. 서블릿 엔드포인트는 서블릿 컨테이너와 좀 더 깊게 통합할 수 있게 해주지만, 그대신 이식성을 포기해야 한다. 서블릿 엔드포인트는 기존 Servlet을 엔드포인트로 노출하는 용도다. 엔드포인트를 새로 만들 때는 가능하면 @Endpoint@WebEndpoint 어노테이션을 사용하는 게 좋다.

Controller Endpoints

@ControllerEndpoint@RestControllerEndpoint는 스프링 MVC나 스프링 웹플럭스로만 노출할 엔드포인트를 구현할 때 사용할 수 있다. 메소드는 @RequestMapping, @GetMapping같은 스프링 MVC와 스프링 웹플럭스의 표준 어노테이션을 사용해 매핑하며, 이 path 앞에 엔드포인트 ID를 붙인다. 컨트롤러 엔드포인트는 스프링의 웹 프레임워크와 좀 더 깊게 통합할 수 있게 해주지만, 그대신 이식성을 포기해야 한다. 가능하면 @Endpoint@WebEndpoint 어노테이션을 사용하는 게 좋다.

8.2.8. Health Information

실행 중인 애플리케이션의 상태는 상태 정보health information를 통해 확인할 수 있다. 모니터링 소프트웨어에선 보통 프로덕션 시스템이 다운되면 누군가에게 알림을 전송할 수 있도록 상태 정보를 활용한다. health 엔드포인트로 노출하는 정보는 management.endpoint.health.show-details, management.endpoint.health.show-components 프로퍼티에 따라 달라진다. 이 프로퍼티는 아래 있는 값들 중 하나로 설정할 수 있다:

Name Description
never 상세 정보는 절대 보여주지 않는다.
when-authorized 인증된 사용자에게만 상세 정보를 보여준다. 권한을 부여할 role은 management.endpoint.health.roles로 설정할 수 있다.
always 모든 사용자에게 상세 정보를 보여준다.

디폴트 값은 never다. 엔드포인트의 role을 하나 이상 가진 사용자는 권한이 있는 것으로 간주한다. 엔드포인트에 설정한 role이 없을 땐 (디폴트) 인증된 모든 사용자가 권한이 있는 것으로 간주한다. 이 role은 management.endpoint.health.roles 프로퍼티로 설정할 수 있다.

보안을 적용한 애플리케이션에서 always를 사용하고 싶다면, 시큐리티 설정에선 반드시 인증된 사용자와 인증되지 않은 사용자 모두에게 health 엔드포인트 접근을 허용해야 한다.

상태는 HealthContributorRegistry에 있는 정보로 수집한다. (기본적으로는 ApplicationContext에 정의한 모든 HealthContributor 인스턴스). 스프링 부트에서는 여러 가지 HealthContributor들을 자동 설정해주며, 직접 구현할 수도 있다.

HealthContributorHealthIndicator일 수도 있고 CompositeHealthContributor일 수도 있다. HealthIndicatorStatus를 포함한 실제 상태 정보를 제공한다. CompositeHealthContributor는 다른 HealthContributor들을 가지고 있는 composite이다. contributor들을 함께 모아서 전반적인 시스템 상태를 나타내는 트리 구조를 형성한다.

기본적으로 시스템의 최종 상태는 각 HealthIndicator의 상태를 정해진 순서대로 정렬하는 StatusAggregator로 만든다. 정렬한 리스트에서 가장 앞에 있는 상태를 전반적인 상태 정보로 사용한다. HealthIndicatorStatusAggregator가 알지 못하는 상태를 반환하면 UNKNOWN 상태를 반환한다.

HealthContributorRegistry를 사용하면 런타임에 health indicator를 등록하거나 제외시킬 수 있다.

Auto-configured HealthIndicators

스프링 부트는 아래 있는 HealthIndicator들을 필요할 때 자동으로 설정해준다. management.health.key.enabled를 아래 테이블에 나와있는 key를 사용해 설정하면 원하는 indicator를 활성화/비활성화할 수도 있다:

Key Name Description
cassandra CassandraDriverHealthIndicator Cassandra 데이터베이스 상태를 검사한다.
couchbase CouchbaseHealthIndicator Couchbase 클러스터 상태를 검사한다.
db DataSourceHealthIndicator DataSource에서 커넥션을 획득할 수 있는 지 검사한다.
diskspace DiskSpaceHealthIndicator 남아 있는 디스크 공간을 검사한다.
elasticsearch ElasticsearchRestHealthIndicator Elasticsearch 클러스터 상태를 검사한다.
hazelcast HazelcastHealthIndicator Hazelcast 서버 상태를 검사한다.
influxdb InfluxDbHealthIndicator InfluxDB 서버 상태를 검사한다.
jms JmsHealthIndicator JMS 브로커 상태를 검사한다.
ldap LdapHealthIndicator LDAP 서버 상태를 검사한다.
mail MailHealthIndicator mail 서버 상태를 검사한다.
mongo MongoHealthIndicator Mongo 데이터베이스 상태를 검사한다.
neo4j Neo4jHealthIndicator Neo4j 데이터베이스 상태를 검사한다.
ping PingHealthIndicator 항상 UP으로 응답한다.
rabbit RabbitHealthIndicator Rabbit 서버 상태를 검사한다.
redis RedisHealthIndicator Redis 서버 상태를 검사한다.
solr SolrHealthIndicator Solr 서버 상태를 검사한다.

management.health.defaults.enabled 프로퍼티를 사용하면 전부 비활성화할 수 있다.

그 밖에 다른 HealthIndicator도 있지만, 기본으로 활성화하진 않는다:

Key Name Description
livenessstate LivenessStateHealthIndicator 애플리케이션의 “Liveness” 가용성 상태를 노출한다.
readinessstate ReadinessStateHealthIndicator 애플리케이션의 “Readiness” 가용성 상태를 노출한다.

Writing Custom HealthIndicators

상태 정보를 커스텀하려면 HealthIndicator 인터페이스를 구현한 스프링 빈을 등록하면 된다. health() 메소드를 구현해서 Health 응답을 반환해야 한다. Health 응답은 status를 가지고 있어야 하며, 표기하고 싶은 다른 세부정보도 함께 넣어도 된다. 다음은 HealthIndicator의 샘플 구현체다:

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check();
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

    private int check() {
        // perform some specific health check
        return ...
    }

}

HealthIndicator 빈의 이름에서 뒤의 HealthIndicator(있다면)를 제외한 게 HealthIndicator의 식별자다. 위 예시에 있는 상태 정보는 my라는 엔트리로 사용할 수 있다.

Health는 스프링 부트에서 미리 정의해둔 Status 타입 외에도 시스템 상태를 나타내는 새로운 커스텀 Status를 반환할 수도 있다. 이땐 StatusAggregator 인터페이스의 커스텀 구현체를 함께 제공해야 하며, 아니면 management.endpoint.health.status.order 설정 프로퍼티로 디폴트 구현체를 설정해야 한다.

예를 들어 code값이 FATALStatus를 새로 만들어 HealthIndicator 구현체 중 하나에서 사용하고 있다고 해보자. Status를 심각한 순서대로 정렬해주려면 애플리케이션 프로퍼티에 아래 프로퍼티를 추가해라:

properties yaml
management.endpoint.health.status.order=fatal,down,out-of-service,unknown,up
management:
  endpoint:
    health:
      status:
        order: "fatal,down,out-of-service,unknown,up"

응답에 있는 HTTP 상태 코드는 전반적인 상태를 반영해서 만든다. 기본적으로 OUT_OF_SERVICEDOWN은 503에 매핑된다. UP과 그외 매핑되지 않은 상태는 200에 매핑한다. HTTP를 통해 상태 엔드포인트에 접근한다면 상태 매핑을 커스텀하고 싶을 수도 있다. 커스텀 매핑을 설정하게되면 DOWNOUT_OF_SERVICE의 디폴트 매핑은 비활성화된다. 디폴트 매핑을 그대로 유지하려면 커스텀 매핑과 함께 명시해줘야 한다. 예를 들어 아래 프로퍼티는 FATAL을 503(service unavailable)으로 매핑하면서 동시에 DOWNOUT_OF_SERVICE의 디폴트 매핑을 보존하고 있다:

properties yaml
management.endpoint.health.status.http-mapping.down=503
management.endpoint.health.status.http-mapping.fatal=503
management.endpoint.health.status.http-mapping.out-of-service=503
management:
  endpoint:
    health:
      status:
        http-mapping:
          down: 503
          fatal: 503
          out-of-service: 503

다른 세세한 로직이 필요하다면 자체 HttpCodeStatusMapper을 정의해도 된다.

다음은 내장된 Status에 대한 디폴트 매핑을 정리한 테이블이다:

Status Mapping
DOWN SERVICE_UNAVAILABLE (503)
OUT_OF_SERVICE SERVICE_UNAVAILABLE (503)
UP 기본적으로는 매핑되지 않아서 HTTP 상태는 200이 된다
UNKNOWN 기본적으로는 매핑되지 않아서 HTTP 상태는 200이 된다

Reactive Health Indicators

스프링 웹플럭스를 사용하는 등의 리액티브 애플리케이션에선 ReactiveHealthContributor가 논블로킹으로 애플리케이션 상태를 가져오는 역할을 담당한다. 기존 HealthContributor와 마찬가지로 상태 정보는 ReactiveHealthContributorRegistry에 있는 정보로 수집한다 (기본적으로는 ApplicationContext에 정의한 모든 HealthContributor, ReactiveHealthContributor 인스턴스). 리액티브 API를 사용하지 않는 일반 HealthContributor는 elastic 스케줄러에서 실행한다.

리액티브 애플리케이션에서 런타임에 health indicator를 등록하고 제외시키려면 ReactiveHealthContributorRegistry를 사용해야 한다. 일반 HealthContributor를 등록해야 한다면 ReactiveHealthContributor#adapt를 사용해 래핑해야 한다.

리액티브 API로 커스텀 상태 정보를 제공하려면 ReactiveHealthIndicator 인터페이스를 구현한 스프링 빈을 등록하면 된다. 다음은 ReactiveHealthIndicator의 샘플 구현체다:

@Component
public class MyReactiveHealthIndicator implements ReactiveHealthIndicator {

    @Override
    public Mono<Health> health() {
        return doHealthCheck().onErrorResume((exception) ->
            Mono.just(new Health.Builder().down(exception).build()));
    }

    private Mono<Health> doHealthCheck() {
        // perform some specific health check
        return ...
    }

}

에러를 자동으로 처리하고 싶으면 AbstractReactiveHealthIndicator를 상속받는 것도 좋다.

Auto-configured ReactiveHealthIndicators

스프링 부트는 아래 있는 ReactiveHealthIndicator들을 필요할 때 자동으로 설정해준다:

Key Name Description
cassandra CassandraDriverReactiveHealthIndicator Cassandra 데이터베이스 서버 상태를 검사한다.
couchbase CouchbaseReactiveHealthIndicator Couchbase 클러스터 상태를 검사한다.
elasticsearch ElasticsearchReactiveHealthIndicator Elasticsearch 클러스터 상태를 검사한다.
mongo MongoReactiveHealthIndicator Mongo 데이터베이스 서버 상태를 검사한다.
neo4j Neo4jReactiveHealthIndicator Neo4j 데이터베이스 서버 상태를 검사한다.
redis RedisReactiveHealthIndicator Redis 서버 상태를 검사한다.

일반 indicator는 필요 시엔 리액티브 indicator로 대체된다. 또한 명시적으로 처리하지 않은 HealthIndicator는 자동으로 래핑된다.

Health Groups

health indicator를 다양한 목적으로 활용할 수 있는 그룹으로 묶으면 유용할 때가 많다.

health indicator 그룹을 생성하려면 management.endpoint.health.group.<name> 프로퍼티를 사용해 includeexclude에 health indicator ID들을 지정하면 된다. 예를 들어, 데이터베이스 indicator만 포함하는 그룹을 생성하려면 아래와 같이 정의하면 된다:

properties yaml
management.endpoint.health.group.custom.include=db
management:
  endpoint:
    health:
      group:
        custom:
          include: "db"

이제 localhost:8080/actuator/health/custom에 접속하면 결과를 확인해볼 수 있다.

마찬가지로, 그룹에서 데이터베이스 indicator를 제외하고, 다른 indicator는 전부 포함시키려면 아래와 같이 정의하면 된다:

properties yaml
management.endpoint.health.group.custom.exclude=db
management:
  endpoint:
    health:
      group:
        custom:
          exclude: "db"

그룹은 기본적으론 시스템 health와 동일하게 StatusAggregatorHttpCodeStatusMapper 설정을 상속하지만, 그룹별로도 따로 정의할 수 있다. 필요하다면 show-detailsroles 프로퍼티도 재정의할 수 있다:

properties yaml
management.endpoint.health.group.custom.show-details=when-authorized
management.endpoint.health.group.custom.roles=admin
management.endpoint.health.group.custom.status.order=fatal,up
management.endpoint.health.group.custom.status.http-mapping.fatal=500
management.endpoint.health.group.custom.status.http-mapping.out-of-service=500
management:
  endpoint:
    health:
      group:
        custom:
          show-details: "when-authorized"
          roles: "admin"
          status:
            order: "fatal,up"
            http-mapping:
              fatal: 500
              out-of-service: 500

그룹과 함께 사용할 커스텀 StatusAggregatorHttpCodeStatusMapper 빈을 등록해야 한다면 @Qualifier("groupname")을 사용하면 된다.

DataSource Health

DataSource health indicator는 표준 데이터소스와 라우팅 데이터소스 빈의 상태를 모두 보여준다. 라우팅 데이터소스의 상태에는 각 타겟 데이터소스의 상태가 들어있다. health 엔드포인트의 응답에선 라우팅 키로 라우팅 데이터소스의 각 타겟들의 이름을 지정한다. indicator에서 라우팅 데이터소스를 출력하지 않도록 하려면 management.health.db.ignore-routing-data-sourcestrue로 설정해라.

8.2.9. Kubernetes Probes

쿠버네티스에 배포된 애플리케이션은 컨테이너 프로브를 통해 내부 상태에 대한 정보를 제공할 수 있다. 사용하는 쿠버네티스 설정에 따라 kubelet이 이 프로브를 호출하고 결과에 따라 대응해줄 거다.

스프링 부트는 특별한 설정 없이도 애플리케이션 가용성 상태를 관리해준다. 액추에이터는 쿠버네티스 환경에 배포되면 ApplicationAvailability 인터페이스에서 모은 “Liveness”와 “Readiness” 정보를 전용 Health Indicator, LivenessStateHealthIndicatorReadinessStateHealthIndicator에 활용한다. 이 indicator들은 글로벌 health 엔드포인트("/actuator/health")에 노출된다. 더불어 Health 그룹, "/actuator/health/liveness""/actuator/health/readiness"를 통해서도 별도 HTTP 프로브로 노출된다.

이제 이 엔드포인트 정보를 활용해서 쿠버네티스 인프라를 구성할 수 있다:

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: <actuator-port>
  failureThreshold: ...
  periodSeconds: ...

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: <actuator-port>
  failureThreshold: ...
  periodSeconds: ...

<actuator-port>는 액추에이터 엔드포인트에 접근할 수 있는 포트로 설정해야 한다. 엔드포인트 포트는 메인 웹 서버의 포트일 수도 있고, "management.server.port" 프로퍼티를 설정했다면 별도로 사용하는 management 포트일 수도 있다.

이 health 그룹은 애플리케이션이 쿠버네티스 환경에서 실행 중일 때만 자동으로 활성화된다. 설정 프로퍼티 management.endpoint.health.probes.enabled를 사용하면 모든 환경에서 활성화할 수 있다.

애플리케이션을 기동하는 시간이 liveness period로 설정한 시간보다 길다면, 쿠버네티스에선 가능한 솔루션으로 "startupProbe"를 언급하고 있다. 모든 기동 태스크를 완료할 때까지 "readinessProbe"가 실패할 거기 때문에 "startupProbe"가 반드시 필요한 건 아니다. 애플리케이션 라이프사이클 동안 프로브가 어떻게 동작하는지도 함께 참고해라.

액추에이터 엔드포인트를 별도 management 컨텍스트에 배포하게 되면, 엔드포인트에선 메인 애플리케이션과 동일한 웹 인프라(포트, 커넥션 풀, 프레임워크 컴포넌트들)를 사용하지 않는다는 점에 유의해라. 이땐 메인 애플리케이션이 제대로 동작하지 않을 때도 (예를 들어 커넥션을 새로 수락할 수 없을 때) 프로브 검사에 성공할 수도 있다.

Checking External State with Kubernetes Probes

액추에이터는 “liveness”와 “readiness” 프로브를 Health 그룹으로 구성한다. 덕분에 여기서는 Health 그룹 기능을 전부 이용할 수 있다. 예를 들어 다른 Health Indicator를 추가로 설정할 수 있다:

properties yaml
management.endpoint.health.group.readiness.include=readinessState,customCheck
management:
  endpoint:
    health:
      group:
        readiness:
          include: "readinessState,customCheck"

기본적으로 스프링 부트는 이 그룹들에 다른 Health Indicator는 추가하지 않는다.

“liveness” 프로브는 외부 시스템의 상태에 의존하면 안 된다. 쿠버네티스는 애플리케이션의 liveness 상태가 깨지면 애플리케이션 인스턴스를 재시작하는 식으로 문제를 해결하려고한다. 즉, 외부 시스템(데이터베이스, 웹 API, 외부 캐시)이 실패하면 쿠버네티스는 모든 인스턴스를 재시작해서 연이은 실패를 트리거할 수 있다.

“readiness” 프로브에선, 외부 시스템까지 체크할 지는 애플리케이션 개발자가 신중하게 결정해야 한다. 다시 말해 스프링 부트에선 readiness 프로브에 별다른 상태 체크를 추가하지 않는다. 쿠버네티스는 애플리케이션 인스턴스의 readiness 상태가 아직 준비되지 않았을 땐 해당 인스턴스로 트래픽을 라우팅하지 않는다. 일부 외부 시스템은 애플리케이션 인스턴스들이 공유하고 있지 않을 수도 있는데, 이럴 땐 readiness 프로브에 포함시키는 게 자연스럽다. 또다른 외부 시스템은 애플리케이션에 반드시 필요한 건 아닐 수도 있다 (서킷 브레이커나 폴백이 있을 수 있다). 이럴 땐 분명히 포함시키지 않는게 맞다. 안타깝지만 모든 애플리케이션 인스턴스가 공유하는 외부 시스템은 굉장히 흔하며, 직접 판단해서 결정을 내려야 한다. readiness 프로브에 포함시켜 외부 서비스가 다운되면 애플리케이션이 서비스에서 제외되길 기대하거나, 아니면 그대로 놔두고 호출부에서 서킷 브레이커 등을 활용하는 식으로 상위 스택으로 실패 처리를 넘겨라.

type=ClusterIPNodePort를 사용하는 쿠버네티스 서비스는 애플리케이션 인스턴스들이 전부 준비되지 않았을 땐 들어오는 커넥션을 허용하지 않는다. 커넥션 자체가 없기 때문에 HTTP 에러 응답(503 등)도 없다. type=LoadBalancer를 사용하는 서비스는 제공업체에 따라 커넥션을 수락할 수도 있고 수락하지 않을 수도 있다. 인그레스를 가지고 있는 서비스도 구현체에 따라 다르게 응답한다. 인그레스 서비스 자체에서 다운스트림에서 발생하는 “connection refused”를 처리하는 방법을 결정해야 할 거다. 로드 밸런서와 인그레스 모두 HTTP 503이 발생할 확률이 높다.

게다가 애플리케이션이 쿠버네티스 오토스케일링을 사용하고 있다면, 오토스케일러 설정에 따라 로드 밸런서에서 애플리케이션이 제거됐을 때의 대응이 다를 수도 있다.

Application Lifecycle and Probe States

쿠버네티스의 프로브를 이용할 땐 애플리케이션 라이프 사이클과의 일관성에 주목해야 한다. 메모리 상에 존재하며 애플리케이션의 내부 상태를 나타내는 AvailabilityState와, 이 state를 노출하는 실제 프로브에는 상당한 차이가 존재한다. 애플리케이션 라이프사이클 단계에 따라 프로브를 사용하지 못할 때도 있다.

스프링 부트는 기동과 종료 중에 애플리케이션 이벤트를 발행하며, 프로브에선 이 이벤트를 받아listen AvailabilityState 정보를 노출할 수 있다.

다음은 각 단계마다의 AvailabilityState와 HTTP 커넥터 상태를 담고있는 테이블이다:

스프링 부트 애플리케이션을 기동할 때:

Startup phase LivenessState ReadinessState HTTP server Notes
Starting BROKEN REFUSING_TRAFFIC 기동 전 쿠버네티스는 “liveness” 프로브를 확인하고 시간이 너무 오래 걸리면 애플리케이션을 재시작한다.
Started CORRECT REFUSING_TRAFFIC 요청 거부 애플리케이션 컨텍스트를 리프레시한다. 애플리케이션은 기동 태스크를 수행하며 아직 트래픽을 받진 않는다.
Ready CORRECT ACCEPTING_TRAFFIC 요청 수락 기동 태스크들을 완료한다. 애플리케이션은 트래픽을 받기 시작한다.

스프링 부트 애플리케이션을 종료할 때:

Shutdown phase Liveness State Readiness State HTTP server Notes
Running CORRECT ACCEPTING_TRAFFIC 요청 수락 종료를 요청했다.
Graceful shutdown CORRECT REFUSING_TRAFFIC 새 요청은 거부 graceful shutdown을 활성화했다면 진행 중인 요청을 처리한다.
Shutdown complete N/A N/A 서버 종료 애플리케이션 컨텍스트를 종료하고 애플래케이션을 종료시킨다.

쿠버네티스 배포에 관한 자세한 내용은 쿠버네티스 컨테이너 라이프 사이클 섹션을 확인해봐라.

8.2.10. Application Information

애플리케이션 정보로 노출하는 정보들은 ApplicationContext에 정의한 모든 InfoContributor 빈에서 수집해온다. 스프링 부트에서는 여러 가지 InfoContributor들을 자동 설정해주며, 직접 구현할 수도 있다.

Auto-configured InfoContributors

스프링 부트는 아래 있는 InfoContributor들을 필요할 때 자동으로 설정해준다:

Name Description
EnvironmentInfoContributor Environment에서 info 아래에 있는 모든 키들을 노출한다.
GitInfoContributor git.properties 파일이 있을 때 git 정보를 노출한다.
BuildInfoContributor META-INF/build-info.properties 파일이 있을 때 빌드 정보를 노출한다.

management.info.defaults.enabled 프로퍼티를 사용하면 전부 비활성화할 수 있다.

Custom Application Information

스프링 프로퍼티 info.*를 설정하면 info 엔드포인트로 노출할 데이터를 커스텀할 수 있다. info 키 아래에 설정한 모든 Environment 프로퍼티를 자동으로 노출해준다. 예를 들어 application.properties 파일에 다음과 같은 설정을 추가할 수 있다:

properties yaml
info.app.encoding=UTF-8
info.app.java.source=11
info.app.java.target=11
info:
  app:
    encoding: "UTF-8"
    java:
      source: "11"
      target: "11"

info 프로퍼티를 직접 하드코딩하는 대신 빌드 시점에 확장할 수도 있다.

메이븐을 사용한다면 위 예제는 아래처럼 작성해도 된다:

properties yaml
info.app.encoding=@project.build.sourceEncoding@
info.app.java.source=@java.version@
info.app.java.target=@java.version@
info:
app:
encoding: "@project.build.sourceEncoding@"
java:
source: "@java.version@"
target: "@java.version@"

Git Commit Information

info 엔드포인트에서 또 한가지 유용한 기능은 프로젝트를 빌드할 때 git 소스 코드 레포지토리의 상태 정보를 게시하는 기능이다. GitProperties 빈을 사용할 수 있으면 이런 프로퍼티도 info 엔드포인트로 노출할 수 있다.

클래스패스 루트에 git.properties 파일이 있다면 GitProperties 빈은 자동으로 설정된다. 자세한 내용은 “깃 정보 생성하기“를 참고해라.

기본적으로 엔드포인트는 git.branch, git.commit.id, git.commit.time 프로퍼티(있으면)를 노출한다. 이런 프로퍼티를 엔드포인트 응답에서 제외하고 싶으면 git.properties 파일에서 제거해야 한다. git 정보를 전부 (즉, git.properties에 있는 전체 내용) 표기하려면 management.info.git.mode 프로퍼티를 사용해라:

properties yaml
management.info.git.mode=full
management:
  info:
    git:
      mode: "full"

info 엔드포인트에서 git 커밋 정보를 완전히 제외시키려면 아래 예시처럼 management.info.git.enabled 프로퍼티를 false로 설정해라:

properties yaml
management.info.git.enabled=false
management:
  info:
    git:
      enabled: false

Build Information

BuildProperties 빈이 등록돼 있으면 info 엔드포인트로 빌드에 관한 정보도 게시할 수 있다. 클래스패스에 META-INF/build-info.properties 파일이 있을 때 게시된다.

메이븐이나 그래들 플러그인으로 이 파일을 생성할 수 있다. 자세한 내용은 “빌드 정보 생성하기“를 참고해라.

Writing Custom InfoContributors

커스텀 애플리케이션 정보를 제공하려면 InfoContributor 인터페이스를 구현한 스프링 빈을 등록하면 된다.

다음 예제에선 단일 값을 가지고 있는 example 엔트리를 제공한다:

@Component
public class MyInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("example", Collections.singletonMap("key", "value"));
    }

}

info 엔드포인트에 접근하면 응답에 아래와 같은 엔트리가 추가된 걸 확인할 수 있을 거다:

{
    "example": {
        "key" : "value"
    }
}

전체 목차는 여기에 있습니다.

<< >>

TOP