Actuator — 운영 중인 애플리케이션의 상태를 어떻게 확인할까
운영 중인 서버의 메모리가 충분한지, DB 연결이 살아 있는지, API 응답 시간은 괜찮은지를 어떻게 확인할 수 있을까요? 서버에 SSH로 접속해서 일일이 확인해야 하나요?
개념 정의
Spring Boot Actuator 는 운영 중인 애플리케이션의 상태를 모니터링하고 관리할 수 있는 HTTP 엔드포인트와 JMX를 제공하는 모듈입니다. 헬스 체크, 메트릭, 설정 정보, 빈 목록 등을 API로 조회할 수 있습니다.
왜 필요한가
운영 환경에서는 다음 질문에 즉시 답할 수 있어야 합니다.
- 서버가 정상 가동 중인가? (헬스 체크)
- DB, Redis, 메시지 큐와의 연결이 살아 있는가?
- 메모리 사용량은 괜찮은가? GC가 너무 자주 일어나지 않는가?
- API 응답 시간이 느려지지 않았는가?
- 어떤 설정값으로 동작하고 있는가?
Actuator는 이 모든 정보를 HTTP API로 제공합니다. Kubernetes의 liveness/readiness probe, Prometheus + Grafana 모니터링의 기반이 됩니다.
내부 동작
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
주요 엔드포인트
| 엔드포인트 | 설명 | 기본 노출 |
|---|---|---|
/health | 애플리케이션 상태 | 웹 ✓ |
/info | 애플리케이션 정보 | 웹 ✓ |
/metrics | 메트릭 목록 | 웹 ✗ |
/env | 환경 변수 & 프로퍼티 | 웹 ✗ |
/beans | 등록된 빈 목록 | 웹 ✗ |
/configprops | @ConfigurationProperties 목록 | 웹 ✗ |
/loggers | 로거 레벨 조회/변경 | 웹 ✗ |
/threaddump | 스레드 덤프 | 웹 ✗ |
/heapdump | 힙 덤프 파일 | 웹 ✗ |
/conditions | Auto-Configuration 적용 조건 | 웹 ✗ |
/shutdown | 애플리케이션 종료 | 비활성화 |
엔드포인트 노출 설정
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus # 특정 엔드포인트만 노출
# include: "*" # 모든 엔드포인트 노출 (주의!)
exclude: env,beans # 특정 엔드포인트 제외
코드 예제
/health 상세 설정
management:
endpoint:
health:
show-details: always # always | when_authorized | never
show-components: always
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "MySQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963174912,
"free": 350000000000,
"threshold": 10485760
이어서 나머지 구현을 완성합니다.
}
},
"redis": {
"status": "UP",
"details": {
"version": "7.2.0"
}
}
}
}
커스텀 HealthIndicator
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final RestClient restClient;
public ExternalApiHealthIndicator(RestClient restClient) {
this.restClient = restClient;
}
@Override
public Health health() {
try {
ResponseEntity<Void> response = restClient.get()
.uri("/health")
.retrieve()
.toBodilessEntity();
이어서 응답 객체를 구성하여 클라이언트에 반환하는 부분입니다.
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("status", "정상")
.build();
}
return Health.down()
.withDetail("status", response.getStatusCode().value())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
/health 응답에 externalApi 항목이 자동으로 추가됩니다.
Kubernetes Probes
management:
endpoint:
health:
probes:
enabled: true # liveness, readiness 그룹 활성화
health:
livenessState:
enabled: true
readinessState:
enabled: true
# Kubernetes deployment.yml
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
/metrics 사용
GET /actuator/metrics
→ 사용 가능한 메트릭 이름 목록
GET /actuator/metrics/jvm.memory.used
→ JVM 메모리 사용량
GET /actuator/metrics/http.server.requests
→ HTTP 요청 통계
GET /actuator/metrics/http.server.requests?tag=uri:/api/users&tag=status:200
→ 특정 URI, 상태 코드로 필터링
Micrometer 커스텀 메트릭
@Service
public class OrderService {
private final Counter orderCounter;
private final Timer orderTimer;
public OrderService(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders.created")
.description("생성된 주문 수")
.tag("type", "total")
.register(registry);
이어서 나머지 구현 부분입니다.
this.orderTimer = Timer.builder("orders.processing.time")
.description("주문 처리 시간")
.register(registry);
}
public Order createOrder(OrderRequest request) {
return orderTimer.record(() -> {
Order order = processOrder(request);
orderCounter.increment();
return order;
});
}
}
Prometheus 연동
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: health,prometheus
prometheus:
metrics:
export:
enabled: true
GET /actuator/prometheus로 Prometheus 형식의 메트릭을 조회할 수 있습니다.
엔드포인트 보안 설정
@Configuration
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/actuator/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll() // 헬스 체크는 공개
.requestMatchers("/actuator/prometheus").permitAll() // Prometheus 스크래핑
.anyRequest().hasRole("ADMIN") // 나머지는 관리자만
)
.httpBasic(Customizer.withDefaults())
.build();
}
}
별도 포트로 분리
management:
server:
port: 9090 # Actuator를 다른 포트에서 운영
address: 127.0.0.1 # 로컬에서만 접근 가능
이렇게 하면 애플리케이션은 8080, Actuator는 9090 포트에서 동작합니다. 네트워크 방화벽으로 외부 접근을 차단하기 쉽습니다.
런타임 로그 레벨 변경
# 현재 로그 레벨 확인
curl http://localhost:8080/actuator/loggers/com.example
# 로그 레벨 동적 변경 (서버 재시작 없이)
curl -X POST http://localhost:8080/actuator/loggers/com.example \
-H "Content-Type: application/json" \
-d '{"configuredLevel":"DEBUG"}'
운영 중 디버깅이 필요할 때 재배포 없이 로그 레벨을 변경할 수 있습니다.
주의할 점
1. Actuator 엔드포인트를 전체 공개하면 민감한 내부 정보가 노출된다
management.endpoints.web.exposure.include=*로 설정하면 /actuator/env에서 환경 변수, /actuator/configprops에서 설정 값, /actuator/heapdump에서 JVM 힙 덤프까지 외부에 노출됩니다. DB 비밀번호, API 키 등이 포함될 수 있으므로, 필요한 엔드포인트만 선별적으로 공개하고 별도 포트(management.server.port)로 분리하세요.
2. health 엔드포인트에 외부 의존성을 모두 포함하면 헬스체크가 불안정해진다
/actuator/health에 DB, Redis, 외부 API 상태를 모두 포함하면, 외부 서비스의 일시적 장애로 헬스체크가 DOWN을 반환합니다. 로드밸런서가 이를 감지하여 정상 인스턴스를 트래픽에서 제외하면, 서버 자체는 멀쩡한데 서비스가 중단됩니다. liveness와 readiness 프로브를 분리하고, 외부 의존성은 readiness에만 포함하세요.
3. /actuator/shutdown을 인증 없이 활성화하면 외부에서 서버를 종료할 수 있다
management.endpoint.shutdown.enabled=true는 POST 요청 하나로 애플리케이션을 종료시킵니다. 인증 없이 이 엔드포인트가 노출되면 외부 공격자가 서비스를 중단시킬 수 있습니다. 반드시 Spring Security로 접근을 제한하거나 비활성화 상태로 유지하세요.
정리
- Actuator 는 운영 중인 애플리케이션의 상태, 메트릭, 설정 정보를 HTTP API로 제공합니다
/health는 Kubernetes probe, 로드밸런서 헬스 체크에 필수입니다- ** 커스텀 HealthIndicator**로 외부 의존성 상태를 헬스 체크에 포함할 수 있습니다
- Micrometer + Prometheus 로 메트릭을 수집하고 Grafana로 시각화하는 것이 운영 표준입니다
- Actuator 엔드포인트는 민감 정보를 포함하므로 보안 설정이 필수 입니다
- 별도 포트로 분리하면 네트워크 레벨에서 접근을 제어할 수 있습니다