처음에는 Startup과 Readiness, Liveness Probe가 같은 health endpoint를 호출해도 충분하다고 생각했습니다. 하지만 초기화가 오래 걸리는 정상 Pod와 외부 DB 연결이 끊긴 Pod가 같은 실패로 처리됐습니다. 그 결과 정상 프로세스가 반복해서 재시작되거나 아직 준비되지 않은 Pod로 요청이 전달됐습니다. 세 Probe가 묻는 질문과 실패 효과를 분리해야 했습니다.
하나의 Health 응답이 서로 다른 장애를 섞었다
migration과 캐시 준비 때문에 시작이 느린 서비스에 liveness가 너무 일찍 적용되어 정상 부팅 중인 컨테이너가 반복 종료됐습니다. readiness가 외부 의존성 하나만 실패해도 내려가면서 모든 트래픽이 동시에 다른 Pod로 몰리기도 했습니다. 또 프로세스는 실행 중이고 health endpoint도 200을 반환하지만, migration이나 cache 준비가 끝나지 않아 실제 요청을 처리할 수 없는 상태도 있었습니다.
세 Probe에 다른 질문을 할당했다
startup은 애플리케이션이 초기화를 끝냈는지, readiness는 지금 트래픽을 받을 수 있는지, liveness는 프로세스 재시작만이 회복 방법인지를 묻도록 나눴습니다. 외부 DB가 잠시 느리다는 이유로 liveness를 실패시키지 않았습니다. 같은 health 함수의 결과를 세 endpoint에서 재사용하면 이 의미가 다시 섞이기 쉬웠습니다.
- startup은 애플리케이션 초기화 완료 여부를 묻습니다.
- readiness는 지금 새 트래픽을 받을 수 있는지를 판단합니다.
- liveness는 재시작만이 회복 방법인 교착 상태만 감지합니다.
Pod가 시작되고 종료될 때의 판정 순서
startupProbe에는 실제 최대 부팅 시간을 포괄하는 failureThreshold와 periodSeconds를 설정했습니다. startup이 성공하기 전에는 liveness와 readiness 판단이 시작되지 않습니다.
readiness endpoint는 서버 초기화와 필수 내부 자원 상태를 확인하되 짧은 외부 API 장애를 무조건 Pod 재시작으로 연결하지 않습니다. 종료 시에는 먼저 readiness를 내려 새 요청을 막습니다. liveness는 가벼운 내부 이벤트 루프와 핵심 스레드 상태만 확인해 DB 지연 같은 외부 문제로 프로세스를 계속 재시작하지 않게 했습니다.
startupProbe:
httpGet: { path: /health/startup, port: 8080 }
periodSeconds: 5
failureThreshold: 60
readinessProbe:
httpGet: { path: /health/readiness, port: 8080 }
livenessProbe:
httpGet: { path: /health/liveness, port: 8080 }
fun liveness() = Health.up().build()
fun readiness() = if (requiredWorkersStarted.get()) Health.up().build()
else Health.outOfService().build()
startup 실패 -> 아직 시작 못함, 재시작 가능 readiness 실패 -> Pod 유지, 신규 트래픽 제외 liveness 실패 -> kubelet이 container 재시작
Startup·Readiness·Liveness의 실패 효과
| Probe | 확인 질문 | 실패 시 동작 |
|---|---|---|
| Startup | 애플리케이션 초기화가 끝났는가 | 완료 전까지 다른 probe 판단을 유예 |
| Readiness | 지금 트래픽을 받을 수 있는가 | Service endpoint에서 제외 |
| Liveness | 프로세스가 복구 불가능하게 멈췄는가 | container 재시작 |
DB 장애와 느린 시작을 분리해 재현했다
초기화 시간이 긴 배포와 실행 중 DB 단절을 별도 실험으로 만들었습니다. 전자에서는 startup이 기다리는 동안 liveness가 개입하지 않아야 하고, 후자에서는 readiness만 내려 기존 연결을 정리해야 했습니다. restart event와 endpoint 제거 시점을 함께 보며 자동 조치가 의도와 맞는지 확인했습니다.
- DB 장애를 liveness 실패로 판단해 모든 pod가 동시에 재시작하는 문제.
- startup 시간보다 짧은 liveness로 정상 초기화 중 kill loop에 빠지는 문제.
- readiness successThreshold와 termination drain이 맞지 않아 종료 pod로 요청이 가는 문제.
Restart 수보다 잘못된 자동 조치를 봤다
- 느린 migration, DB 단절, 스레드 교착을 각각 구분해 장애 주입합니다.
- probe timeout이 평소 지연 분포보다 지나치게 짧지 않은지 봅니다.
- 롤링 배포에서 최소 ready Pod 수와 termination grace를 함께 검증합니다.
- 초기화 지연, DB 단절, event loop 교착을 서로 다른 실험으로 주입합니다.
- probe timeout은 평시 p99보다 충분히 크되 장애 감지 목표 안에 둡니다.
- Deployment rollout 중 available replica와 5xx가 기대 범위인지 확인합니다.
정상 Pod의 재시작을 줄인 뒤 남은 조정
정상적으로 오래 걸리는 시작이 재시작으로 방해받지 않았고 준비가 끝난 뒤에만 트래픽이 들어왔습니다. 외부 의존성 장애가 전체 Pod의 동시 재시작으로 증폭되는 현상도 줄었습니다.
Probe 기준이 느슨하면 장애 감지가 늦고, 지나치게 엄격하면 정상 pod를 계속 교체합니다. 시작 시간과 종료 drain, 외부 의존성의 실제 장애 패턴을 관찰하며 세 probe의 threshold를 따로 조정했습니다.
Health Check를 추가하기 전에 묻는 것
Startup은 초기화가 끝났는지, Readiness는 지금 요청을 받을 수 있는지, Liveness는 재시작 외에는 회복할 수 없는지를 묻습니다. 이 질문을 하나로 합치면 DB 장애 같은 외부 문제까지 컨테이너 재시작으로 증폭시킬 수 있습니다.
새 health check를 추가할 때는 먼저 실패 시 Kubernetes가 어떤 행동을 할지 확인합니다. 트래픽 제외가 필요한 문제와 프로세스 교체가 필요한 문제를 구분해야 자동 복구가 서비스 장애를 키우지 않습니다.
답글 남기기