이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Liveness, readiness, startup probe, LB health check를 어떻게 나눠야 하는가?
- health check가 너무 얕거나 너무 깊으면 어떤 장애가 생기는가?
- Spring Boot Actuator와 배포 시스템에서 readiness를 어떻게 운영해야 하는가?
개요
Health check는 운영자가 “살아 있나?”를 묻는 단순 ping이 아니라 LB와 orchestrator가 traffic을 보낼지, 재시작할지, 배포를 진행할지 결정하는 신호다. 백엔드 개발자는 health endpoint가 무엇을 보장하고 무엇을 보장하지 않는지 명확히 해야 한다.
- Liveness는 프로세스를 재시작해야 하는지 판단한다.
- Readiness는 새 traffic을 받아도 되는지 판단한다.
- Startup probe는 긴 초기화 시간 동안 liveness 실패를 늦춘다.
- LB health check는 target을 routing pool에 넣거나 빼는 신호다.
원리
좋은 health 설계는 질문을 나눈다.
liveness: 이 프로세스는 죽었는가?
readiness: 지금 새 요청을 받아도 되는가?
deep health: 핵심 기능이 실제로 동작하는가?하나의 /health에 모든 의미를 넣으면 문제가 생긴다. DB가 잠깐 느려질 때 모든 pod가 unready가 되어 전체 트래픽이 끊기거나, 반대로 너무 얕은 health가 200이라 실제 기능 장애를 숨길 수 있다.
Probe별 설계 기준
| 신호 | 질문 | 실패 시 기대 동작 | 넣으면 좋은 것 | 넣으면 위험한 것 |
|---|---|---|---|---|
| Liveness | 프로세스가 복구 불가능하게 멈췄는가? | 재시작 | deadlock, event loop 멈춤 | 외부 DB/API 일시 장애 |
| Readiness | 새 traffic을 받아도 되는가? | routing 제외 | startup/warm-up, 필수 dependency | 부가 기능 dependency |
| Startup | 초기화가 아직 긴가? | liveness 지연 | migration, cache warm-up 시간 | 상시 운영 dependency |
| Deep health | 핵심 사용자 기능이 되는가? | 알람/관측 | synthetic transaction | LB routing 결정 단독 사용 |
핵심은 health endpoint 하나로 모든 운영 판단을 대신하지 않는 것이다. LB가 보는 신호와 사람이 보는 신호가 같을 필요는 없다.
너무 얕은 Health
return 200 OK만 하는 health는 process alive만 알려 준다. 다음 문제를 놓친다.
- DB migration 미완료
- message broker 연결 실패
- thread pool 포화
- dependency DNS/TLS 실패
- cache warm-up 전 상태
배포 직후 502/503이 짧게 튀는 경우 readiness가 너무 일찍 true가 되었을 수 있다.
너무 깊은 Health
모든 dependency를 readiness에 넣는 것도 위험하다.
- 공통 DB 장애 때 모든 target이 unready가 되어 LB가 보낼 곳이 없어진다.
- 외부 부가 기능 장애가 핵심 기능까지 중단시킨다.
- health check traffic이 dependency에 불필요한 부하를 만든다.
핵심은 “이 dependency가 없으면 이 target이 traffic을 받으면 안 되는가?”다. readiness와 별도로 deep check 또는 synthetic monitoring을 둘 수 있다.
Spring Boot 설정
Spring Boot Actuator probes를 켠다.
management:
endpoint:
health:
probes:
enabled: true
endpoints:
web:
exposure:
include: health,info,prometheusLB는 readiness에 가깝게, orchestrator liveness는 liveness에 가깝게 연결한다. readiness는 앱 startup, warm-up, graceful shutdown 시점과 함께 움직여야 한다.
코드와 명령으로 확인하기
health endpoint를 직접 확인한다.
curl -i https://api.example.com/actuator/health/liveness
curl -i https://api.example.com/actuator/health/readiness배포 직후 target이 ready가 되는 시각과 첫 사용자 요청 성공 시각을 비교한다.
10:00:00 pod started
10:00:15 readiness UP
10:00:16 LB target healthy
10:00:17 first user request 500
10:00:25 cache warm-up done이런 타임라인이 나오면 readiness가 warm-up 완료 전에 열렸다는 뜻이다.
인프라 협업 포인트
- LB health check path, interval, timeout, healthy/unhealthy threshold를 앱 팀과 공유한다.
- 앱 팀은 startup time, warm-up time, shutdown time, dependency policy를 알려야 한다.
- readiness false 전환이 LB target deregistration으로 얼마나 빨리 반영되는지 확인한다.
- health endpoint는 인증/WAF/rate limit에 막히지 않아야 하지만 외부 공개 범위는 제한해야 한다.
주니어가 자주 하는 오해
/health가 200이면 실제 API도 모두 정상이라고 생각한다.- DB가 느릴 때 liveness를 실패시켜 재시작하면 해결될 것이라 생각한다.
- readiness가 배포 완료 신호라고 보고 cache warm-up이나 consumer subscription을 따로 확인하지 않는다.
- health endpoint를 외부에 과하게 공개해 dependency 정보까지 노출한다.
시니어의 설계 판단 기준
- 재시작으로 회복되는 문제와 traffic 제외로 완화되는 문제를 분리한다.
- readiness는 배포, graceful shutdown, warm-up, autoscaling과 같은 시간축에서 본다.
- 공통 dependency 장애가 모든 target을 동시에 unready로 만들지 않는지 검토한다.
- deep health와 synthetic monitoring은 사용자 journey 검증으로 두되 LB routing 결정과 분리한다.
실전 팁
- readiness는 “새 요청을 받을 준비”이지 “모든 기능 완전 정상”이 아니다. 심층 기능 검사는 synthetic monitoring과 알람으로 분리한다.
- health response body는 외부에 과도한 dependency 정보를 노출하지 않는다.
- startup이 긴 앱은 startup probe나 initial delay로 liveness 재시작 루프를 피한다.
- readiness에 DB를 넣을 때는 connection pool 고갈과 DB 장애 시 전체 target 제거 위험을 함께 본다.
- 개인 프로젝트에서도
/healthz와/readyz를 나누면 배포 장애를 줄일 수 있다.
위험 신호!
- health endpoint가 항상 200만 반환한다.
- readiness가 true인데 migration, cache warm-up, consumer subscription이 끝나지 않았다.
- 외부 부가 API 장애가 readiness를 내려 전체 서비스를 막는다.
- health check interval/threshold가 배포 시간과 맞지 않는다.
확인 질문
확인 질문
- liveness와 readiness를 나누는 가장 큰 이유는 무엇인가?
- 재시작해야 하는 상태와 새 traffic을 받으면 안 되는 상태가 다르기 때문이다.
- readiness가 너무 깊으면 어떤 장애가 생길 수 있는가?
- 공통 dependency 장애 때 모든 target이 unready가 되어 전체 트래픽을 받을 target이 없어질 수 있다.
- health check 200이 실제 API 성공을 보장하지 않는 이유는 무엇인가?
- health path가 실제 route, dependency, auth, payload, DB transaction을 모두 검증하지 않을 수 있기 때문이다.