Kubernetes Pod 장애 문제 해결: 종합 가이드

이 종합 가이드를 통해 Kubernetes Pod 장애의 복잡성을 탐색하세요. CrashLoopBackOff, ImagePullBackOff, 리소스 고갈과 같은 일반적인 문제를 진단하는 체계적인 프로세스를 배웁니다. `kubectl describe` 및 `kubectl logs --previous`와 같은 필수 도구를 활용하여 근본 원인을 파악하고, 컨테이너 종료 상태를 해석하며, 안정적인 애플리케이션 가동 시간과 안정성을 유지하기 위한 실용적인 수정 사항을 구현하는 방법을 자세히 설명합니다.

Kubernetes Pod 장애 문제 해결: 종합 가이드

Kubernetes Pod 장애 문제 해결은 모든 상태를 암기하는 것보다 Kubernetes가 어디에 단서를 남기는지 배우는 것이 더 중요합니다. Pod가 "조용히" 실패하는 경우는 거의 없습니다. 스케줄러, kubelet, 컨테이너 런타임, 이미지 레지스트리, 볼륨 플러그인 및 애플리케이션은 모두 다른 위치에 흔적을 남깁니다. 핵심은 올바른 순서로 확인하여 이미지를 가져오지도 않은 Pod의 애플리케이션 로그를 읽는 데 20분을 낭비하지 않는 것입니다.

저는 보통 한 가지 질문으로 시작합니다: Pod가 컨테이너가 시작되기 전에 실패했는지, 컨테이너가 시작되는 동안 실패했는지, 아니면 애플리케이션이 실행된 후에 실패했는지? 이 한 가지 구분만으로도 조사가 명확해집니다. Pending은 일반적으로 스케줄링, 스토리지 또는 이미지 설정 문제를 나타냅니다. ImagePullBackOff는 레지스트리 경로, 태그, 자격 증명 또는 노드 이그레스 문제를 나타냅니다. CrashLoopBackOff는 일반적으로 프로세스가 시작된 후 종료됨을 의미하지만, 그 이유는 구성, 누락된 파일, 잘못된 명령, 실패한 종속성 또는 메모리 압박 때문일 수 있습니다.


Pod 진단의 세 가지 기둥

문제 해결은 세 가지 기본 kubectl 명령을 사용하여 실패한 Pod에 대한 모든 사용 가능한 정보를 수집하는 것으로 시작됩니다.

1. 초기 상태 확인 (kubectl get pods)

첫 번째 단계는 항상 Pod 및 해당 컨테이너의 현재 상태를 확인하는 것입니다. STATUSREADY 열에 주의를 기울이십시오.

kubectl get pods -n my-namespace

Pod 상태 해석

상태 의미 초기 조치
Running 하나 이상의 컨테이너가 실행 중입니다. 항상 앱이 트래픽을 제공하고 있음을 의미하지는 않습니다. READY, 재시작 횟수 및 준비 상태 이벤트를 확인합니다.
Pending Pod가 Kubernetes에 의해 승인되었지만 컨테이너가 아직 생성되지 않았습니다. 스케줄링 이벤트 또는 이미지 풀 상태를 확인합니다.
CrashLoopBackOff 컨테이너가 시작되고 충돌하며 Kubelet이 반복적으로 다시 시작하려고 시도합니다. 애플리케이션 로그를 확인합니다 (kubectl logs --previous).
ImagePullBackOff Kubelet이 필요한 컨테이너 이미지를 가져올 수 없습니다. 이미지 이름, 태그 및 레지스트리 자격 증명을 확인합니다.
Error 런타임 오류 또는 시작 명령 실패로 인해 Pod가 종료되었습니다. 로그 및 describe 이벤트를 확인합니다.
Terminating/Unknown Pod가 종료 중이거나 Kubelet 호스트가 응답하지 않습니다. 노드 상태를 확인합니다.

2. 심층 검사 (kubectl describe pod)

상태가 Running 이외의 것이라면 describe 명령은 스케줄링 결정, 리소스 할당 및 컨테이너 상태에 대한 중요한 컨텍스트를 제공합니다.

kubectl describe pod [POD_NAME] -n my-namespace

출력에서 다음 섹션에 집중하십시오:

  • Containers/Init Containers: State (특히 Waiting 또는 Terminated) 및 Last State (실패 이유가 종종 기록됨, 예: Reason: OOMKilled)를 확인합니다.
  • Resource Limits: LimitsRequests가 올바르게 설정되었는지 확인합니다.
  • Events: 이것이 가장 중요한 섹션입니다. 이벤트는 스케줄링 실패, 볼륨 마운트 문제 및 이미지 풀 시도를 포함한 수명 주기 기록을 보여줍니다.

팁: Events 섹션에 "0/N nodes available"과 같은 메시지가 표시되면 Pod가 리소스(CPU, 메모리) 부족 또는 어피니티 규칙 충족 실패로 인해 스케줄링에 실패할 가능성이 높습니다.

가장 최근의 단서를 원할 때는 이벤트를 아래에서 위로 읽되, 오래된 이벤트를 무시하지 마십시오. Pod는 두 가지 이상의 문제를 가질 수 있습니다. 예를 들어, 배포는 요청된 메모리가 너무 높아 FailedScheduling으로 시작한 후, 노드가 추가된 후 ImagePullBackOff로 이동할 수 있습니다. 최종 상태만 보면 문제를 앞으로 나아가게 만든 변경 사항을 놓칠 수 있습니다.

3. 로그 검토 (kubectl logs)

런타임 애플리케이션 문제의 경우 로그는 프로세스가 종료된 이유를 설명하는 스택 추적 또는 오류 메시지를 제공합니다.

# 현재 컨테이너 로그 확인
kubectl logs [POD_NAME] -n my-namespace

# Pod 내 특정 컨테이너의 로그 확인
kubectl logs [POD_NAME] -c [CONTAINER_NAME] -n my-namespace

# CrashLoopBackOff에 중요: *이전* 실패한 실행의 로그 확인
kubectl logs [POD_NAME] --previous -n my-namespace

Pod에 사이드카가 있는 경우 항상 -c를 포함하십시오. 실패한 애플리케이션 컨테이너 대신 정상적인 사이드카의 로그를 읽음으로써 발생하는 많은 좌절스러운 조사가 있습니다. Init 컨테이너 실패의 경우에도 -c와 함께 Init 컨테이너 이름을 사용하십시오:

kubectl logs [POD_NAME] -c [INIT_CONTAINER_NAME] -n my-namespace

일반적인 Pod 실패 시나리오 및 해결 방법

대부분의 Pod 실패는 몇 가지 인식 가능한 패턴에 속하며, 각각 대상 진단 접근 방식이 필요합니다.

시나리오 1: CrashLoopBackOff

이것은 가장 빈번한 Pod 실패입니다. 컨테이너가 성공적으로 시작되었지만 컨테이너 내의 애플리케이션이 즉시 종료됨(0이 아닌 종료 코드로)을 의미합니다.

진단:

  1. kubectl logs --previous를 사용하여 역추적 또는 종료 이유를 확인합니다.
  2. kubectl describe를 사용하여 Last State 섹션의 Exit Code를 확인합니다.

일반적인 원인 및 수정:

  • 종료 코드 1/2: 일반적인 애플리케이션 오류, 누락된 구성 파일, 데이터베이스 연결 실패 또는 잘못된 입력으로 인한 애플리케이션 충돌.
    • 수정: 애플리케이션 코드를 디버깅하거나 마운트된 ConfigMap/Secret을 확인합니다.
  • 누락된 종속성: 진입점 스크립트에 존재하지 않는 파일 또는 환경이 필요합니다.
    • 수정: Dockerfile 및 이미지 빌드 프로세스를 확인합니다.
  • 잘못된 명령 또는 인수: 컨테이너 이미지는 유효하지만 Pod 사양의 명령이 이미지 진입점을 잘못 재정의합니다.
    • 수정: Deployment commandargs를 이미지의 예상 시작 명령과 비교합니다. 가능하면 동일한 이미지를 로컬에서 테스트합니다.
  • 프로브로 인한 재시작: 활성 프로브가 워밍업을 마치기 전에 느리게 시작되는 앱을 종료할 수 있습니다.
    • 수정: initialDelaySeconds를 늘리거나, startupProbe를 사용하거나, 프로브를 더 가벼운 상태 확인 엔드포인트로 지정합니다.

실용적인 패턴은 동일한 이미지를 사용하지만 무해한 명령으로 임시 복사본 하나를 배포한 다음 파일 시스템과 환경을 검사하는 것입니다:

kubectl run debug-image \
  --image=registry.example.com/app:tag \
  --restart=Never \
  --command -- sleep 3600

kubectl exec -it debug-image -- /bin/sh

이것이 Deployment 수정을 대체하지는 않지만, 간단한 질문에 신속하게 답하는 데 도움이 됩니다: 구성 파일이 실제로 이미지에 있는가, 바이너리가 존재하는가, 컨테이너에 예상 셸이 있는가, 환경 변수가 있는가?

시나리오 2: ImagePullBackOff / ErrImagePull

이는 Kubelet이 Pod 정의에 지정된 컨테이너 이미지를 가져올 수 없을 때 발생합니다.

진단:

  1. kubectl describeEvents 섹션에서 특정 오류 메시지(예: 404 Not Found 또는 authentication required)를 확인합니다.

일반적인 원인 및 수정:

  • 오타 또는 잘못된 태그: 이미지 이름 또는 태그가 잘못되었습니다.
    • 수정: Deployment 또는 Pod 사양에서 이미지 이름을 수정합니다.
  • 프라이빗 레지스트리 액세스: 클러스터에 프라이빗 레지스트리(예: Docker Hub, GCR, ECR)에서 가져올 자격 증명이 없습니다.
    • 수정: 적절한 imagePullSecret이 Pod 사양에서 참조되고 해당 Secret이 네임스페이스에 존재하는지 확인합니다.
# 풀 시크릿 사용을 위한 예시 Pod 사양 스니펫
spec:
  containers:
  ...
  imagePullSecrets:
  - name: my-registry-secret

또한 풀 시크릿이 있는 위치를 확인하십시오. Kubernetes 시크릿은 네임스페이스 범위입니다. default에 있는 regcred라는 시크릿은 payments의 Pod에 도움이 되지 않습니다. 동일한 이미지가 한 네임스페이스에서는 작동하지만 다른 네임스페이스에서는 실패하는 경우, 레지스트리가 손상되었다고 가정하기 전에 서비스 계정과 이미지 풀 시크릿을 비교하십시오:

kubectl get serviceaccount default -n payments -o yaml
kubectl get secret regcred -n payments

시나리오 3: Pending 상태 (멈춤)

Pod가 Pending 상태로 유지되며, 일반적으로 노드에 스케줄링할 수 없거나 리소스(예: PersistentVolume)를 기다리고 있음을 나타냅니다.

진단:

  1. kubectl describe를 실행하고 Events 섹션을 확인합니다.

일반적인 원인 및 수정:

  • 리소스 고갈: 클러스터에 Pod의 requests를 충족할 수 있는 충분한 CPU 또는 메모리를 가진 노드가 부족합니다.
    • 수정: 클러스터 크기를 늘리거나 Pod 리소스 요청을 줄입니다(가능한 경우).
    • 이벤트 메시지 예: 0/4 nodes are available: 4 Insufficient cpu.
  • 볼륨 바인딩 문제: Pod에 기본 PersistentVolume(PV)에 바인딩할 수 없는 PersistentVolumeClaim(PVC)이 필요합니다.
    • 수정: PVC 상태(kubectl get pvc)를 확인하고 StorageClass가 작동하는지 확인합니다.
  • 셀렉터 또는 어피니티 불일치: Pod가 존재하지 않는 노드 레이블을 요청하거나 필수 어피니티 규칙이 모든 노드를 제외합니다.
    • 수정: nodeSelector, nodeAffinity 및 노드 레이블을 kubectl get nodes --show-labels와 비교합니다.
  • 용인되지 않는 테인트: 노드는 사용 가능하지만 Pod에 일치하는 톨러레이션이 없어 Pod를 밀어냅니다.
    • 수정: 의도된 톨러레이션을 Pod에 추가하거나, 테인트가 더 이상 실제 배치 규칙을 나타내지 않으면 제거합니다.

시나리오 4: OOMKilled (메모리 부족으로 종료)

일반적으로 CrashLoopBackOff 상태가 되지만, 근본 원인은 구체적입니다: 컨테이너가 사양에 정의된 제한보다 더 많은 메모리를 사용하여 호스트 운영 체제(Kubelet을 통해)가 강제로 종료합니다.

진단:

  1. kubectl describe -> Last State -> Reason: OOMKilled를 확인합니다.

수정:

  1. 제한 증가: Pod 사양에서 메모리 limit를 늘려 더 많은 헤드룸을 제공합니다.
  2. 애플리케이션 최적화: 애플리케이션을 프로파일링하여 메모리 사용량을 줄입니다.
  3. 요청 설정: requests를 실제 정상 상태 사용량에 가깝게 설정하여 스케줄링 안정성을 개선합니다.
resources:
  limits:
    memory: "512Mi" # 이 값 증가
  requests:
    memory: "256Mi"

제한만 높이는 메모리 "수정"에 주의하십시오. 애플리케이션에 누수가 있는 경우 더 높은 제한은 다음 실패를 지연시키고 노드가 더 많은 위험을 감수하게 할 수 있습니다. 메트릭 시스템에서 시간에 따른 메모리를 확인하십시오. 가비지 컬렉션 후 기준선으로 돌아가는 톱니 패턴은 OOM까지 꾸준히 상승하는 것과 다릅니다.

시나리오 5: CreateContainerConfigError 및 CreateContainerError

이러한 상태는 애플리케이션 실패처럼 들리지 않기 때문에 간과하기 쉽습니다. 일반적으로 kubelet이 컨테이너 구성을 조합할 수 없음을 의미합니다.

일반적인 원인은 다음과 같습니다:

  • 참조된 ConfigMap 또는 Secret이 네임스페이스에 존재하지 않습니다.
  • ConfigMap 또는 Secret 내의 키 철자가 잘못되었습니다.
  • 볼륨 마운트 경로가 다른 마운트와 충돌합니다.
  • 컨테이너가 유효하지 않은 보안 컨텍스트를 참조합니다.

가장 빠른 확인 방법은 여전히 describe입니다:

kubectl describe pod [POD_NAME] -n my-namespace

secret "app-config" not found 또는 configmap "settings" not found와 같은 이벤트 메시지를 찾으십시오. 그런 다음 객체를 확인합니다:

kubectl get secret app-config -n my-namespace
kubectl get configmap settings -n my-namespace -o yaml

이것은 일반적인 배포 파이프라인 실수입니다. 애플리케이션 매니페스트가 적용되었지만 시크릿 생성 단계가 실패했거나 잘못된 네임스페이스에 대해 실행되었습니다.

시나리오 6: 실행 중이지만 준비되지 않음

Pod가 Running을 표시하면서도 사용할 수 없는 상태일 수 있습니다. READY 열은 준비 상태 프로브에 따라 준비된 컨테이너 수를 알려줍니다. 1/2 또는 0/1인 Pod는 살아있을 수 있지만 Service 엔드포인트에서 제거됩니다.

트래픽이 실패하지만 Pod가 살아있는 것처럼 보일 때 엔드포인트를 확인하십시오:

kubectl get endpoints [SERVICE_NAME] -n my-namespace
kubectl describe pod [POD_NAME] -n my-namespace

엔드포인트 목록이 비어 있으면 문제는 준비 상태 프로브, Service 셀렉터 불일치 또는 애플리케이션이 Service가 예상하는 포트와 다른 포트에서 수신 대기하는 것일 수 있습니다. 실제 인시던트에서 사람들이 시간을 낭비하는 지점입니다. Pod가 트래픽이 누락된 이유가 아님에도 불구하고 계속 Pod를 재시작합니다.


향후 실패 방지: 모범 사례

강력한 애플리케이션은 일반적인 배포 함정을 방지하기 위해 신중한 구성이 필요합니다.

Liveness 및 Readiness 프로브 사용

프로브를 적절히 구현하면 Kubernetes가 컨테이너 상태를 지능적으로 관리할 수 있습니다:

  • Liveness 프로브: 컨테이너가 계속 실행될 만큼 건강한지 확인합니다. Liveness 프로브가 실패하면 Kubelet이 컨테이너를 다시 시작합니다(소프트 잠금 해결).
  • Readiness 프로브: 컨테이너가 트래픽을 제공할 준비가 되었는지 확인합니다. Readiness 프로브가 실패하면 Pod가 Service 엔드포인트에서 제거되어 컨테이너가 복구되는 동안 실패한 요청을 방지합니다.

해당 종속성에 단기 중단이 있을 때마다 Kubernetes가 컨테이너를 다시 시작하도록 하려는 것이 아니라면 Liveness 프로브를 깊은 종속성 검사에 지정하지 마십시오. 데이터베이스를 30초 동안 사용할 수 없는 것이 일반적으로 프로세스가 죽었다는 증거는 아닙니다. Readiness가 "지금 이 Pod에 트래픽을 보내지 마십시오"라고 말하기에 더 좋은 위치입니다.

리소스 제한 및 요청 적용

항상 컨테이너에 대한 리소스 요구 사항을 정의하십시오. 이는 Pod가 과도한 리소스를 소비하여 노드 불안정을 초래하는 것을 방지하고 스케줄러가 충분한 용량을 가진 노드에 Pod를 배치할 수 있도록 합니다.

설정을 위한 Init 컨테이너 활용

Pod가 기본 애플리케이션이 시작되기 전에 종속성 확인 또는 데이터 설정이 필요한 경우(예: 데이터베이스 마이그레이션 완료 대기) Init 컨테이너를 사용하십시오. Init 컨테이너가 실패하면 Pod가 반복적으로 다시 시작하여 설정 오류를 애플리케이션 런타임 오류와 깔끔하게 분리합니다.

실용적인 트라이지 플로우

온콜 중일 때는 반복 가능한 경로를 사용하십시오:

  1. kubectl get pods -n <namespace> -o wide를 확인하여 상태, 재시작 횟수, 수명 및 노드 배치를 확인합니다.
  2. kubectl describe pod를 실행하고 Events, State, Last State, 마운트 및 리소스 설정을 읽습니다.
  3. kubectl logs로 로그를 가져오고, 재시작된 컨테이너에는 --previous를, 멀티 컨테이너 Pod에는 -c를 추가합니다.
  4. Pod가 Pending이면 앱 로그를 읽기 전에 스케줄링, 테인트, 노드 레이블 및 PVC를 검사합니다.
  5. Pod가 Running이지만 트래픽을 수신하지 않으면 readiness 프로브, Service 셀렉터 및 엔드포인트를 검사합니다.
  6. Pod가 OOMKilled된 경우 단순히 숫자를 늘리기 전에 제한을 실제 메모리 그래프와 비교합니다.

이 순서는 Kubernetes가 아직 애플리케이션을 시작하지도 않았는데 애플리케이션으로 바로 뛰어드는 것을 방지합니다.

최종 확인

가장 유용한 습관은 증상과 원인을 분리하는 것입니다. CrashLoopBackOff는 증상입니다. 원인은 누락된 시크릿, 잘못된 마이그레이션, Liveness 프로브 또는 메모리 제한일 수 있습니다. Pending은 증상입니다. 원인은 CPU 요청, PVC, 테인트 또는 노드 셀렉터일 수 있습니다. Pod 상태가 어디를 봐야 하는지 알려주고, 이벤트와 로그가 무엇이 변경되었는지 알려주도록 하십시오.