- Published on
EKS Pod 1분마다 재시작? livenessProbe 실패 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 Pod가 Running으로 보이고 애플리케이션 로그도 얼핏 정상인데, 정확히 30초~1분 주기로 컨테이너가 재시작되는 경우가 있습니다. 이때 kubectl get pods에서 RESTARTS 숫자만 계속 올라가고, 서비스 트래픽은 간헐적으로 끊기거나(혹은 운 좋게는 안 끊겨 보이기도) 합니다.
이 현상의 핵심은 쿠버네티스가 “살아있지 않다(liveness 실패)”고 판단해 컨테이너를 죽이고 다시 띄우는 것입니다. 즉, Pod가 “정상처럼 보이는” 건 스케줄링/런타임 관점이고, “1분마다 재시작”은 kubelet의 헬스체크 로직 관점에서 벌어집니다.
이 글에서는 다음을 목표로 합니다.
livenessProbe실패인지 빠르게 판별- 실패 원인을 네트워크/리스너/리소스/애플리케이션/노드 관점으로 분류
- EKS에서 자주 나오는 함정(iptables, sidecar, slow start, GC stop-the-world 등)에 대한 해결책 제공
- probe를 “죽이기 위한 장치”가 아니라 “복구를 위한 장치”로 설계하는 방법
> Readiness 문제와 헷갈린다면 함께 참고: EKS에서 Readiness 실패인데 로그는 정상일 때
1) 먼저 확인: 정말 livenessProbe 때문에 재시작하나?
가장 먼저 할 일은 이 재시작이 kubelet에 의해 발생했는지(liveness 실패), 아니면 프로세스 크래시/OOMKill인지 구분하는 것입니다.
1-1. Pod 이벤트에서 “Liveness probe failed” 찾기
kubectl -n <ns> describe pod <pod-name>
아래와 같은 이벤트가 보이면 거의 확정입니다.
Liveness probe failed: HTTP probe failed with statuscode: 500Liveness probe failed: dial tcp ...: connect: connection refusedLiveness probe failed: context deadline exceeded- 그리고 이어서
Killing container .../Back-off restarting failed container등
1-2. 컨테이너 종료 사유 확인 (OOMKill/ExitCode)
kubectl -n <ns> get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason} {"\n"}{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'
OOMKilled면 liveness가 아니라 메모리 제한 초과가 1순위Error+ exit code가 비정상(예: 137, 143, 1 등)이면 앱 프로세스 자체 크래시 가능- 반면 liveness로 죽은 경우는 이벤트에서 단서가 더 명확히 나옵니다.
2) “1분마다” 재시작되는 이유: probe 타이밍의 합
livenessProbe가 실패하면 즉시 죽는 게 아니라, 보통 아래 값들의 조합으로 “주기”가 만들어집니다.
periodSeconds: 체크 주기(기본 10초)timeoutSeconds: 응답 기다리는 시간(기본 1초)failureThreshold: 연속 실패 허용 횟수(기본 3회)
예를 들어 periodSeconds=10, failureThreshold=3이면 대략 30초 안에 “죽임”이 발생합니다. 여기에 재시작/초기화 시간, 이미지/볼륨/사이드카 준비 시간이 더해져 체감상 1분 주기로 보이기 쉽습니다.
이 “주기성” 때문에 많은 팀이 크론/스케줄러/오토스케일러를 의심하지만, 실제로는 probe 설정이 만든 리듬인 경우가 많습니다.
3) 원인 패턴 7가지 (증상 → 진단 → 해결)
아래는 EKS에서 특히 자주 나오는 livenessProbe 실패 원인을 패턴으로 정리한 것입니다.
3-1. 앱은 뜨는데 포트 리스닝이 늦다 (cold start/마이그레이션/캐시 로딩)
증상
- 초기 30~120초 동안
connection refused - 로그에는 “서버 시작 중…” 단계가 길게 존재
진단
kubectl -n <ns> logs <pod> -c <container> --previous
--previous는 “죽기 직전” 로그를 보게 해줘서 매우 중요합니다.
해결
- startupProbe를 도입해 초기 구간은 liveness 판단에서 제외
- 또는
initialDelaySeconds를 충분히 늘림
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 10초 주기라면 최대 300초까지 스타트업 허용
periodSeconds: 10
startupProbe가 성공하기 전까지는 livenessProbe가 컨테이너를 죽이지 않습니다. “초기 부팅이 느린 서비스”에서 사실상 정답에 가깝습니다.
3-2. 헬스 엔드포인트가 ‘의존성’(DB/Redis/외부 API)에 묶여 있다
증상
- 평소엔 OK인데, DB 커넥션 스파이크/Redis 장애/외부 API 지연 때마다 재시작
HTTP 500혹은timeout
진단
/healthz내부 로직을 확인: DB ping, 외부 API call, DNS resolve 등을 하는지- 장애 시점에 DB 연결이 폭증했다면 RDS 측도 확인 필요: RDS PostgreSQL too many connections 원인·해결
해결
- liveness는 “프로세스가 살아있는가”만 보게 단순화
- 의존성 체크는 readiness로 옮기거나, 별도의
/readyz로 분리
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
/livez는 “이 프로세스가 이벤트 루프/스레드 풀/GC로 완전히 멈췄는가” 정도만 판단하고, DB/캐시/외부 의존성은 /readyz에서 판단하는 것이 운영 안정성에 유리합니다.
3-3. timeoutSeconds가 너무 짧다 (특히 기본값 1초)
증상
- 간헐적으로
context deadline exceeded - 노드가 바쁘거나 GC가 도는 타이밍에만 실패
진단
- 애플리케이션에서 health handler의 p99 latency 확인
- 노드 리소스 압박(특히 CPU throttling) 여부 확인
kubectl -n <ns> top pod <pod>
kubectl -n <ns> top node
해결
timeoutSeconds를 2~5초로 늘리고,failureThreshold를 조정- CPU limit이 지나치게 낮아 throttling이 심하면 limit 상향 또는 제거 검토
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
헬스체크는 “빠르게 실패하라”가 아니라 “거짓 음성(false negative)을 줄여라”가 더 중요할 때가 많습니다.
3-4. probe가 잘못된 포트/경로/스킴(HTTP vs HTTPS)을 때린다
증상
- 항상 실패,
connection refused또는404/301/403 - 앱은 실제로 다른 포트에서 떠 있음
진단
Pod 내부에서 직접 curl로 확인합니다.
kubectl -n <ns> exec -it <pod> -c <container> -- sh
# 컨테이너에 curl이 없다면 wget/busybox로 대체
wget -qO- http://127.0.0.1:8080/healthz
해결
containerPort와 probe 포트 일치httpGet.scheme: HTTPS필요 여부 확인- Ingress 경로가 아니라 “컨테이너 로컬” 기준 경로로 설계
특히 ALB/Ingress에서 /health로 잘 되던 것을 그대로 probe에 넣으면, 컨테이너 내부 라우팅/리다이렉트 때문에 실패하는 케이스가 많습니다.
3-5. sidecar/mesh(istio/envoy) 또는 프록시가 헬스 요청을 가로챈다
증상
- 앱은 정상인데 probe만 실패
- mTLS/인증/리다이렉트로 401/403/503이 발생
진단
- 이벤트에
HTTP probe failed with statuscode: 403등 - 사이드카가 있는지 확인
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.containers[*].name}'
해결
- 서비스 메시 가이드에 따라 probe rewrite 기능 사용 또는
execprobe로 전환(프록시를 우회해 로컬 프로세스만 확인)
livenessProbe:
exec:
command: ["sh", "-c", "wget -qO- http://127.0.0.1:8080/livez >/dev/null"]
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
환경에 따라 exec probe는 도구(wget/curl) 유무가 변수이므로, 최소 이미지(distroless 등)를 쓴다면 별도 바이너리 포함 또는 TCP probe를 고려해야 합니다.
3-6. 노드 디스크/파일시스템 문제로 앱이 멈춘다 (로그 폭주, ephemeral storage)
증상
- 앱이 로그/임시파일을 많이 쓰는 순간부터 health timeout
- 노드에서
DiskPressure/Pod Evicted가 같이 보이기도 함
진단
- 노드 상태/이벤트 확인
kubectl describe node <node-name>
DiskPressure=True또는 이미지/로그로 디스크가 꽉 찬 흔적
해결
- 로그/캐시 경로를 emptyDir로 분리하고 사이즈 제한
- ephemeral-storage requests/limits 설정
- 노드 디스크 압박을 근본적으로 해결
이 주제는 별도 글에서 더 깊게 다룹니다: EKS 노드 디스크 부족 Evicted 폭주 해결 가이드
3-7. readiness/liveness를 혼동해서 “트래픽 차단” 대신 “재시작”을 유발한다
증상
- 일시적 과부하나 외부 의존성 지연이 오면 Pod가 재시작 → 더 큰 부하(재시작 폭풍)
- 오토스케일링과 맞물리면 장애가 증폭
진단
- liveness가 과도한 조건(DB ping, 외부 API call 등)을 포함하는지 확인
해결
- readiness는 트래픽 유입 제어, liveness는 프로세스 복구로 역할 분리
- 과부하 시에는 readiness만 fail → 엔드포인트에서 빠져서 회복 시간을 벌게 설계
서비스 엔드포인트 관점에서 문제가 보이면 함께 확인: EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때
4) 실전 트러블슈팅 체크리스트 (10분 내 원인 좁히기)
아래 순서대로 하면 “왜 1분마다 재시작하는지”를 빠르게 좁힐 수 있습니다.
- 이벤트 확인:
describe pod에서Liveness probe failed문구/상태코드/에러 메시지 확보 - 직전 로그 확인:
logs --previous로 죽기 전 상황(부팅 지연, 예외, GC, DB timeout 등) 확인 - Pod 내부에서 직접 호출:
exec로127.0.0.1:<port>/healthz확인(경로/포트/스킴 검증) - 리소스 확인:
top pod/node로 CPU throttling/메모리 압박 확인 - 노드 이벤트 확인:
describe node로 DiskPressure/네트워크 플러그인 이슈 단서 확인 - probe 설계 재검토: liveness가 의존성에 묶였는지, timeout이 너무 짧은지, startupProbe가 필요한지
5) 권장 probe 설계 템플릿 (운영 친화형)
마지막으로, EKS 운영에서 재시작 폭주를 줄이는 “안전한 기본값” 예시를 제시합니다. 서비스 특성에 맞게 조정하되, 방향성은 다음과 같습니다.
startupProbe로 부팅 구간 보호liveness는 단순하고 빠르게(외부 의존성 X)readiness로 트래픽 제어(의존성 포함 가능)timeoutSeconds는 1초에 고정하지 말고 p99 기준으로 설정
containers:
- name: app
image: <image>
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 5
failureThreshold: 24 # 최대 120초 부팅 허용
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1
결론
EKS에서 “Pod는 정상인데 1분마다 재시작”은 대부분 **livenessProbe가 너무 공격적이거나(타이밍/timeout), 잘못된 대상(포트/경로/프록시), 혹은 잘못된 의미(의존성 체크)**를 담고 있어서 발생합니다.
해결의 핵심은 다음 3가지입니다.
describe pod이벤트와logs --previous로 실패 메시지를 먼저 확보startupProbe도입 +liveness/readiness역할 분리로 거짓 음성을 줄이기- 노드/리소스/디스크 압박 같은 환경 요인까지 함께 점검
원하시면, 실제 describe pod 이벤트/현재 probe YAML/헬스 엔드포인트 코드(또는 프레임워크: Spring Boot, FastAPI, NestJS 등)를 공유해 주시면 케이스에 맞춰 “최소 변경으로 재시작 멈추는” 튜닝 값을 구체적으로 잡아드릴 수 있습니다.