본문 바로가기
Infra2026년 8월 21일7분 읽기

쿠버네티스 프로브 튜닝 — liveness·readiness·startup 제대로 나누기

YS
김영삼
조회 3
쿠버네티스 프로브 튜닝 — liveness·readiness·startup 제대로 나누기

쿠버네티스 프로브는 컨테이너가 살아있는지, 트래픽을 받을 준비가 됐는지, 부팅이 끝났는지를 kubelet이 주기적으로 확인하는 헬스체크다. 종류는 세 가지 — liveness(죽었으면 재시작), readiness(준비 안 됐으면 서비스에서 제외), startup(느린 초기화 동안 나머지 둘을 잠재움). 이름이 비슷해서 헷갈리지만 하는 일은 완전히 다르고, 잘못 걸면 멀쩡한 앱을 스스로 죽이는 사고가 난다.

솔직히 나도 처음엔 세 개를 다 똑같은 /health 엔드포인트로 물려놨다. 그러다 한 번 크게 데였다. 그 얘기부터 하겠다.

내가 겪은 무한 재시작 사고

어느 날 배포한 API 서버가 5분에 한 번씩 재시작을 반복했다. 로그를 보면 앱은 정상, 그런데 kubectl get pod의 RESTARTS 숫자만 계속 올라갔다. 원인은 liveness 프로브를 DB 커넥션까지 확인하는 엔드포인트에 걸어둔 것이었다.

DB가 잠깐 느려지면 liveness가 실패한다. 그러면 kubelet이 "이 컨테이너 죽었네" 하고 프로세스를 죽이고 새로 띄운다. 하지만 앱 자체는 멀쩡했다. DB가 느린 순간에 앱을 재시작해봤자 문제는 그대로고, 오히려 커넥션 풀만 다시 채우느라 상황이 악화됐다. 재시작이 재시작을 부르는 악순환.

liveness는 "이 프로세스를 죽이고 새로 띄우면 나아지는가?"를 물어야 한다. 외부 의존성(DB, 캐시, 다른 API) 상태는 절대 넣으면 안 된다.

세 프로브의 역할을 한 번에 정리

프로브실패하면무엇을 확인하나
liveness컨테이너 재시작프로세스가 데드락·행 상태인가 (자기 자신만)
readinessService 엔드포인트에서 제외지금 요청을 받아도 되는가 (의존성 포함 OK)
startup컨테이너 재시작초기화가 끝났는가 (끝나면 비활성화)

핵심은 이거다. readiness가 실패하면 파드는 죽지 않고 Service의 로드밸런싱 대상에서만 빠진다. 트래픽을 안 받으니 사용자에게는 영향이 없고, 회복되면 다시 돌아온다. 반면 liveness와 startup은 실패하면 컨테이너를 죽인다. 그래서 외부 의존성 체크는 readiness에만 넣는 게 원칙이다.

startup 프로브가 해결하는 것

JVM 앱이나 마이그레이션을 도는 서버는 부팅에 40초, 길면 2분도 걸린다. startup 프로브가 없던 시절엔 liveness의 initialDelaySeconds를 넉넉하게 줘서 버텼는데, 이게 애매했다. 너무 짧으면 부팅 중에 죽고, 너무 길면 진짜 행이 걸렸을 때 감지가 늦어진다.

startup 프로브는 이 딜레마를 깔끔하게 없앤다. startup이 성공할 때까지 liveness와 readiness는 아예 돌지 않는다. 그러니 startup에는 부팅용으로 관대한 타이밍을, liveness에는 운영용으로 촘촘한 타이밍을 따로 줄 수 있다.

livenessProbe:
  httpGet:
    path: /healthz     # 자기 자신만 확인, 의존성 없음
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3  # 30초간 실패해야 재시작
readinessProbe:
  httpGet:
    path: /readyz      # DB·캐시 연결까지 확인해도 됨
    port: 8080
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 3
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 5
  failureThreshold: 30 # 5초 x 30 = 최대 150초까지 부팅 허용

여기서 startup의 failureThreshold * periodSeconds가 앱이 뜨는 데 걸리는 최대 시간의 상한이다. 위 예시는 150초. 이 시간 안에 한 번이라도 성공하면 startup은 끝나고, 그때부터 liveness가 30초 룰로 감시를 시작한다.

타이밍 파라미터, 실전 감각

수치를 정할 때 내가 쓰는 기준을 적어둔다. 정답은 없지만 출발점으로는 쓸 만하다.

  • timeoutSeconds는 1로 두지 마라. 기본값 1초는 GC 한 번, 잠깐의 네트워크 지터에도 걸린다. HTTP 프로브는 최소 2~3초를 준다.
  • liveness failureThreshold는 3 이상. 한 번의 실패로 재시작하면 순간적인 부하 스파이크에 취약하다. 연속 실패를 요구해서 일시적 흔들림을 흡수한다.
  • readiness periodSeconds는 짧게(3~5초). 회복을 빨리 감지해서 트래픽을 다시 받아야 하니까.
  • graceful shutdown과 readiness를 엮어라. SIGTERM을 받으면 먼저 readiness를 실패로 돌려 신규 트래픽을 끊고, 진행 중 요청을 마무리한 뒤 종료한다. 이게 무중단 배포의 숨은 핵심이다.

프로브 종류로 exec(명령 실행)도 있는데, 개인적으로는 HTTP나 TCP를 선호한다. exec는 매번 프로세스를 fork하기 때문에, 촘촘한 주기로 걸면 CPU를 은근히 갉아먹는다. 특히 파드 밀도가 높은 노드에서 체감된다.

흔한 실수 모음

몇 번 데인 뒤에 정리한 체크리스트다. liveness에 의존성을 넣지 않았는가. startup 없이 initialDelay로 버티고 있진 않은가. timeout이 1초로 방치돼 있진 않은가. readiness가 SIGTERM 때 곧바로 실패로 바뀌는가. 이 네 개만 지켜도 재시작 관련 새벽 알람의 절반은 사라진다.

자주 묻는 질문

liveness와 readiness를 같은 엔드포인트로 써도 되나요?

권장하지 않습니다. readiness가 DB 장애로 실패하는 순간 liveness도 같이 실패해서 컨테이너가 재시작되고, 재시작은 DB 장애를 전혀 해결하지 못하므로 상황만 악화됩니다. 최소한 liveness는 외부 의존성이 없는 가벼운 엔드포인트로 분리하세요.

startup 프로브는 언제 꼭 필요한가요?

부팅이 느리고 그 편차가 큰 앱, 예를 들어 JVM 워밍업이나 시작 시 마이그레이션을 도는 서버에 필요합니다. 부팅이 몇 초로 일정하고 짧다면 startup 없이 liveness의 initialDelaySeconds만으로 충분합니다.

readiness가 실패하면 파드가 재시작되나요?

아니요. readiness 실패는 파드를 Service의 로드밸런싱 대상에서 제외할 뿐, 컨테이너를 죽이지 않습니다. 이 차이가 세 프로브를 구분하는 가장 중요한 지점입니다.

failureThreshold와 periodSeconds는 어떻게 정하나요?

둘을 곱한 값이 "이 상태를 얼마나 견딜지"의 총 시간입니다. liveness는 너무 예민하지 않게 연속 3회(약 30초) 정도, startup은 앱의 최대 부팅 시간을 상한으로 잡아 넉넉하게 계산하는 것을 추천합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록