/인프라/Liveness/Readiness Probe 실패 6대 원인과 해결법: Pod 무한 재시작 잡기
인프라kubernetesliveness-probe

Liveness/Readiness Probe 실패 6대 원인과 해결법: Pod 무한 재시작 잡기

앱은 정상인데 Pod가 CrashLoopBackOff로 재시작되거나 503이 뜨는 원인은 Probe 설정입니다. Liveness·Readiness·Startup 차이부터 6대 실패 원인별 YAML 처방전과 5분 진단 체크리스트까지 정리했습니다.

Liveness/Readiness Probe 실패 6대 원인과 해결법: Pod 무한 재시작 잡기

"앱은 멀쩡한데 Pod가 계속 재시작돼요?" - Probe 설정이 오히려 독이 되는 이유

DevOps 엔지니어라면 한 번쯤 겪어봤을, 가장 당황스러운 상황이 있습니다. 애플리케이션 로그를 확인하면 모든 것이 정상적으로 동작하고, 테스트 환경에서는 완벽하게 작동하는데, 실제 Kubernetes 클러스터에 배포한 Pod만 유독 불안정합니다. kubectl get pods를 확인해보면 CrashLoopBackOffError 상태가 떠 있고, 심지어 Ready 상태가 0/1로 표시되어 트래픽을 전혀 받지 못하는 상황이죠.

대부분의 원인은 애플리케이션 코드의 버그가 아닙니다. 바로 Liveness ProbeReadiness Probe의 오해에서 비롯됩니다. 이 프로브들은 우리를 보호하기 위해 존재하지만, 잘못 설정하면 오히려 애플리케이션을 죽이는 '가짜 감시자'가 될 수 있습니다.

이 글에서는 단순한 에러 메시지 해석을 넘어, Probe 3형제의 동작 원리를 완벽히 분해하고, 현업에서 가장 빈번하게 발생하는 6가지 실패 원인과 이를 즉시 해결할 수 있는 YAML 처방전까지, 실전적인 가이드를 제공합니다.

Liveness, Readiness, Startup Probe 3형제: 동작 원리 완벽 분해

이 세 가지 프로브는 이름만 비슷할 뿐, Kubernetes 내부에서 수행하는 역할과 실패 시의 영향은 완전히 다릅니다. 이 차이를 이해하는 것이 문제 해결의 80%를 차지합니다.

Probe 종류목적실패 시 동작주요 영향
Liveness Probe컨테이너가 살아있는지(Alive) 확인 (치명적 오류 감지)실패 횟수 초과 시, K8s가 컨테이너를 강제 재시작시킵니다.Pod의 재시작 횟수(restarts) 증가, 서비스 중단 발생.
Readiness Probe컨테이너가 트래픽을 받을 준비가 되었는지(Ready) 확인실패 시, 해당 Pod의 IP를 Service Endpoint에서 제외합니다. (재시작하지 않음)트래픽이 Pod로 라우팅되지 않아 503 Service Unavailable 발생.
Startup Probe컨테이너가 초기 부팅을 완료했는지 확인실패 횟수 초과 시, Liveness/Readiness Probe 검사를 시작합니다.느린 부팅 앱이 Liveness/Readiness Probe에 의해 강제 종료되는 것을 방지합니다.

핵심 이해:

  • Liveness 실패 $\rightarrow$ 재시작 (Restart)
  • Readiness 실패 $\rightarrow$ 트래픽 차단 (Service Endpoint 제외)
  • Startup 실패 $\rightarrow$ 대기 후 검사 시작 (Liveness/Readiness 검사 자체를 지연)

🚨 진단 첫 단계: kubectl describe로 에러 메시지 읽는 법

문제가 생겼을 때 가장 먼저 해야 할 일은 감(感)이 아니라 증거(Evidence)를 찾는 것입니다. kubectl get pod <pod-name>에서 READY 컬럼이 0/1이거나, RESTARTS가 비정상적으로 높은 경우, 즉시 kubectl describe pod <pod-name>을 실행하세요.

여기서 주목해야 할 섹션은 EventsConditions입니다.

실제 에러 메시지 예시 및 해석:

  1. Liveness 실패 예시:

    Liveness probe failed: HTTP probe failed with statuscode: 503

    • 해석: 컨테이너는 살아있지만(재시작되지 않았을 수 있음), /healthz 엔드포인트가 503을 반환하며 "지금은 트래픽을 받을 수 없다"고 명시적으로 말하고 있습니다. (→ Readiness Probe 문제일 가능성 높음)
  2. Readiness 실패 예시:

    Readiness probe failed: Get "http://10.x.x.x:8080/healthz": dial tcp connection refused

    • 해석: 컨테이너가 해당 포트(8080)에서 아예 연결을 거부당했습니다. 포트가 열려있지 않거나, 앱이 아직 해당 포트를 바인딩하지 못한 상태입니다. (→ initialDelaySeconds 부족 또는 포트 오타 의심)
  3. Startup 실패 예시:

    Startup probe failed: ...

    • 해석: 부팅이 너무 느려서, Liveness/Readiness 검사가 시작되기도 전에 부팅 자체가 실패하고 있습니다. (→ startupProbe 추가가 시급합니다.)

🛠️ 현업에서 만나는 6대 Probe 실패 원인과 YAML 처방전

실제 운영 환경에서 가장 많이 부딪히는 6가지 시나리오를 '증상 $\rightarrow$ 원인 $\rightarrow$ 해결' 구조로 정리했습니다.

1. 초기 지연 시간 부족 (InitialDelaySeconds 부족)

  • 증상: 배포 직후 Pod가 즉시 실패하고 재시작 루프에 빠집니다.
  • 원인: 애플리케이션이 JVM 로딩이나 DB 연결 초기화에 시간이 걸리는데, Probe가 너무 빨리 검사를 시작합니다.
  • 해결: initialDelaySeconds를 충분히 늘려줍니다. (예: 30초)

2. 포트 또는 Path 오타/불일치

  • 증상: connection refused 또는 unknown endpoint 에러가 발생합니다.
  • 원인: YAML에 명시된 포트(예: 8080)와 실제 앱이 리스닝하는 포트가 다릅니다.
  • 해결: kubectl exec으로 직접 curl http://localhost:8080/healthz를 실행하여 정확한 포트와 경로를 확인하고 YAML을 수정합니다.

3. Status Code의 함정 (200 OK가 아닐 때)

  • 증상: Probe는 통신에 성공했으나, 계속 실패하여 Pod가 불안정합니다.
  • 원인: 헬스체크 엔드포인트가 성공적으로 응답하더라도, 3xx 리다이렉트나 401(인증 필요) 같은 코드를 반환하면 Probe는 이를 '실패'로 간주합니다.
  • 해결: 헬스체크 경로는 반드시 200 OK를 반환하도록 애플리케이션 레벨에서 수정해야 합니다.

4. 외부 의존성 대기 (DB/Redis 등)

  • 증상: Pod는 재시작하지 않지만, 트래픽을 받지 못하고 503 에러가 발생합니다.
  • 원인: 앱이 시작되자마자 DB 연결을 시도하는데, DB가 아직 준비되지 않아 연결이 실패합니다.
  • 해결: Readiness Probe를 사용하되, 이 Probe가 DB 연결 성공 여부를 확인하도록 로직을 분리해야 합니다. (가장 이상적인 패턴)

5. 과도한 부하로 인한 타임아웃 (Timeout/PeriodSeconds)

  • 증상: 평소엔 괜찮다가 트래픽이 몰리면 갑자기 실패합니다.
  • 원인: timeoutSeconds가 너무 짧으면, 일시적으로 부하가 걸려 응답 시간이 1초를 넘길 때마다 Probe가 실패 처리됩니다.
  • 해결: timeoutSeconds를 충분히 늘리거나, periodSeconds를 늘려 검사 빈도를 낮춥니다.

6. 느린 부팅 앱의 사망 (Startup Probe 부재)

  • 증상: JVM 기반 앱이나 마이그레이션 로직이 복잡한 앱에서만 간헐적으로 재시작이 발생합니다.
  • 원인: Liveness/Readiness Probe가 부팅 완료 전에 너무 빨리 실행되어 앱을 강제로 종료시킵니다.
  • 해결: 반드시 startupProbe를 사용하여 부팅 완료를 위한 '면죄부 시간'을 확보해야 합니다.

💡 실전 YAML 처방전: Probe 3종 조합 예시

다음은 가장 권장되는 조합의 YAML 예시입니다. (HTTP Get 방식 기준)

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  template:
    spec:
      containers:
      - name: my-container
        image: your-registry/my-app:latest
        ports:
        - containerPort: 8080
        
        # 1. Startup Probe: 부팅 완료까지 최대 120초 허용 (12초 * 10회)
        startupProbe:
          httpGet:
            path: /bootstrap-ready # 부팅 전용 경로
            port: 8080
          failureThreshold: 10 # 최대 10번 실패 허용
          periodSeconds: 12 # 12초마다 검사
          
        # 2. Readiness Probe: 트래픽 수신 준비 완료 시점 체크
        readinessProbe:
          httpGet:
            path: /readyz # 트래픽 수신 준비 경로
            port: 8080
          initialDelaySeconds: 30 # 30초 대기 후 검사 시작
          periodSeconds: 10
          failureThreshold: 3
          
        # 3. Liveness Probe: 치명적 오류 감지용 (최후의 보루)
        livenessProbe:
          httpGet:
            path: /livez # 생존 여부만 확인하는 경량 경로
            port: 8080
          initialDelaySeconds: 60 # 가장 늦게 검사 시작
          periodSeconds: 20
          failureThreshold: 5

📌 Startup Probe 최대 허용 시간 계산 팁: 최대 허용 시간 $\approx$ failureThreshold $\times$ periodSeconds (위 예시: $10 \times 12 = 120$초)

🚀 무중단 배포를 위한 고급 패턴: PreStop Hook 활용

단순히 Pod가 준비되는 것만으로는 부족합니다. 롤링 업데이트 과정에서 클라이언트가 연결을 끊는 순간(Connection Drop)을 막아야 합니다. 이때 preStop 훅을 활용합니다.

preStop 훅은 K8s가 Pod를 종료하기 직전에 실행되는 스크립트입니다. 이 시점에 애플리케이션에게 "이제 곧 종료할 테니, 들어오는 연결을 받지 말고 점진적으로 연결을 끊어라"라는 신호를 줄 수 있습니다.

YAML
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sleep", "10"] # 10초 동안 대기하며 연결 종료 유도

preStop으로 시간을 벌고, 동시에 readinessProbe가 실패하도록 유도하면, Service는 이 Pod로의 트래픽 전송을 즉시 중단하여 502/503 에러를 최소화할 수 있습니다.

⏱️ 5분 진단 체크리스트: Probe 문제 해결 순서

문제가 발생했을 때 이 순서대로 점검하면 90% 이상 해결됩니다.

  1. kubectl describe pod 확인: Events에서 정확한 에러 메시지(503, connection refused 등)를 확보합니다.
  2. initialDelaySeconds 점검: 앱의 실제 부팅 시간을 고려하여 충분한 시간을 주었는지 확인합니다.
  3. kubectl exec로 직접 검증: kubectl exec -it <pod-name> -- curl http://localhost:8080/path를 실행하여, YAML에 명시된 포트/Path가 정말 작동하는지 확인합니다.
  4. Probe 분리 및 점검:
    • 느린 앱? $\rightarrow$ startupProbe 추가 (가장 먼저).
    • 트래픽 차단 문제? $\rightarrow$ readinessProbe의 로직(DB 연결 등)을 분리하여 검증.
    • 치명적 오류? $\rightarrow$ livenessProbe의 경량화 및 재검토.
  5. 의존성 분리: 외부 의존성(DB)이 있다면, 해당 의존성 체크 로직을 readinessProbe에만 포함시키고, livenessProbe는 단순한 메모리 체크 등으로 분리합니다.

실무자 경험 공유: 초기에는 Liveness Probe와 Readiness Probe를 같은 /health 엔드포인트에 걸어버리는 실수를 자주 했습니다. 그 결과, DB 연결이 잠시 끊기면 Liveness Probe가 실패하여 Pod가 재시작되고, 이 재시작 과정 자체가 클라이언트에게는 '서비스 중단'으로 인식되는 악순환이 발생했습니다. 가장 중요한 원칙은 '생존(Liveness)'과 '서비스 준비(Readiness)'의 로직을 분리하는 것입니다.

자주 묻는 질문 (FAQ)

Q. Liveness Probe와 Readiness Probe를 둘 다 설정하면 어떻게 되나요? A. 두 프로브 모두 실패할 경우, Liveness Probe가 먼저 실패하면 Pod가 재시작되므로 서비스가 중단됩니다. Readiness Probe가 실패하면 트래픽만 차단됩니다. 따라서 두 프로브의 실패 원인과 영향 범위를 명확히 분리하여 설정해야 합니다.

Q. failureThreshold를 높게 설정하는 것이 항상 좋은가요? A. 아닙니다. failureThreshold는 '실패해도 괜찮은 횟수'를 의미합니다. 너무 높게 설정하면 실제 장애가 발생했을 때 복구 시간이 길어지므로, 앱의 특성과 허용 가능한 장애 시간을 고려하여 적정 수준(보통 3~5회)으로 설정하는 것이 좋습니다.

Q. readinessProbe가 실패하면 Pod는 재시작되나요? A. 아닙니다. Readiness Probe가 실패하면 K8s는 해당 Pod를 Service의 Endpoint 목록에서 제외(Deregister)할 뿐, 컨테이너를 재시작시키지 않습니다. 재시작은 Liveness Probe의 역할입니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 12일

댓글

불러오는 중...