이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Liveness와 Readiness는 각각 어떤 운영 조치를 유도하는가?
  • Spring Actuator health를 트래픽 수신 판단에 어떻게 연결해야 하는가?
  • Health Check를 잘못 설계하면 어떤 장애가 생기는가?

개요

Health Check는 서비스가 살아 있는지와 요청을 받을 준비가 되었는지 확인하는 신호다.

Liveness는 프로세스를 다시 시작해야 하는지를 판단한다.

Readiness는 지금 트래픽을 받아도 되는지를 판단한다.

둘은 비슷해 보이지만 조치가 완전히 다르다.

Liveness를 너무 민감하게 만들면 재시작 폭주가 생기고, readiness를 너무 느슨하게 만들면 준비되지 않은 인스턴스가 사용자 요청을 받는다.

Liveness

Liveness는 애플리케이션이 더 이상 자체 회복하기 어려운 상태인지 보는 신호다.

예를 들어 main thread deadlock, process hang, event loop 정지 같은 상태가 대상이다.

작은 DB 지연이나 외부 API timeout을 liveness 실패로 만들면 재시작이 해결책이 아닌데도 프로세스를 계속 죽이게 된다.

curl -fsS http://localhost:8080/actuator/health/liveness

Liveness는 가볍고 안정적이어야 한다.

외부 의존성을 많이 포함하지 않는 편이 안전하다.

Readiness

Readiness는 지금 요청을 처리할 준비가 되었는지 보는 신호다.

curl -fsS http://localhost:8080/actuator/health/readiness

DB connection, migration 완료 여부, Redis 연결, 필수 config 로딩 같은 조건을 포함할 수 있다.

하지만 모든 외부 API를 readiness에 넣으면 작은 dependency 장애가 전체 트래픽 차단으로 이어질 수 있다.

핵심은 “트래픽을 받으면 사용자 요청이 의미 있게 처리되는가”다.

Spring 설정

Spring Boot Actuator는 liveness/readiness health endpoint를 제공할 수 있다.

management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.health.group.liveness.include=livenessState
management.endpoint.health.group.readiness.include=readinessState,db,redis

운영에서는 endpoint 접근 경로와 인증을 조심해야 한다.

Health detail을 외부에 그대로 노출하면 내부 의존성 정보가 새어 나갈 수 있다.

Health Indicator 설계

Health indicator는 가벼워야 한다.

매번 무거운 query를 실행하거나 외부 API를 깊게 호출하면 health check 자체가 부하가 된다.

DB는 connection 가능 여부와 가벼운 validation 수준으로 충분한 경우가 많다.

핵심 business API의 정상 여부는 smoke test나 synthetic check로 별도 확인하는 편이 낫다.

Health check는 “모든 기능이 완벽함”을 증명하는 장치가 아니다.

운영 체크리스트

  • Liveness와 readiness가 서로 다른 endpoint와 의미를 가지는가?
  • Liveness에 외부 dependency를 과도하게 넣지 않았는가?
  • Readiness가 DB, migration, 필수 config 등 트래픽 수신 조건을 반영하는가?
  • Health endpoint가 빠르고 가벼운가?
  • Health detail 노출 범위가 제한되어 있는가?
  • 배포 직후 readiness가 UP된 뒤에만 트래픽을 받는가?
  • Health check 실패 시 재시작, 트래픽 제외, 알림 중 어떤 조치인지 문서화되어 있는가?

장애 신호

  • Health는 UP인데 핵심 API는 DB connection timeout으로 실패한다.
  • Liveness가 외부 API 지연 때문에 실패해 애플리케이션이 반복 재시작된다.
  • Readiness가 너무 늦게 UP되어 정상 인스턴스가 오래 트래픽에서 제외된다.
  • Readiness가 너무 빨리 UP되어 migration 완료 전 요청을 받는다.
  • Health endpoint 호출이 느려 load balancer health check가 흔들린다.
  • Health detail에 DB host, internal service name이 외부로 노출된다.

안전한 완화 조치

Health check 장애가 보이면 먼저 실패한 endpoint가 liveness인지 readiness인지 구분한다.

Liveness 실패라면 실제 process hang인지, 잘못 포함된 dependency 지연인지 확인한다.

Readiness 실패라면 트래픽 제외가 의도한 완화인지 확인하고, 핵심 의존성 상태를 본다.

Health endpoint가 무거우면 check 로직을 단순화하고 별도 synthetic test로 분리한다.

운영 중 health 기준을 바꿀 때는 배포/트래픽 전환과 함께 관찰한다.

인프라 담당자와 공유할 자료

문제: 배포 직후 일부 instance traffic 제외
service: order-api
version: 1.8.3-abc1234
liveness: UP
readiness: DOWN, db migration pending
사용자 영향: capacity 30% 감소, 5xx 없음, p95 +80ms
요청: migration 완료 예상 시간과 traffic 재진입 기준 확인

Health check 공유에는 endpoint별 상태와 그 상태가 유도한 조치를 함께 적어야 한다.

실전 팁

  • Liveness는 재시작 기준, readiness는 트래픽 수신 기준이다.
  • Health check는 가벼워야 한다.
  • Readiness에는 migration 완료와 필수 config 상태를 고려한다.
  • 외부 dependency 장애를 모두 liveness 실패로 만들지 않는다.
  • Health detail은 외부 공개를 제한한다.

위험 신호!

  • Liveness와 readiness가 같은 로직이다.
  • 모든 dependency를 liveness에 넣는다.
  • readiness 없이 배포 직후 트래픽을 받는다.
  • health endpoint가 실제 API보다 무겁다.
  • Health가 UP이면 서비스 전체가 정상이라고 판단한다.

확인 질문

확인 질문

  • Liveness와 readiness의 차이는 무엇인가?
    • Liveness는 재시작 필요 여부이고 readiness는 트래픽 수신 가능 여부다.
  • 외부 API 지연을 liveness 실패로 만들면 왜 위험한가?
    • 재시작으로 해결되지 않는 문제 때문에 프로세스를 반복 재시작해 장애를 키울 수 있기 때문이다.
  • Health check와 smoke test를 분리해야 하는 이유는 무엇인가?
    • Health check는 빠르고 가벼운 상태 신호여야 하고, 핵심 business flow 검증은 더 무거운 smoke test로 다루는 편이 안전하기 때문이다.

참고 문서