새 버전을 배포하고 첫 화면도 잘 열렸어요. 배포 도구가 확인한 health endpoint도/actuator/health가UP이라고 해서, 사용자의 주문까지 정상이라는 뜻은 아니에요.
200 OK예요. 그런데 잠시 뒤부터 주문 요청이 느려지고, 일부 사용자는 결제 단계에서 오류를 봐요.
이럴 때 health=UP만 보고 있으면 질문이 막혀요.
“서버가 살아 있다는데, 왜 사용자는 실패하고 있죠?”사실은 살아 있음, 트래픽을 받을 준비가 됨, 요청이 정상 속도로 처리됨은 서로 다른 상태예요. 하나의 초록불로 모두 표현할 수 없어요. 앞 글에서는 database와 외부 HTTP 경계를 실제에 가깝게 test하는 방법을 봤어요. 이번에는 test가 끝난 뒤 실행 중인 application을 어떻게 바라볼지 살펴볼게요. Spring Boot Actuator의 health, info, metrics를 나누고, 운영 도구가 읽을 Prometheus 형식까지 연결해 볼 거예요.
설정과 dependency 예시는 Spring Boot 4.x와 Java 21을 기준으로 해요. Actuator의 endpoint 기본값은 version에 따라 달라질 수 있으므로, 실제 project를 운영할 때는 사용 중인 Spring Boot version의 공식 문서도 함께 확인하세요.
먼저 “정상”이라는 말을 네 가지 질문으로 나눠 봐요
Application을 운영한다는 것은 한 번 접속해 보고 끝내는 일이 아니에요. 서로 다른 주체가 서로 다른 질문을 계속 던지는 일이에요.
여기서 자주 생기는 오해가 있어요.
health, metrics, prometheus는 비슷한 숫자를 다른 모양으로 보여주는 endpoint가 아니에요.
Health는 지금의 상태를 작게 요약하고, info는 실행 중인 artifact가 무엇인지 알려줘요. Metrics는 여러 순간의 측정값을 쌓아 추세를 보게 하고, Prometheus endpoint는 그 측정값을 monitoring system이 가져갈 수 있는 형식으로 내보내요.
Actuator dependency를 넣으면 운영용 입구가 준비돼요
먼저 Actuator starter를 추가해요. Prometheus가 metric을 가져가게 하려면 Prometheus registry도 runtime dependency로 넣어요.build.gradle
- Actuator endpoint 구현과 자동 설정을 classpath에서 찾고 준비해요.
HealthContributor들을 모아 health 상태를 만들어요.- Micrometer의
MeterRegistry를 만들고 JVM, process, HTTP 요청 같은 meter를 등록해요. - Prometheus registry가 있으면 Prometheus scrape 형식을 만들 수 있게 해요.
Endpoint가 준비됐어도 HTTP exposure 목록에 없으면 URL로 접근할 수 없어요. URL이 열렸어도 인증과 network 경계가 없으면 운영 정보가 의도하지 않은 사람에게 보일 수 있죠.
현재 Spring Boot 4.x의 기본 HTTP exposure에는
health만 들어가요. info, metrics, prometheus는 필요한 것만 명시적으로 열어야 해요.
src/main/resources/application.yml
SecurityFilterChain이 있다면 Spring Boot의 Actuator 보안 자동 설정은 물러나요. Security filter chain 글에서 본 것처럼, 이때는 health 공개 범위와 운영자 endpoint 인증을 직접 규칙에 넣어야 해요.
Health는 “모든 기능이 정상”이 아니라 상태 판단 재료예요
기본/actuator/health 응답은 아주 작아요.
show-details의 기본값이 never이기 때문에, 공개 요청에는 보통 전체 상태만 보여요. 내부에서는 database, disk space, Redis처럼 classpath와 bean 구성에 맞춰 만들어진 HealthContributor들이 각자의 상태를 내고 있어요.
여러 contributor의 결과는 상태 집계기(status aggregator)가 하나의 전체 상태로 합쳐요. 기본 HTTP mapping에서는 DOWN과 OUT_OF_SERVICE가 503 Service Unavailable로 이어지고, UP은 200으로 응답해요.
여기서 중요한 점은 어떤 component를 전체 health에 넣었는지가 곧 운영 정책이라는 거예요. 추천 상품용 Redis가 잠시 실패해도 주문은 받을 수 있는데 전체 health를 DOWN으로 만들면, platform이 정상 주문 instance까지 빼 버릴 수 있어요.
반대로 database가 끊기면 모든 API가 실패하는 application인데 단순 process 상태만 UP으로 내보내면, load balancer는 실패할 instance로 계속 요청을 보낼 수 있죠.
Details는 장애 원인에 가깝지만 공개 정보도 많아져요
권한이 있는 운영자에게 health details를 보여주면 어느 component가DOWN인지 빠르게 찾을 수 있어요. 하지만 database 제품, disk 상태, 외부 system 이름처럼 내부 topology를 추측할 단서도 늘어나요.
그래서 보통은 다음처럼 나눠요.
Health endpoint는 dashboard가 아니라 자동화가 행동을 결정하는 신호에 가까워요. 무엇을 넣을지는 “알고 싶은가?”보다 “이 결과를 받은 platform이 무엇을 할 것인가?”로 결정해야 해요.
Liveness와 readiness를 하나로 합치면 장애가 커질 수 있어요
Container platform은 health 실패 뒤에 실제 행동을 해요. 그래서 상태를 적어도 두 가지로 나눠야 해요.
Database 장애를 liveness에 넣었다고 생각해 볼게요. 모든 application instance가 같은 database를 봐요. Database가 잠깐 멈추면 모든 instance의 liveness가 실패하고, platform은 application을 한꺼번에 재시작해요. Database 장애는 그대로인데 재시작 부하까지 더해져요.
그래서 liveness는 application 내부가 복구 불가능하게 망가졌는지를 봐야 해요. 외부 system 실패를 그대로 넣으면 cascading failure를 만들 수 있어요.
Readiness는 조금 더 판단이 필요해요. Database 없이 어떤 요청도 처리할 수 없다면 해당 instance를 traffic에서 빼는 편이 자연스러울 수 있어요. 하지만 모든 instance가 공유하는 database라면 전부 routing에서 빠져 서비스가 완전히 사라질 수도 있죠. 일부 기능이 fallback으로 동작한다면 readiness를 유지하고 개별 요청에서 오류를 다루는 편이 나을 수도 있어요.
Spring Boot Actuator는 두 상태를 health group으로 제공해요.
별도 management port만 검사하면 생기는 빈틈
운영 endpoint를 application traffic과 다른 port로 분리하는 경우가 있어요.src/main/resources/application.yml
add-additional-paths: true를 켜면 main server port에도 /livez, /readyz가 추가돼요. Platform은 실제 traffic이 들어가는 web infrastructure까지 통과해 상태를 확인할 수 있어요.
Info는 “무슨 application이 떠 있나?”에 답해요
장애 대응 중에는 상태만큼 정체도 중요해요.“지금 이 instance에는 어느 commit으로 만든 artifact가 떠 있죠?”
/actuator/info는 application 이름, build version, commit 같은 배포 metadata를 보여줄 수 있어요. Spring Boot build plugin이 build info를 만들게 할 수도 있어요.
build.gradle
git.properties를 만들었다면 branch와 commit 정보도 연결할 수 있어요.
이 정보는 다음 질문에 유용해요.
- 일부 instance만 이전 version인지 확인해요.
- 배포 직후 오류율 변화와 release를 연결해요.
- Rollback 대상 artifact를 빠르게 식별해요.
Metrics는 한순간이 아니라 변화의 모양을 보여줘요
Health가UP이어도 요청 시간이 100ms에서 5초로 늘어날 수 있어요. 오류가 아니더라도 thread와 connection pool이 거의 다 찼을 수 있죠. 이런 현상은 한 번의 상태 확인보다 시간에 따라 쌓인 측정값(metric)으로 봐야 해요.
Spring Boot Actuator는 Micrometer를 통해 meter를 등록해요. Micrometer는 application code와 Prometheus, OTLP, Datadog 같은 monitoring backend 사이에서 공통 계측 모델을 제공해요.
Actuator를 붙이면 JVM memory와 GC, process와 system, application 시작 시간, Spring MVC 요청 같은 여러 meter가 자동으로 등록돼요. 예를 들어 Spring MVC 요청은 기본적으로
http.server.requests라는 이름으로 측정돼요.
/metrics는 탐색용이고 /prometheus는 수집용이에요
두 endpoint의 역할을 자주 바꿔 읽어요.
metrics endpoint는 현재 등록된 meter 이름을 찾고, measurement와 사용 가능한 tag를 좁혀 보는 진단 도구예요. Production monitoring backend처럼 주기적으로 긁어 history를 저장하도록 만든 endpoint는 아니에요.
Prometheus가 읽을 대상은 다음 endpoint예요.
MeterRegistry의 측정값이지만 소비자가 달라요. 개발자는 metrics로 meter 구조를 확인하고, Prometheus는 exporter endpoint를 반복해서 scrape해 시간축을 만들어요.
Prometheus 설정은 대략 다음 모양이에요.
prometheus.yml
이름이 달라 보여도 같은 meter일 수 있어요
Micrometer 안에서는http.server.requests처럼 점으로 구분된 이름을 써요. Prometheus 형식에서는 naming convention과 meter type에 따라 http_server_requests_seconds_count 같은 이름으로 바뀔 수 있어요.
그래서 /actuator/metrics를 조회할 때 Prometheus에서 본 이름을 그대로 넣으면 못 찾을 수 있어요. 진단 endpoint에서는 code와 registry에 등록된 원래 meter 이름을 써야 해요.
직접 metric을 만들기 전에 운영 질문부터 정해요
기본 HTTP, JVM, database pool metric으로 답할 수 없는 business 질문도 있어요.“결제 승인 뒤 실제 주문 생성은 몇 건 성공했나요?”이때 custom counter를 만들 수 있어요.
src/main/java/com/example/order/OrderMetrics.java
Tag는 검색 조건이면서 시계열 개수예요
channel=web, result=success처럼 제한된 값은 metric을 나눠 보는 데 유용해요. 하지만 다음 값은 tag로 넣으면 안 돼요.
userIdorderId- 전체 request URL
- 자유 형식 error message
- 제한 없이 늘어나는 tenant 이름
Dashboard보다 먼저 어떤 행동을 할지 정해요
Metric을 많이 모았다고 관측 가능성(observability)이 저절로 생기지는 않아요. 운영자가 답할 질문과 대응 행동이 있어야 해요. 웹 API라면 먼저 다음 네 가지 축을 잡을 수 있어요.
Alert도 “CPU가 잠깐 높다”보다 사용자가 실제로 겪는 실패와 가까운 신호에서 시작하는 편이 좋아요.
- 일정 시간 동안 5xx 비율이 기준을 넘었어요.
- p95 또는 p99 latency가 service 목표를 오래 벗어났어요.
- Readiness에서 빠지는 instance가 계속 늘어요.
- Connection pool 대기가 늘면서 요청 시간이 함께 나빠져요.
운영 공개 범위는 endpoint마다 다르게 설계해요
한 가지 설정을 모든 환경에 복사하기보다 소비자와 위험을 기준으로 나누는 편이 안전해요.
별도 management port, firewall, network policy, Spring Security는 경쟁 관계가 아니에요. 서로 다른 층에서 노출 범위를 줄여요. 중요한 것은 “Actuator니까 안전하다”가 아니라 누가 어느 경로로 어떤 operation까지 실행할 수 있는지를 실제 배포 환경에서 확인하는 일이에요.
배포 전에 직접 확인할 것
200으로 만드는 것이 목표는 아니에요. 공개 health는 작은 응답을 내고, 운영자 endpoint는 인증 없이 거절되며, Prometheus 경로는 monitoring network에서만 성공하는지처럼 의도한 주체에게 의도한 결과가 나오는지 확인해야 해요.
또 health contributor가 외부 system을 검사한다면 응답 시간도 확인하세요. Health check 자체가 느리거나 매번 비싼 query를 실행하면 platform probe가 timeout되고, 실제 application 상태보다 검사 방식 때문에 재시작과 routing 제외가 반복될 수 있어요.
참고한 링크
- Spring Boot 공식 문서: Actuator Endpoints
- Spring Boot 공식 문서: HTTP를 통한 Monitoring과 Management
- Spring Boot 공식 문서: Metrics
- Spring Boot 공식 문서: Actuator Metrics API
- Spring Boot 공식 문서: Actuator Prometheus API
- Spring Boot 공식 문서: Application Availability
- Spring Boot 공식 문서: Common Application Properties
- Micrometer 공식 문서: Meter와 Registry 개념
자, 정리해볼까요?
- Actuator는 실행 중 application의 health, info, metrics 같은 운영 입구를 자동 설정하지만, dependency를 넣었다고 모든 endpoint가 HTTP에 공개되는 것은 아니에요.
- Health는 “모든 기능이 완벽하다”는 인증서가 아니라, contributor와 집계 정책으로 만든 현재 상태 판단 신호예요.
- Liveness 실패는 재시작, readiness 실패는 routing 제외로 이어지므로 외부 dependency를 어느 probe에 넣을지 행동부터 생각해야 해요.
- 별도 management port를 쓸 때 main traffic port의
/livez,/readyz도 확인하면 관리 port만 살아 있는 false positive를 줄일 수 있어요. info는 실행 중인 build와 commit을 식별하고,metrics는 등록된 meter를 진단하며,/prometheus는 monitoring system이 scrape할 시계열 형식을 제공해요.- Custom metric은 business 경계에서 기록하고, 무한히 늘어나는 ID를 tag로 넣어 높은 cardinality를 만들지 않아야 해요.
- 운영 endpoint는 소비자별로 최소 노출하고, network와 인증과 operation 권한을 함께 설계해야 해요.