새로 배포한 파드가 CrashLoopBackOff에 빠졌습니다. 근데 kubectl logs로 보면 앱은 정상적으로 부팅해서 "Started Application" 까지 찍혀요. 그러다 조금 지나면 컨테이너가 재시작되고 이걸 반복합니다.
Normal Killing kubelet Container app failed liveness probe, will be restarted
Warning Unhealthy kubelet Liveness probe failed: Get "http://10.1.2.3:8080/health": context deadline exceededliveness probe는 /health 로 잡았고 앱엔 그 엔드포인트 있습니다. 로컬에서 curl 하면 200 잘 나오고요. 왜 클러스터에서만 죽는 걸까요?
댓글 3
하나는 앱이 완전히 준비되기 전에 probe가 시작되는 경우. initialDelaySeconds가 기본 0이라 컨테이너 뜨자마자 때리는데, 스프링부트 같은 건 'Started' 찍히고도 실제 커넥션 받을 준비까지 시간이 더 걸리기도 해요. 그리고 probe의 timeoutSeconds 기본이 1초라, /health가 1초 안에 응답 못 하면 실패로 칩니다. 로컬 curl은 200이어도 클러스터에서 부하 있을 때 1초 넘기면 죽는 거예요.
요즘은 startupProbe를 따로 두는 걸 권장해요. startupProbe가 성공할 때까지 liveness/readiness를 아예 안 돌리거든요.
startupProbe:
httpGet: { path: /health, port: 8080 }
failureThreshold: 30
periodSeconds: 5
이러면 최대 150초까지 부팅을 기다려주고, 그 안에 한 번 뜨면 그때부터 liveness가 정상 주기로 돕니다. 그리고 liveness의 timeoutSeconds도 3~5초로 늘려주세요.
분리하세요. liveness는 의존성 안 보는 가벼운 엔드포인트(그냥 프로세스가 응답하는지, 예를 들어 /livez가 바로 200)로 두고, DB나 레디스 체크는 readiness probe(/readyz)에만 넣으세요. readiness가 실패하면 파드를 죽이는 게 아니라 서비스 엔드포인트에서만 잠깐 빼서 트래픽을 안 보내요. 의존성 회복되면 자동으로 다시 붙고요. 스프링부트 액추에이터면 liveness와 readiness 그룹이 이미 나뉘어 있으니 /actuator/health/liveness 랑 /actuator/health/readiness 를 각각 매핑하면 됩니다.