이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › Spring › 스프링 부트
액추에이터와 헬스 체크
결제사 연결이 끊겼을 때 헬스 체크는 무엇이라고 답하나
액추에이터는 떠 있는 스프링 부트 애플리케이션의 상태를 밖에서 물어볼 수 있게 주소를 만들어 주는 라이브러리입니다. 그 가운데 /actuator/health는 "이 서버가 정상인가"에 답합니다. 답은 애플리케이션 안의 여러 구성 요소가 각자 보고한 상태를 합쳐서 정해지고, 어느 주소로 묻느냐에 따라 살펴보는 구성 요소가 달라집니다.
실행으로 대조 · 스프링 부트 4.1.1 서버에 아흔여섯 가지 조합의 요청을 보내 대조
이 실습에 나오는 개념
- Actuator
- 운영 중인 Spring Boot 애플리케이션의 상태를 확인할 수 있는 엔드포인트를 제공하는 모듈이다. 헬스 체크, 각종 지표(메모리, 스레드, 요청 수 등), 설정 정보 등을 조회할 수 있어 모니터링 시스템이나 로드밸런서의 상태 확인과 연동한다.
- HealthIndicator와 상태 합치기
- HealthIndicator는 애플리케이션의 구성 요소 하나가 정상인지 점검해 상태로 답하는 객체이다. health() 메서드가 UP(정상), DOWN(고장), OUT_OF_SERVICE(일부러 내려 둠), UNKNOWN(알 수 없음) 가운데 하나를 돌려주며, 빈 이름에서 HealthIndicator를 뗀 것이 구성 요소의 이름이 된다. /actuator/health는 구성 요소들의 답을 모아 가장 심각한 것 하나를 전체 상태로 삼는데, 심각한 순서는 DOWN, OUT_OF_SERVICE, UP, UNKNOWN이다. 전체 상태가 DOWN이나 OUT_OF_SERVICE이면 HTTP 상태 코드 503으로, UP이나 UNKNOWN이면 200으로 응답한다. 구성 요소 하나만 DOWN이어도 전체가 DOWN이 되므로, 어떤 점검을 넣을지는 그것이 고장 나면 서버 전체를 비정상으로 볼 것인지를 기준으로 정한다.
- 헬스 그룹
- 헬스 엔드포인트에 등록된 구성 요소 가운데 일부만 골라 묶고 이름을 붙인 것이다. /actuator/health/그룹이름으로 요청하면 그 그룹에 든 구성 요소에게만 상태를 묻고 그것만으로 전체 상태와 HTTP 상태 코드를 정한다. 스프링 부트 4는 liveness와 readiness라는 그룹을 기본으로 만들어 두며, 각각 애플리케이션 자신의 상태를 알려 주는 livenessState와 readinessState 하나씩만 들어 있다. management.endpoint.health.group.그룹이름.include에 구성 요소 이름을 적어 그룹에 넣을 것을 정한다. 그룹에 들지 않은 구성 요소는 DOWN이어도 그 그룹의 응답에 반영되지 않는다.
- 헬스 체크(Liveness, Readiness)
- 서버가 정상인지 주기적으로 확인하는 것이다. Liveness는 프로세스가 살아 있는지를 확인해 문제가 있으면 재시작하게 한다. Readiness는 요청을 받을 준비가 되었는지를 확인해 준비되지 않은 서버로는 트래픽을 보내지 않게 한다. 서버가 시작 중이거나 종료 중일 때 요청이 들어가는 것을 막는다.
- 액추에이터 엔드포인트의 노출과 접근
- 액추에이터의 엔드포인트(health, env, beans처럼 정보를 하나씩 맡은 기능)를 밖에서 쓸 수 있으려면 두 가지 조건이 모두 맞아야 한다. 노출(exposure)은 그 엔드포인트에 웹 주소를 만들어 줄지를 정하며, management.endpoints.web.exposure.include의 기본값은 health 하나뿐이다. 접근(access)은 그 엔드포인트 자체를 쓸 수 있게 할지를 정하며 management.endpoint.이름.access로 바꾼다. 둘 중 하나라도 막혀 있으면 주소가 만들어지지 않아 404로 응답한다. 서버를 종료시키는 shutdown과 메모리 내용을 통째로 내려받게 하는 heapdump는 접근이 기본으로 막혀 있어서, 노출 설정에서 전부 열어도 주소가 생기지 않는다.
이렇게 바꿔 보세요
- 처음 설정으로 — pay가 DOWN입니다. 503이 되지만 본문에는 DOWN 한마디뿐이라 무엇이 문제인지 보이지 않습니다.
- show-details를 always로 — 구성 요소별 상태와 세부 정보가 본문에 나옵니다.
- when-authorized인데 로그인하지 않으면 — 로그인 정보가 없는 요청에는 never와 똑같이 숨깁니다.
- liveness로 물으면 — pay가 DOWN이어도 200 UP입니다. liveness 그룹은 pay를 묻지 않습니다.
- readiness로 물으면 — readiness 그룹도 기본으로는 pay를 묻지 않습니다. 역시 200 UP입니다.
- readiness 그룹에 pay를 넣으면 — 이제 pay가 DOWN이면 readiness도 503이 됩니다.
- UNKNOWN은 UP에 밀린다 — pay가 "알 수 없음"을 답해도 전체 상태는 UP이고 200입니다.
처음 설정으로 실행하면
- GET /actuator/health: 로그인하지 않은 요청이 도착합니다. /actuator로 시작하는 주소는 액추에이터가 만들어 둔 것입니다. 따로 설정하지 않으면 웹으로는 health 하나만 열려 있습니다.
- /actuator/health: 주소가 health에서 끝나므로 등록된 구성 요소를 모두 묻습니다. 이 애플리케이션에는 livenessState, readinessState, pay 세 개가 등록돼 있습니다. 앞의 둘은 스프링 부트가 넣어 둔 것이고, pay는 직접 만든 PayHealthIndicator입니다.
- livenessState에 묻기: 답은 UP입니다. 애플리케이션이 고장 나지 않고 돌아가고 있다는 뜻입니다. 애플리케이션 자신만 보고 바깥 서버는 보지 않습니다.
- readinessState에 묻기: 답은 UP입니다. 애플리케이션이 시작을 마치고 요청을 받을 준비가 됐다는 뜻입니다.
- PayHealthIndicator.health() 실행: 답은 DOWN입니다. 결제사 서버에 연결할 수 없다는 뜻입니다.
- 상태 합치기: 모은 상태는 livenessState UP, readinessState UP, pay DOWN입니다. 이 가운데 우선순위가 가장 높은 것 하나가 전체 상태가 됩니다. 스프링 부트가 기본으로 쓰는 상태 우선순위(설정으로 바꿀 수 있음)는 DOWN, OUT_OF_SERVICE, UP, UNKNOWN입니다. DOWN이 하나 있으므로 전체 상태는 DOWN입니다.
- HTTP 상태 코드 정하기: 전체 상태 DOWN에 맞는 코드는 503 Service Unavailable입니다. DOWN과 OUT_OF_SERVICE는 503, UP과 UNKNOWN은 200으로 응답합니다. 상태를 확인하는 쪽은 본문을 읽지 않고 이 숫자만 봐도 정상인지 알 수 있습니다.
- 응답 본문 만들기: show-details를 적지 않았으므로 기본값 never가 적용됩니다. 구성 요소별 내용은 숨기고 전체 상태만 보냅니다. 본문은 {"groups":["liveness","readiness"],"status":"DOWN"}입니다. groups는 이 애플리케이션에 있는 그룹의 이름입니다.
실행 결과
- 구성 요소 하나만 DOWN이어도 전체가 DOWN이 되고 503으로 응답합니다. 이 주소로 상태를 확인하는 쪽은 이 서버를 비정상으로 봅니다.
- show-details의 기본값은 never입니다. 본문만 봐서는 어느 구성 요소가 문제인지 알 수 없습니다.
코드
// 보내는 요청 (로그인하지 않음)
// GET /actuator/health
# application.properties
# 자동으로 붙는 구성 요소(diskSpace, ping, ssl, db)를 끈다. 실습을 단순하게 하려는 설정
management.health.defaults.enabled=false
# show-details는 적지 않았다 (기본값 never)
// 직접 만든 구성 요소. 빈 이름 payHealthIndicator에서
// HealthIndicator를 뗀 "pay"가 구성 요소의 이름이 된다
@Component
class PayHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 결제사 서버에 연결해 본 결과를 상태로 답한다. 여기서는 DOWN
return Health.down().withDetail("target", "pay.example").build();
}
}
이 모형의 전제
pay의 상태 네 가지, 주소 세 가지, show-details와 요청한 사람 네 가지, readiness 그룹에 pay를 넣었는지 두 가지를 곱한 아흔여섯 조합을 모두 실제 스프링 부트 4.1.1 서버(스프링 프레임워크 7.0.9, 내장 톰캣 11, 127.0.0.1)에 HTTP 요청으로 보내, HTTP 상태 코드와 본문의 글자 전체, 그 주소가 묻는 구성 요소의 목록, PayHealthIndicator가 실행됐는지가 화면과 일치하는 것을 확인했습니다. 본문에서 항목이 components, groups, status 순서로 나오는 것도 이 버전의 실제 응답 그대로이며, 버전에 따라 순서는 다를 수 있습니다. 구성 요소를 통제한 방법: 대조에 쓴 환경에는 H2와 DataSource 자동 설정이 함께 있어, 아무 설정 없이 띄우면 db, diskSpace, ping, ssl이 자동으로 붙습니다(실행으로 확인). 이것들을 management.health.defaults.enabled=false로 꺼서 livenessState, readinessState, pay 세 개만 남겼습니다. 실제 애플리케이션에서는 이 구성 요소들도 함께 합쳐집니다. 스프링 시큐리티는 모든 요청을 허용하도록 두었습니다. 시큐리티가 /actuator 주소를 막아 둔 애플리케이션에서는 로그인하지 않은 요청이 헬스 엔드포인트에 닿기 전에 거절될 수 있고, 그 경우는 다루지 않았습니다. "로그인한 사용자의 요청"은 비밀번호 로그인 대신, 요청에 스프링 시큐리티의 인증 정보를 채워 주는 시험용 필터로 만들었습니다. management.endpoint.health.roles를 비워 두면 로그인한 사용자 모두에게 보여 준다는 것은 라이브러리에 든 설정 설명과 이 실행 결과가 일치합니다. 화면의 PayHealthIndicator는 @Component 클래스이지만 대조에서는 같은 빈 이름(payHealthIndicator)의 @Bean으로 등록했고, 답할 상태를 실행 중에 바꿨습니다. 스프링 부트 3과 달라진 점: 4.1.1에서는 management.endpoint.health.probes.enabled의 기본값이 true여서 아무 설정 없이도 liveness와 readiness 그룹이 있습니다(실행으로 확인. 스프링 부트 3에서는 쿠버네티스 같은 환경에서만 자동으로 켜진다고 알려져 있으나 그 버전은 실행해 보지 않았습니다). HealthIndicator와 Health는 spring-boot-health 모듈의 org.springframework.boot.health.contributor 패키지에 있습니다. 화면에 없는 경우도 같은 방법으로 확인했습니다. 아무 설정이 없으면 /actuator의 목록에는 health만 있고 /actuator/env, /actuator/beans, /actuator/info는 404입니다. management.endpoints.web.exposure.include=*로 열면 env와 beans가 200이 되지만 shutdown은 여전히 404입니다. 노출(exposure)과 별개로 접근(access)이 막혀 있기 때문이며, 반대로 management.endpoint.shutdown.access=unrestricted만 주고 노출하지 않아도 404입니다(둘 다 주면 주소가 생깁니다. 실제로 종료시키는 POST 요청은 보내지 않았습니다). exposure.include=*에 management.endpoint.env.access=none을 더하면 env만 404가 됩니다. exposure.include=env처럼 적으면 기본값을 대신하므로 health가 404가 됩니다. probes.enabled=false로 끄면 liveness와 readiness 주소가 404가 되고 본문의 groups도 사라집니다. liveness 그룹에 pay를 넣으면 pay가 DOWN일 때 liveness도 503이 됩니다. 상태의 순서는 구성 요소 둘의 상태 열여섯 쌍으로 확인했고, 모든 구성 요소가 UNKNOWN이면 전체 상태도 UNKNOWN이지만 응답은 200입니다. /actuator/health/pay처럼 구성 요소 하나만 묻는 주소는 구성 요소별 내용이 보이는 설정에서만 응답하고, 숨기는 설정에서는 404입니다. 확인하지 않은 것: 애플리케이션이 시작하는 중이거나 종료하는 중일 때 livenessState와 readinessState가 바뀌는 것, 로드밸런서나 쿠버네티스가 이 응답을 받아 하는 일(널리 알려진 설명을 따랐습니다), 관리용 포트를 따로 두는 management.server.port, 상태의 순서와 HTTP 상태 코드 대응을 바꾸는 설정, show-components 설정, 헬스 응답의 캐시.
앞 실습: 설정 우선순위 · 다음 실습: 테스트