Pod가 Pending에서 멈출 때: 원인 7가지와 확인 순서

5 분 소요

배포했는데 Pod가 Pending에서 움직이지 않습니다. Pending은 에러가 아니라 “실행할 노드가 아직 정해지지 않았다"는 상태라서, 로그도 없고(컨테이너가 시작 전이므로 kubectl logs는 아무것도 보여 주지 않습니다) 원인이 바로 보이지 않습니다. 이 글은 Pending의 원인을 실무 빈도순으로 7가지 정리합니다. 시험 관점의 트러블슈팅 전반은 CKA #22에서 다뤘고, 여기서는 운영 중 만나는 시나리오에 집중합니다.

확인은 한 곳에서 시작합니다: Events #

Pending의 이유는 거의 항상 스케줄러가 Events에 적어 둡니다.

kubectl describe pod
$ kubectl describe pod api-server-7d4b9c-x2k8p
...
Events:
  Type     Reason            Message
  ----     ------            -------
  Warning  FailedScheduling  0/6 nodes are available: 3 Insufficient cpu,
                             2 node(s) had untolerated taint {gpu: "true"},
                             1 node(s) didn't match Pod's node affinity/selector.

이 메시지가 진단의 전부에 가깝습니다. “노드 6대 중 0대 가능"과 그 내역(CPU 부족 3대, taint 2대, affinity 불일치 1대)이 나오므로, 아래 원인 목록은 이 메시지를 해석하는 사전이라고 보면 됩니다.

1. 리소스 부족: Insufficient cpu/memory #

가장 흔한 원인입니다. 스케줄러는 노드의 실사용량이 아니라 requests의 합으로 자리를 계산합니다. Insufficient cpu는 “모든 노드에서 남은 할당 가능량(allocatable)보다 이 Pod의 requests가 크다"는 뜻입니다.

  • 확인: kubectl describe node에서 노드별 Allocated resources를 보면 requests 합이 얼마나 찼는지 나옵니다.
  • 처방: 노드 증설이나 다른 워크로드 정리가 기본 대응입니다. 다만 그 전에 다음 항목(과도한 requests)을 먼저 의심해 볼 가치가 있습니다.

2. 과도한 requests: 실사용은 낮은데 예약이 가득 #

kubectl top node의 실사용률은 30%인데 Insufficient cpu가 나온다면, 누군가의 requests가 실제보다 크게 잡혀 있는 것입니다. requests는 예약이라서, 쓰지 않아도 자리를 차지합니다. “일단 크게” 잡은 requests가 쌓이면 클러스터의 절반이 빈 예약으로 잠깁니다.

  • 확인: 워크로드별 requests와 실사용을 나란히 봅니다(메트릭 서버, 또는 VPA 권고 모드).
  • 처방: 실측 기반으로 requests를 재조정합니다. 기준 잡는 법은 K8s 중급 #4의 개념 위에서 별도 글로 다룹니다.

3. nodeSelector·affinity 불일치 #

didn't match Pod's node affinity/selector가 보이면, Pod가 요구하는 라벨을 가진 노드가 없거나 부족한 경우입니다. 노드 라벨이 변경·삭제됐거나, 매니페스트의 라벨 오타(disktype: ssd vs diskType: ssd)가 전형입니다.

  • 확인: kubectl get nodes --show-labels로 실제 라벨과 매니페스트의 요구를 대조합니다.
  • 처방: 라벨을 맞추거나, 반드시 필요한 제약이 아니면 required를 preferred affinity로 완화합니다.

4. taint를 견디는 toleration이 없음 #

untolerated taint는 노드 쪽이 “아무나 오지 마라"고 선언한 경우입니다. GPU 노드, 전용 노드 풀, 그리고 컨트롤 플레인 노드의 기본 taint가 해당합니다. 클러스터에 GPU 노드만 남아 있으면 일반 Pod가 전부 Pending이 되는 식의 사고가 납니다.

  • 확인: kubectl describe node <node> | grep Taints로 봅니다.
  • 처방: 의도된 전용 노드라면 일반 워크로드용 노드를 확보하고, 그 노드에 가야 하는 Pod라면 toleration을 추가합니다.

5. PVC가 바인딩되지 않음 #

볼륨을 쓰는 Pod는 PVC가 준비돼야 스케줄됩니다. waiting for a volume to be created나 PVC가 Pending이면 이쪽입니다. StorageClass 이름 오타, 프로비저너 미설치, 그리고 WaitForFirstConsumer 모드에서 다른 제약과 얽혀 노드를 못 정하는 경우가 흔합니다. AZ가 있는 클라우드에서는 “볼륨은 a존에 있는데 스케줄 가능한 노드는 b존뿐"인 지역 불일치도 단골입니다.

  • 확인: kubectl get pvckubectl describe pvc의 Events를 봅니다.
  • 처방: StorageClass 확인, 존 분산 재검토(볼륨을 쓰는 워크로드의 affinity를 존과 함께 설계)입니다.

6. ResourceQuota·LimitRange 위반 #

네임스페이스에 쿼터가 걸려 있으면 노드에 자리가 있어도 거부됩니다. 이 경우 Pod가 생성조차 안 되거나(ReplicaSet Events에 기록) exceeded quota 메시지가 남습니다. 신규 팀 온보딩 때 기본 쿼터를 복사해 쓰다가 걸리는 패턴이 많습니다.

  • 확인: kubectl describe quota -n <ns>와 ReplicaSet·Deployment의 Events를 봅니다.
  • 처방: 쿼터 조정 또는 워크로드 requests 축소입니다.

7. Cluster Autoscaler가 안 늘어나는 경우 #

“오토스케일러가 있으니 노드가 자동으로 늘겠지"라는 기대가 어긋나는 경우입니다. 노드 그룹 최대치 도달, 클라우드 쪽 인스턴스 한도·용량 부족(특정 타입 품절), 그리고 Pod의 제약(affinity·taint) 때문에 늘려도 어차피 못 갈 노드라서 안 늘리는 경우가 있습니다. Autoscaler는 이유를 이벤트와 로그에 남깁니다.

  • 확인: kubectl describe podNotTriggerScaleUp 이벤트와 autoscaler 로그를 봅니다.
  • 처방: 노드 그룹 상한·인스턴스 타입 다변화, Pod 제약 완화입니다.

진단 순서 정리 #

  1. kubectl describe pod의 Events부터 읽습니다. FailedScheduling 메시지가 원인 분포를 알려 줍니다.
  2. Insufficient면 describe node의 Allocated resources로 예약 포화를 확인하고, 실사용과 대조해 과대 requests를 찾습니다.
  3. selector/taint면 노드 라벨·taint와 매니페스트를 대조합니다.
  4. 볼륨 워크로드면 PVC 상태와 존 배치를 봅니다.
  5. 쿼터 메시지면 네임스페이스 쿼터를, 오토스케일러 환경이면 NotTriggerScaleUp 이벤트를 확인합니다.

정리 #

  • Pending은 에러가 아니라 “갈 노드가 없다"는 상태이고, 이유는 거의 항상 describe의 FailedScheduling 이벤트에 적혀 있습니다.
  • 스케줄러는 실사용이 아니라 requests 합으로 계산합니다. 실사용이 낮은데 자리가 없다면 과대 requests부터 의심합니다.
  • 라벨·taint 불일치는 매니페스트와 노드 상태의 대조로 바로 잡힙니다.
  • 볼륨 Pod는 PVC와 존 배치까지, 오토스케일러 환경은 “왜 안 늘렸는지” 이벤트까지 봐야 끝납니다.
  • 같은 메시지가 반복되면 개별 처방보다 requests 산정 기준과 노드 풀 설계를 손보는 쪽이 근본 처방입니다.
X