문제 해결: Kubernetes Pod가 Pending 또는 CrashLoopBackOff 상태에서 멈춘 이유는?

Kubernetes Pod가 `Pending` 또는 `CrashLoopBackOff` 상태에서 멈추면 배포가 중단될 수 있습니다. 이 포괄적인 가이드는 이러한 일반적인 상태를 명확히 설명하고, `kubectl` 명령어를 사용하여 리소스 제약, 이미지 풀 오류, 애플리케이션 실패, 프로브 설정 오류와 같은 문제를 진단하는 실용적이고 단계적인 문제 해결 방법을 제공합니다. 실행 가능한 통찰력과 모범 사례를 통해 Pod 문제를 신속하게 해결하고 강력하고 안정적인 Kubernetes 환경을 유지하여 애플리케이션이 항상 실행되도록 하세요.

문제 해결: Kubernetes Pod가 Pending 또는 CrashLoopBackOff 상태에서 멈춘 이유는?

PendingCrashLoopBackOff는 롤아웃을 기다릴 때 비슷해 보이지만, 의미는 매우 다릅니다. Pending은 일반적으로 Kubernetes가 Pod를 배치하거나 준비하지 못했음을 의미합니다. CrashLoopBackOff는 컨테이너가 시작된 후 종료되었으며, Kubernetes가 다음 재시작을 지연시키고 있음을 의미합니다.

이 차이는 중요합니다. Pending 상태의 Pod는 일반적으로 스케줄러, 이미지 또는 스토리지 문제입니다. CrashLoopBackOff 상태의 Pod는 일반적으로 애플리케이션, 명령어, 프로브, 권한 또는 메모리 문제입니다. 이 구분으로 시작하면 문제 해결 경로가 훨씬 짧아집니다.

Pod 상태 이해: Pending vs. CrashLoopBackOff

문제 해결에 들어가기 전에 이 두 상태가 무엇을 의미하는지 이해하는 것이 중요합니다.

Pod 상태: Pending

Pending 상태의 Pod는 Kubernetes가 Pod 객체를 수락했지만, 아직 실행 중인 컨테이너 상태로 완전히 전환되지 않았음을 의미합니다. 때로는 노드에 스케줄링되지 않았을 수도 있습니다. 때로는 노드가 할당되었지만 이미지 풀, 볼륨 연결 또는 샌드박스 설정이 완료되지 않았을 수도 있습니다.

Pod 상태: CrashLoopBackOff

CrashLoopBackOff 상태의 Pod는 Pod 내의 컨테이너가 반복적으로 시작, 충돌, 재시작을 반복하고 있음을 의미합니다. Kubernetes는 재시작 사이에 지수 백오프 지연을 적용하여 노드에 과부하가 걸리는 것을 방지합니다. 이 상태는 거의 항상 컨테이너 내부에서 실행 중인 애플리케이션 자체 또는 그 즉시 환경에 문제가 있음을 나타냅니다.

미묘한 경우 중 하나는 컨테이너가 종료 코드 0으로 종료되더라도 워크로드가 장기 실행 서버여야 하는 경우 재시작 루프에 빠질 수 있다는 것입니다. 이는 Deployment가 실수로 마이그레이션 스크립트나 즉시 완료되는 셸 명령어와 같은 일회성 명령어를 실행할 때 자주 발생합니다.

Pending 상태의 Pod 문제 해결

Pod가 Pending 상태일 때 가장 먼저 확인해야 할 것은 스케줄러와 Pod가 배치되려는 노드입니다. 다음은 일반적인 원인과 진단 단계입니다.

1. 노드의 리소스 부족

Pod가 Pending 상태가 되는 가장 빈번한 이유 중 하나는 클러스터 내 어떤 노드도 Pod의 requests를 충족할 수 있는 충분한 가용 리소스(CPU, 메모리)를 가지고 있지 않기 때문입니다. 스케줄러가 적합한 노드를 찾을 수 없습니다.

진단 단계:

  1. Pod 설명: kubectl describe pod 명령어가 가장 유용합니다. Pod를 스케줄링할 수 없는 이유를 자세히 설명하는 이벤트를 자주 보여줍니다.

    kubectl describe pod <pod-name> -n <namespace>
    

    "FailedScheduling"과 같은 이벤트와 "0/3 nodes are available: 3 Insufficient cpu" 또는 "memory"와 같은 메시지를 찾아보세요.

  2. 노드 리소스 확인: 노드의 현재 리소스 사용량과 용량을 확인합니다.

    kubectl get nodes
    kubectl top nodes # (metrics-server 필요)
    

해결 방법:

  • 클러스터 용량 증가: Kubernetes 클러스터에 노드를 더 추가합니다.
  • Pod 리소스 요청 조정: Pod 매니페스트에서 CPU 및 메모리에 대한 requests가 너무 높게 설정된 경우 줄입니다.
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
    
  • 다른 Pod 축출: 노드에서 우선순위가 낮은 Pod를 수동으로 축출하여 리소스를 확보합니다(주의해서 사용).

2. 이미지 풀 오류

Kubernetes가 Pod를 노드에 스케줄링할 수 있지만 노드가 컨테이너 이미지를 가져오지 못하면 Pod는 Pending 상태로 유지됩니다.

일반적인 원인:

  • 잘못된 이미지 이름/태그: 이미지 이름의 오타 또는 존재하지 않는 태그 사용.
  • 프라이빗 레지스트리 인증: 프라이빗 레지스트리에 대한 ImagePullSecrets 누락 또는 잘못됨.
  • 네트워크 문제: 노드가 이미지 레지스트리에 연결할 수 없음.

진단 단계:

  1. Pod 설명: 다시 kubectl describe pod가 핵심입니다. "Failed" 또는 "ErrImagePull" 또는 "ImagePullBackOff"와 같은 이벤트를 찾아보세요.

    kubectl describe pod <pod-name> -n <namespace>
    

    예시 출력 이벤트: Failed to pull image "my-private-registry/my-app:v1.0": rpc error: code = Unknown desc = Error response from daemon: pull access denied for my-private-registry/my-app, repository does not exist or may require 'docker login'

  2. ImagePullSecrets 확인: Pod 또는 ServiceAccount에 imagePullSecrets가 올바르게 구성되었는지 확인합니다.

    kubectl get secret <your-image-pull-secret> -o yaml -n <namespace>
    

해결 방법:

  • 이미지 이름/태그 수정: 배포 매니페스트에서 이미지 이름과 태그를 다시 확인합니다.
  • ImagePullSecrets 구성: docker-registry 시크릿을 생성하고 Pod 또는 ServiceAccount에 연결했는지 확인합니다.
    kubectl create secret docker-registry my-registry-secret \
      --docker-server=your-registry.com \
      --docker-username=your-username \
      --docker-password=your-password \
      --docker-email=your-email -n <namespace>
    
    그런 다음 Pod 스펙에 추가합니다:
    spec:
      imagePullSecrets:
      - name: my-registry-secret
      containers:
      ...
    
  • 네트워크 연결: 노드에서 이미지 레지스트리까지의 네트워크 연결을 확인합니다.

프라이빗 레지스트리를 사용하는 경우 ServiceAccount도 확인하세요. 많은 팀이 모든 Deployment에 imagePullSecrets를 연결하는 대신 네임스페이스의 기본 ServiceAccount에 연결합니다:

kubectl get serviceaccount default -n <namespace> -o yaml

시크릿이 존재하지만 풀이 여전히 실패하는 경우, 시크릿의 레지스트리 호스트 이름이 이미지 참조의 호스트 이름과 정확히 일치하는지 확인하세요. registry.example.com/app:v1https://registry.example.com/app:v1은 동일한 참조가 아닙니다.

3. 볼륨 관련 문제

Pod에 PersistentVolumeClaim(PVC)이 필요하고 해당 PersistentVolume(PV)을 프로비저닝하거나 바인딩할 수 없는 경우 Pod는 Pending 상태로 유지됩니다.

진단 단계:

  1. Pod 설명: 볼륨과 관련된 이벤트를 찾아보세요.

    kubectl describe pod <pod-name> -n <namespace>
    

    이벤트에 FailedAttachVolume, FailedMount 또는 유사한 메시지가 표시될 수 있습니다.

  2. PVC 및 PV 상태 확인: PVC 및 PV의 상태를 검사합니다.

    kubectl get pvc <pvc-name> -n <namespace>
    kubectl get pv
    

    Pending 상태에서 멈춰 있거나 바인딩되지 않은 PVC를 찾아보세요.

해결 방법:

  • StorageClass 확인: 특히 동적 프로비저닝을 사용하는 경우 StorageClass가 정의되고 사용 가능한지 확인합니다.
  • PV 가용성 확인: 정적 프로비저닝을 사용하는 경우 PV가 존재하고 PVC 기준과 일치하는지 확인합니다.
  • 액세스 모드 확인: 액세스 모드(예: ReadWriteOnce, ReadWriteMany)가 호환되는지 확인합니다.

또한 Pod가 볼륨을 연결할 수 있는 영역에 스케줄링되었는지 확인하세요. 클라우드 클러스터에서 한 가용 영역에 생성된 디스크는 다른 영역의 노드에 연결되지 않을 수 있습니다. 이벤트는 일반적으로 볼륨 노드 선호도 또는 연결 실패를 언급합니다. 이 경우 해결 방법은 스케줄링 제약 조건, 다른 StorageClass를 사용하거나 올바른 영역에서 볼륨을 다시 생성하는 것일 수 있습니다.

4. 테인트, 톨러레이션 및 노드 셀렉터

Pod는 클러스터에 CPU와 메모리가 충분하더라도 Pending 상태로 남을 수 있습니다. 스케줄러는 배치 규칙도 준수해야 합니다.

일반적인 예:

  • Pod에 일치하는 노드가 없는 nodeSelector가 있습니다.
  • Pod에 너무 엄격한 노드 선호도가 필요합니다.
  • 유일하게 일치하는 노드에 테인트가 있고 Pod에 일치하는 톨러레이션이 없습니다.
  • 네임스페이스에 요청된 리소스를 차단하는 할당량이 있습니다.

먼저 스케줄링 이벤트를 확인하세요:

kubectl describe pod <pod-name> -n <namespace>

그런 다음 Pod의 배치 규칙을 노드 레이블과 비교합니다:

kubectl get pod <pod-name> -n <namespace> -o yaml
kubectl get nodes --show-labels
kubectl describe node <node-name>

이벤트에서 테인트가 허용되지 않았다고 표시되면 Pod를 다른 곳에 스케줄링하거나 해당 워크로드가 실제로 해당 노드에 속하는 경우에만 톨러레이션을 추가하세요. 모든 테인트를 맹목적으로 허용하지 마십시오. 테인트는 종종 특수 노드, GPU 노드, 인프라 노드 또는 압박을 받는 노드를 보호합니다.

CrashLoopBackOff 상태의 Pod 문제 해결

CrashLoopBackOff 상태는 애플리케이션 수준의 문제를 나타냅니다. 컨테이너가 성공적으로 시작되었지만 오류와 함께 종료되어 Kubernetes가 반복적으로 재시작하게 됩니다.

1. 애플리케이션 오류

가장 일반적인 원인은 애플리케이션 자체가 시작에 실패하거나 시작 직후 치명적인 오류가 발생하는 것입니다.

일반적인 원인:

  • 종속성/구성 누락: 애플리케이션이 의존하는 중요한 구성 파일, 환경 변수 또는 외부 서비스를 찾을 수 없습니다.
  • 잘못된 명령어/인수: 컨테이너 스펙에 지정된 command 또는 args가 잘못되었거나 즉시 종료됩니다.
  • 애플리케이션 로직 오류: 시작 시 충돌을 일으키는 애플리케이션 코드의 버그.

진단 단계:

  1. Pod 로그 보기: 이것이 가장 중요한 단계입니다. 로그는 종종 애플리케이션 충돌의 정확한 오류 메시지를 보여줍니다.

    kubectl logs <pod-name> -n <namespace>
    

    Pod가 반복적으로 충돌하는 경우 로그는 가장 최근 실패한 시도의 출력을 보여줄 수 있습니다. 충돌하는 컨테이너의 이전 인스턴스 로그를 보려면 -p(이전) 플래그를 사용하세요:

    kubectl logs <pod-name> -p -n <namespace>
    
  2. Pod 설명: Containers 섹션에서 Restart Count를 찾아 컨테이너가 충돌한 횟수를 확인합니다. 또한 Last State에서 Exit Code를 확인합니다.

    kubectl describe pod <pod-name> -n <namespace>
    

    종료 코드 0은 일반적으로 정상 종료를 의미하지만, 0이 아닌 종료 코드는 오류를 나타냅니다. 일반적인 0이 아닌 종료 코드로는 1(일반 오류), 137(SIGKILL, 종종 OOMKilled), 139(SIGSEGV, 세그멘테이션 오류) 등이 있습니다.

해결 방법:

  • 애플리케이션 로그 검토: 로그를 기반으로 애플리케이션 코드 또는 구성을 디버깅합니다. 모든 필수 환경 변수, ConfigMapSecret이 올바르게 마운트/주입되었는지 확인합니다.
  • 로컬에서 테스트: 동일한 환경 변수 및 명령어로 로컬에서 컨테이너 이미지를 실행하여 문제를 재현하고 디버깅합니다.

Pod에 여러 컨테이너가 있는 경우 항상 컨테이너 이름을 지정하세요:

kubectl logs <pod-name> -c <container-name> -n <namespace>
kubectl logs <pod-name> -c <container-name> -p -n <namespace>

-c 없이 메인 앱이 충돌하는 동안 사이드카 로그를 읽을 수 있습니다.

2. Liveness 및 Readiness 프로브 실패

Kubernetes는 Liveness 및 Readiness 프로브를 사용하여 애플리케이션의 상태와 가용성을 결정합니다. Liveness 프로브가 계속 실패하면 Kubernetes는 컨테이너를 재시작하여 CrashLoopBackOff로 이어집니다.

진단 단계:

  1. Pod 설명: Containers 섹션에서 LivenessReadiness 프로브 정의와 Last State를 확인합니다.

    kubectl describe pod <pod-name> -n <namespace>
    

    "Liveness probe failed: HTTP probe failed with statuscode: 500"과 같은 프로브 실패를 나타내는 메시지를 찾아보세요.

  2. 애플리케이션 로그 검토: 때로는 애플리케이션 로그가 프로브 엔드포인트가 실패하는 이유에 대한 컨텍스트를 제공합니다.

해결 방법:

  • 프로브 구성 조정: 프로브의 path, port, command, initialDelaySeconds, periodSeconds 또는 failureThreshold를 수정합니다.
  • 프로브 엔드포인트 상태 확인: 프로브가 대상으로 하는 애플리케이션 엔드포인트가 실제로 정상이고 예상대로 응답하는지 확인합니다. 애플리케이션 시작 시간이 너무 오래 걸려 더 큰 initialDelaySeconds가 필요할 수 있습니다.

느리게 시작되는 애플리케이션의 경우 startupProbe를 고려하세요. Liveness 프로브가 평가를 시작하기 전에 애플리케이션에 초기화 시간을 더 줍니다. 이는 모든 재시작에 대해 큰 liveness initialDelaySeconds를 설정하는 것보다 깔끔합니다.

3. 리소스 제한 초과

컨테이너가 memory.limit보다 더 많은 메모리를 지속적으로 사용하거나 cpu.limit을 초과하여 CPU가 제한되는 경우, 커널이 프로세스를 종료할 수 있으며, 종종 OOMKilled(Out Of Memory Killed) 이벤트가 발생합니다.

진단 단계:

  1. Pod 설명: Last State 또는 Events 섹션에서 OOMKilled를 찾아보세요. Exit Code: 137은 종종 OOMKilled 이벤트를 나타냅니다.

    kubectl describe pod <pod-name> -n <namespace>
    
  2. kubectl top 확인: metrics-server가 설치된 경우 kubectl top pod를 사용하여 Pod의 실제 리소스 사용량을 확인합니다.

    kubectl top pod <pod-name> -n <namespace>
    

해결 방법:

  • 리소스 제한 증가: 애플리케이션에 실제로 더 많은 리소스가 필요한 경우 Pod 매니페스트에서 memory 및/또는 cpu limits를 늘립니다. 이렇게 하려면 노드에 더 많은 용량이 필요할 수 있습니다.
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "512Mi" # 이 값을 늘리세요
        cpu: "1000m"   # 이 값을 늘리세요
    
  • 애플리케이션 최적화: 애플리케이션을 프로파일링하여 리소스 소비를 식별하고 줄입니다.

4. 권한 문제

컨테이너는 필요한 파일, 디렉토리 또는 네트워크 리소스에 액세스하는 데 필요한 권한이 없으면 충돌할 수 있습니다.

진단 단계:

  1. 로그 검토: 애플리케이션 로그에 권한 거부 오류(EACCES)가 표시될 수 있습니다.
  2. Pod 설명: 사용 중인 ServiceAccount와 마운트된 securityContext 설정을 확인합니다.

해결 방법:

  • securityContext 조정: 필요에 따라 runAsUser, fsGroup 또는 allowPrivilegeEscalation을 설정합니다.
  • ServiceAccount 권한: Pod와 연결된 ServiceAccountRoleBindingsClusterRoleBindings를 통해 바인딩된 필요한 RolesClusterRoles가 있는지 확인합니다.
  • 볼륨 권한: 마운트된 볼륨(예: emptyDir, hostPath, ConfigMap, Secret)이 컨테이너 사용자에 대해 올바른 권한을 가지고 있는지 확인합니다.

빠른 의사 결정 트리

누군가 "Pod가 고장났다"고 말하면 다음 명령어를 순서대로 실행하세요:

kubectl get pod <pod-name> -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --all-containers=true --tail=100
kubectl logs <pod-name> -n <namespace> --all-containers=true -p --tail=100

그런 다음 분기합니다:

  • kubectl get pod -o wide에 노드가 없으면 스케줄링(requests, taints, affinity, quota, node availability)에 집중합니다.
  • 노드가 있지만 이벤트가 이미지 풀을 언급하면 이미지 이름, 태그, 레지스트리 인증 및 노드-레지스트리 네트워크 액세스에 집중합니다.
  • 이벤트가 마운트 또는 연결을 언급하면 PVC, PV, StorageClass, 액세스 모드 및 영역 배치에 집중합니다.
  • Pod가 시작된 후 재시작되면 로그, 종료 코드, 프로브, 명령어/인수, 구성, 시크릿 및 메모리 제한에 집중합니다.

이 순서는 일반적인 실수인 애플리케이션 컨테이너가 실제로 시작되지 않은 Pod의 애플리케이션 로그를 읽는 것을 방지합니다.

종료 코드 과도하게 반응하지 않고 읽기

종료 코드는 단서일 뿐 완전한 설명이 아닙니다.

  • 1은 일반적으로 애플리케이션이 일반 오류를 반환했음을 의미합니다. 숫자보다 로그가 더 중요합니다.
  • 2는 많은 프로그램에서 명령줄 사용 오류를 가리킬 수 있습니다.
  • 126은 명령어가 존재하지만 실행할 수 없음을 의미하는 경우가 많습니다.
  • 127은 명령어를 찾을 수 없음을 의미하는 경우가 많습니다.
  • 137은 프로세스가 SIGKILL을 수신할 때 자주 나타납니다. Kubernetes에서는 종종 OOMKilled와 관련이 있지만 항상 그런 것은 아닙니다.
  • 143은 프로세스가 SIGTERM을 수신했음을 의미하며, 이는 정상 종료 중에 발생할 수 있습니다.

종료 코드가 137이면 메모리 누수를 가정하기 전에 Pod의 Last State와 이벤트를 확인하세요. 노드 드레인, 축출 또는 수동 종료도 컨테이너를 종료할 수 있습니다.

일반 진단 단계 및 도구

Pod 문제에 직면했을 때 실행할 명령어의 빠른 체크리스트입니다:

  • 빠른 개요 보기: Pod의 상태를 확인합니다.
    kubectl get pods -n <namespace>
    kubectl get pods -n <namespace> -o wide
    
  • 상세 Pod 정보: Pod 이벤트, 상태 및 조건을 이해하는 가장 중요한 명령어입니다.
    kubectl describe pod <pod-name> -n <namespace>
    
  • 컨테이너 로그: 애플리케이션이 보고하는 내용을 확인합니다.
    kubectl logs <pod-name> -n <namespace>
    kubectl logs <pod-name> -p -n <namespace> # 이전 인스턴스
    kubectl logs <pod-name> -f -n <namespace> # 로그 팔로우
    
  • 클러스터 전체 이벤트: 때로는 문제가 특정 Pod가 아니라 클러스터 전체 이벤트(예: 노드 압박)일 수 있습니다.
    kubectl get events -n <namespace>
    
  • 대화형 디버깅: 컨테이너가 시작되지만 빠르게 충돌하는 경우, 잠시 동안 exec로 들어가거나 구성된 경우 별도의 디버그 컨테이너로 들어갈 수 있습니다.
    kubectl exec -it <pod-name> -n <namespace> -- bash
    
    (참고: 컨테이너가 연결될 수 있을 만큼 충분히 오래 살아 있는 경우에만 작동합니다.)

Pod 문제를 방지하기 위한 모범 사례

예방이 치료보다 낫습니다. 다음 모범 사례를 따르면 PendingCrashLoopBackOff 발생을 크게 줄일 수 있습니다:

  • 현실적인 리소스 요청 및 제한 설정: 합리적인 requestslimits로 시작한 다음 애플리케이션 프로파일링 및 모니터링을 기반으로 미세 조정합니다.
  • 특정 이미지 태그 사용: 프로덕션에서 latest 태그를 사용하지 마십시오. 재현성을 위해 변경 불가능한 태그(예: v1.2.3, commit-sha)를 사용하십시오.
  • 강력한 프로브 구현: 애플리케이션 상태를 정확하게 반영하는 livenessreadiness 프로브를 구성합니다. initialDelaySeconds로 시작 시간을 고려합니다.
  • 중앙 집중식 로깅 및 모니터링: Prometheus, Grafana, ELK 스택 또는 클라우드 네이티브 로깅 서비스와 같은 도구를 사용하여 Pod 로그 및 메트릭을 수집하고 분석합니다.
  • 매니페스트 버전 관리: Kubernetes 매니페스트를 버전 관리 시스템(예: Git)에 저장하여 변경 사항을 추적하고 롤백을 용이하게 합니다.
  • 철저한 테스트: 프로덕션에 배포하기 전에 개발 및 스테이징 환경에서 컨테이너 이미지와 Kubernetes 배포를 테스트합니다.
  • 정상 종료: 애플리케이션이 정상 종료를 위해 SIGTERM 신호를 처리하여 종료 전에 리소스를 해제할 수 있도록 합니다.

가장 빠르게 해결되는 방법

Pending의 경우 kubectl describe pod가 일반적으로 가장 빠른 경로입니다. 스케줄러 및 kubelet 이벤트가 Kubernetes가 수행할 수 없는 작업을 설명하기 때문입니다. CrashLoopBackOff의 경우 이전 로그가 일반적으로 가장 빠른 경로입니다. 현재 컨테이너가 루프를 유발한 충돌을 표시하기에는 너무 새 것일 수 있기 때문입니다.

즉각적인 문제를 해결한 후에는 예방 단계를 찾으십시오: 적절한 크기의 요청, 더 나은 이미지 태그, 시작 프로브, CI에서 누락된 시크릿 확인 또는 더 명확한 런북. 최고의 Pod 인시던트는 다음 번에 인식하기 쉬워지는 인시던트입니다.