GKE에서 Pod이 Pending 상태에 머무르는 문제: 원인 분석 및 체계적 해결 가이드

GKE(Google Kubernetes Engine) 환경에서 Pod을 배포했을 때 상태가 Pending에 오랫동안 머무르며 Running으로 전환되지 않는 현상은, Kubernetes 스케줄러가 해당 Pod을 실행할 적합한 노드(Node)를 찾지 못했거나, 노드를 할당했더라도 컨테이너 이미지를 성공적으로 다운로드하지 못하거나, 요청한 리소스(CPU, 메모리, 스토리지 등)를 확보하지 못했음을 의미합니다. 이 상태는 단순히 “대기 중”이라는 메시지로 표시되지만, 실제로는 클러스터의 리소스 관리, 네트워크 연결성, 이미지 레지스트리 접근, 노드 상태, 보안 정책, 스케줄링 제약 조건 등 다층적인 시스템 구성 요소에서 발생하는 복합적인 장애를 내포하고 있습니다. 특히 GKE는 Google Cloud의 관리형 Kubernetes 서비스로서 높은 안정성과 자동화 기능을 제공하지만, 사용자가 정의한 리소스 요청, 노드 풀 구성, IAM 권한, VPC 네트워크 정책 등이 미세하게 어긋나면 Pod이 영구적으로 Pending 상태에 갇히는 문제가 빈번히 발생합니다.

Pending 상태는 Kubernetes의 Pod lifecycle에서 중요한 진단 포인트로, kubectl get pods 명령어로 확인할 때 STATUS 열에 Pending으로 표시되며, kubectl describe pod <pod-name>을 통해 Events 섹션에서 구체적인 실패 원인을 확인할 수 있습니다. 일반적으로 나타나는 이벤트 메시지는 다음과 같습니다:

  • FailedScheduling: 스케줄러가 적합한 노드를 찾지 못함 (예: 0/5 nodes are available: 5 Insufficient cpu.)
  • Failed to pull image: 컨테이너 이미지를 다운로드하지 못함 (예: ImagePullBackOff, ErrImagePull)
  • FailedMount: PersistentVolumeClaim(PVC) 또는 ConfigMap/Secret 마운트 실패
  • Unschedulable: 노드에 Pod을 배치할 수 없음 (taints, node selector, affinity 규칙 등으로 인해)

이러한 원인들은 단일 요소로 끝나지 않고, 서로 연쇄적으로 작용할 수 있습니다. 예를 들어, 노드 풀의 VM 인스턴스가 CPU 부족으로 인해 스케줄링에 실패하면, GKE의 Cluster Autoscaler가 새로운 노드를 추가하려 하지만, 해당 노드 생성 시 IAM 권한 부족으로 실패하고, 결국 Pod은 계속 Pending 상태로 남게 됩니다. 또는, 프라이빗 GCR(Google Container Registry)에 저장된 이미지를 사용하려는데, 노드의 서비스 계정에 storage.objectViewer 권한이 없으면 이미지 풀에 실패하며 Pending으로 고착됩니다.


주요 원인별 상세 분석

1. 리소스 부족 (Insufficient CPU/Memory/Ephemeral Storage)

가장 흔한 원인으로, Pod의 spec.containers[].resources.requests에 정의된 CPU 또는 메모리 양을 노드 풀의 가용 리소스가 충족하지 못할 때 발생합니다.

  • 진단: kubectl describe podEvents에서 Insufficient cpu, Insufficient memory 메시지 확인
  • 해결:
  • kubectl top nodes로 노드별 리소스 사용량 확인
  • 노드 풀 확장 (gcloud container node-pools update --enable-autoscaling)
  • Pod의 리소스 요청 값 조정 또는 LimitRange 정책 적용

2. 이미지 풀 실패 (ImagePullBackOff / ErrImagePull)

컨테이너 이미지를 Container Registry에서 가져오지 못하는 경우.

  • 원인:
  • 잘못된 이미지 태그 (latest 오용, 태그 누락)
  • 프라이빗 레지스트리 접근 권한 부족
  • 네트워크 정책에 의한 아웃바운드 차단 (VPC Firewall, Cloud NAT 미설정)
  • GCR 도메인(gcr.io, pkg.dev)에 대한 DNS 해석 실패

  • 진단: kubectl describe podFailed to pull image "registry/...": rpc error: code = Unknown desc = Error response from daemon: ...

  • 해결:
  • gcloud auth configure-docker로 로컬 인증 확인
  • 노드의 서비스 계정에 roles/storage.objectViewer 부여
  • ImagePullPolicy: IfNotPresent 설정 또는 캐시된 이미지 사용

3. 노드 스케줄링 제약 조건 (Node Selector, Affinity, Taints/Tolerations)

Pod에 nodeSelector, affinity, taint 관련 설정이 잘못되었거나, 노드에 NoSchedule taint가 적용된 경우.

  • 예시:
  nodeSelector:
    cloud.google.com/gke-nodepool: wrong-pool-name


→ 해당 레이블을 가진 노드가 없으면 영구 Pending

  • 해결:
  • kubectl get nodes --show-labels로 노드 레이블 확인
  • taint 확인: kubectl get nodes -o json | jq '.items[].spec.taints'
  • 필요 시 toleration 추가 또는 taint 제거

4. PersistentVolumeClaim(PVC) 바인딩 실패

Pod이 PVC를 마운트하려 하지만, 해당 볼륨이 생성되지 않았거나 지역/스토리지 클래스 불일치.

  • 진단: Pending + FailedMount 이벤트
  • 해결:
  • kubectl get pvc로 상태 확인 (Pending → StorageClass 또는 지역 문제)
  • StorageClassprovisionerallowVolumeExpansion 설정 점검

5. 네트워크 및 보안 정책 문제

  • Cloud NAT 미설정: Private 클러스터에서 외부 레지스트리 접근 불가
  • Firewall 규칙 차단: 노드 → GCR 도메인(443 포트) 아웃바운드 차단
  • Workload Identity 미흡: Pod이 서비스 계정을 사용하지 않아 권한 부족
  • 해결:
  • Private GKE 클러스터는 Cloud NAT 또는 Authorized Networks 설정 필수
  • gcloud container clusters update --enable-master-authorized-networks 고려

6. Cluster Autoscaler 또는 Node Provisioning 지연

리소스 부족 시 자동 확장은 하지만, 새로운 노드 생성에 3~5분 소요 → 일시적 Pending 정상

  • 구분법: kubectl get eventsCluster autoscaler triggered scale-up 메시지 존재
  • 해결: 대기하거나, --autoscaling 옵션으로 최소/최대 노드 수 조정

실전 진단 플로우차트

graph TD
    A[Pod Pending?] --> B{kubectl describe pod}
    B --> C{이벤트 메시지?}
    C -->|FailedScheduling| D[리소스/스케줄링 문제]
    C -->|ImagePullBackOff| E[이미지/네트워크/권한 문제]
    C -->|FailedMount| F[PVC/볼륨 문제]
    C -->|기타| G[기타 로그 분석]

    D --> H[리소스 부족?]
    H -->|Yes| I[노드 풀 확장 / 요청 조정]
    H -->|No| J[taint, selector, affinity 확인]

    E --> K[이미지 태그 확인]
    K --> L[권한/IAM 확인]
    L --> M[네트워크/NAT/Firewall 확인]

고급 팁: 자동화된 진단 스크립트 예시

#!/bin/bash
POD=$1
echo "=== Pod Events ==="
kubectl describe pod $POD | grep -A 10 Events:

echo -e "\n=== Node Resources ==="
kubectl top nodes

echo -e "\n=== Node Labels & Taints ==="
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints[],LABELS:.metadata.labels

echo -e "\n=== PVC Status ==="
kubectl get pvc --all-namespaces

결론

GKE(Google Kubernetes Engine)에서 Pod이 Pending 상태에 장기간 머무르는 현상은 단순히 “일시적인 대기”로 치부할 수 있는 수준을 넘어, 전체 클러스터 운영의 건강도와 안정성을 직접적으로 반영하는 핵심 지표입니다. 이 상태는 Kubernetes 스케줄러가 Pod을 적절한 노드에 배치하지 못했거나, 배치 후에도 컨테이너 실행에 필요한 전제 조건(이미지 다운로드, 리소스 확보, 볼륨 마운트, 네트워크 연결 등)이 충족되지 않았음을 명확히 드러내며, 방치할 경우 애플리케이션 배포 지연, 서비스 가용성 저하, 운영팀의 대응 부담 증가로 이어질 수 있습니다.

이 문제를 신속하고 정확하게 해결하려면, kubectl describe pod 명령어로 노출되는 Events 로그를 정밀하게 분석하는 것이 출발점입니다. 이후 리소스 → 스케줄링 제약 → 컨테이너 이미지 → 네트워크 연결성 → 보안 및 IAM 정책의 순서로 체계적이고 계층적인 점검을 진행해야 합니다. 이 순서는 단순한 체크리스트가 아니라, Kubernetes 스케줄링 아키텍처의 동작 원리를 기반으로 한 논리적 흐름이며, 대부분의 Pending 문제를 80% 이상 커버할 수 있는 검증된 진단 프레임워크입니다.

특히 GKE의 관리형 특성을 최대한 활용하는 것이 예방의 핵심입니다. 예를 들어:

  • Cluster Autoscaler를 활성화하여 리소스 부족 시 자동으로 노드를 확장하고,
  • Workload Identity를 통해 Pod별로 안전한 Google Cloud 권한을 부여하며,
  • VPC-native 클러스터Private Google Access를 조합해 네트워크 보안을 강화하고,
  • Autopilot 모드를 도입해 노드 관리 부담을 최소화하면,

Pending 상태 발생 빈도를 근본적으로 줄일 수 있습니다. 또한, Node Auto-provisioning, Vertical Pod Autoscaler(VPA), Horizontal Pod Autoscaler(HPA)를 적절히 조합하면, 워크로드 변화에 동적으로 대응하는 자기 치유형 클러스터를 구현할 수 있습니다.


Disclaimer: 본 블로그의 정보는 개인의 단순 참고 및 기록용으로 작성된 것이며, 개인적인 조사와 생각을 담은 내용이기에 오류가 있거나 편향된 내용이 있을 수 있습니다.