본문 바로가기
Cloud Computing & MSA

쿠버네티스 파드가 Pending에서 안 뜰 때

oopennova·2026년 9월 20일·조회 2

새 노드에 파드를 올리거나 매니페스트를 손본 직후, 파드가 Pending에서 한참을 안 넘어가는 상황은 운영하다 보면 주기적으로 만난다. 급하면 노드부터 하나 더 붙이고 싶어지는데, 원인을 안 보고 노드를 늘렸다가 문제가 taint나 PVC 쪽이라 그대로였던 적이 있다. 그래서 파드가 안 뜰 때는 손보다 눈이 먼저 가야 한다.

결론부터 말하자면, kubectl describe pod를 실행해 맨 아래 Events의 FailedScheduling 한 줄을 읽으면 원인 대부분이 갈린다. 스케줄러가 왜 노드를 못 골랐는지를 그 메시지에 그대로 적어주기 때문이다. 리소스 부족, taint 불일치, nodeAffinity 불일치, PVC 바인딩 실패가 서로 다른 문구로 나오므로, 문구만 매칭하면 5분 안에 원인을 특정할 수 있다.

1. Pending의 의미와 스케줄러의 역할

파드가 Pending이라는 건 아직 어느 노드에도 배치되지 않았다는 뜻이다. 쿠버네티스에는 kube-scheduler라는 컴포넌트가 있는데, 새로 생긴 파드를 보고 클러스터의 모든 노드를 훑어 조건에 맞는 노드를 하나 고르는 일을 한다. 이 배치에 성공해야 파드가 노드에 붙고, kubelet이 컨테이너를 띄우기 시작한다.

스케줄러가 어느 노드도 고르지 못하면 파드는 계속 Pending에 남고, 그때마다 파드에 FailedScheduling 이벤트를 남긴다. 이 이벤트는 노드 몇 개 중 몇 개가 왜 탈락했는지를 짧게 요약한다. 예를 들어 노드 3개짜리 클러스터에서 CPU가 부족하면 0/3 nodes are available: 3 Insufficient cpu 같은 식이다.

그러니 진단의 출발점은 항상 이 이벤트다. 로그를 뒤지거나 노드에 SSH로 들어가기 전에, 파드 이벤트부터 읽는다.

2. 먼저 이벤트를 읽는다

파드 상태를 확인하고, 해당 파드를 describe한다.

$ kubectl get pod web-0
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          2m14s

$ kubectl describe pod web-0

출력이 길지만 볼 곳은 맨 아래 Events 한 곳이다.

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  2m    default-scheduler  0/3 nodes are available: 3 Insufficient cpu.
           preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.

메시지가 비어 있고 파드가 방금 만들어졌다면 스케줄러가 아직 돌기 전일 수 있으니 몇 초 뒤 다시 본다. 이벤트가 안 보이면 이벤트 보존 시간(기본 1시간)이 지났을 수 있는데, 그럴 땐 kubectl get events --sort-by=.lastTimestamp로 네임스페이스 이벤트를 다시 확인한다.

아래에서는 이 FailedScheduling 문구별로 원인과 확인 명령, 해결을 차례로 살펴본다.

3. Insufficient cpu / Insufficient memory - 리소스 부족

메시지에 Insufficient cpu 또는 Insufficient memory가 있으면, 파드가 요청한 리소스(requests)를 담을 여유가 있는 노드가 없다는 뜻이다. 여기서 기준은 노드에 실제로 남은 여유가 아니라, 이미 배치된 파드들의 requests 합계다. 실제 사용량이 낮아도 requests가 다 예약돼 있으면 탈락한다.

확인은 노드의 할당 현황을 보는 것이 정확하다.

$ kubectl describe node ip-10-0-1-23
...
Allocatable:
  cpu:                1930m
  memory:             3420Mi
...
Allocated resources:
  Resource           Requests      Limits
  --------           --------      ------
  cpu                1800m (93%)   2 (103%)
  memory             2900Mi (84%)  3Gi (89%)

여기서 Allocatable cpu가 1930m인데 이미 1800m이 requests로 예약돼 있다. 내 파드가 requests.cpu: 500m을 달라고 하면 130m밖에 안 남았으니 들어갈 자리가 없다. kubectl top nodes로 실제 사용률을 같이 보면 예약과 실사용의 차이가 눈에 들어온다.

해결은 세 갈래다. 파드의 requests를 실제 필요에 맞게 낮추거나, 놀고 있는 파드를 정리하거나, 노드를 늘린다. 오토스케일러를 쓰는 클러스터라면 requests가 과하게 잡혀 있어 스케일아웃이 계속 트리거되는 경우도 흔하니, 노드부터 늘리기 전에 requests가 현실적인지부터 본다.

4. untolerated taint - taint/toleration 불일치

taint는 노드에 "이런 파드는 받지 않겠다"고 붙이는 표식이고, toleration은 파드 쪽에서 "그 표식을 견딜 수 있다"고 선언하는 짝이다. 전용 노드(GPU 노드, 컨트롤플레인 등)를 특정 파드에만 열어줄 때 쓴다. 파드에 맞는 toleration이 없으면 그 노드는 후보에서 빠진다.

이때 이벤트는 이렇게 나온다.

Events:
  Warning  FailedScheduling  1m  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) had untolerated taint {gpu: true}.

메시지가 어떤 taint 때문인지 키까지 찍어준다. 노드에 걸린 taint는 이렇게 확인한다.

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME           TAINTS
ip-10-0-1-23   [map[effect:NoSchedule key:gpu value:true]]
ip-10-0-1-24   [map[effect:NoSchedule key:gpu value:true]]

taint effect는 세 가지다. NoSchedule은 toleration 없는 새 파드를 안 받고(기존 파드는 유지), PreferNoSchedule은 가급적 피하는 약한 버전, NoExecute는 toleration 없는 파드를 즉시 쫓아낸다.

파드가 그 노드에 들어가야 한다면 매니페스트에 toleration을 추가한다. 키, effect가 같아야 하고 operator에 따라 값 비교 방식이 갈린다.

spec:
  tolerations:
  - key: "gpu"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

여기서 자주 걸리는 함정 하나. toleration은 "그 노드에 갈 수 있다"는 허가일 뿐 "그 노드로 보내라"는 지시가 아니다. 특정 노드에만 확실히 보내려면 toleration에 더해 nodeAffinity나 nodeSelector로 목적지를 지정해야 한다. 반대로 노드의 taint가 실수로 붙은 거라면 kubectl taint nodes ip-10-0-1-23 gpu=true:NoSchedule-처럼 끝에 하이픈을 붙여 제거한다.

5. node affinity/selector 불일치

파드에 nodeSelectornodeAffinity를 걸면 특정 라벨을 가진 노드에만 배치된다. 그 라벨을 가진 노드가 없으면 후보가 0이 되어 Pending에 남는다. 이벤트는 이렇게 나온다.

Events:
  Warning  FailedScheduling  30s  default-scheduler  0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.

파드가 요구하는 라벨과 노드가 실제로 가진 라벨을 맞춰본다.

$ kubectl get pod web-0 -o jsonpath='{.spec.nodeSelector}'
{"disktype":"ssd"}

$ kubectl get nodes -l disktype=ssd
No resources found

파드는 disktype=ssd 노드를 찾는데 그런 라벨을 가진 노드가 없다. 오타(ssd vs SSD)이거나, 라벨을 안 붙였거나, 해당 노드가 빠졌을 수 있다. 노드에 라벨을 붙여준다.

$ kubectl label node ip-10-0-1-23 disktype=ssd
node/ip-10-0-1-23 labeled

requiredDuringSchedulingIgnoredDuringExecution로 건 affinity는 조건을 만족하는 노드가 없으면 이렇게 Pending으로 끝난다. 반드시 특정 노드여야 하는 게 아니라면 preferredDuringScheduling...으로 완화하는 편을 검토한다.

6. unbound PersistentVolumeClaims - PVC 바인딩 실패

파드가 볼륨으로 PVC를 참조하는데 그 PVC가 아직 실제 볼륨(PV)에 연결되지 않았으면, 스케줄러는 파드를 노드에 붙이지 못한다. 이벤트 문구는 다음과 같다.

Events:
  Warning  FailedScheduling  45s  default-scheduler  0/3 nodes are available: pod has unbound immediate PersistentVolumeClaims.

이때는 파드가 아니라 PVC 상태를 본다.

$ kubectl get pvc
NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-web   Pending                                      standard       1m

$ kubectl describe pvc data-web
...
Events:
  Warning  ProvisioningFailed  20s  persistentvolume-controller  storageclass.storage.k8s.io "standard" not found

여기서는 standard라는 StorageClass가 없어서 동적 프로비저닝이 실패했다. kubectl get storageclass로 클러스터에 실제로 있는 StorageClass 이름을 확인하고, PVC의 storageClassName을 맞춘다. 기본 StorageClass가 지정돼 있으면 필드를 비워도 된다.

StorageClass는 맞는데도 PVC가 Pending이면, 정적 PV를 쓰는 환경에서 조건에 맞는 PV가 없거나(용량, accessModes 불일치), 클라우드 프로비저너가 특정 존에만 볼륨을 만드는데 파드가 다른 존으로 스케줄되려는 topology 문제일 수 있다. describe pvc의 이벤트가 대개 어느 쪽인지 알려준다.

7. 5분 진단 순서 정리

파드가 Pending일 때 다음 순서로 움직이면 원인 특정까지 오래 걸리지 않는다.

  1. kubectl describe pod <name>으로 Events의 FailedScheduling 문구를 읽는다.
  2. Insufficient cpu/memorykubectl describe nodekubectl top nodes로 requests 예약 현황을 본다.
  3. untolerated taint면 노드 taint를 보고 toleration을 추가하거나 taint를 제거한다.
  4. didn't match Pod's node affinity/selector면 파드가 요구하는 라벨과 노드 라벨을 대조한다.
  5. unbound ... PersistentVolumeClaimskubectl describe pvc로 StorageClass와 프로비저닝 이벤트를 확인한다.

이벤트에 아무 문구도 없이 그냥 안 뜨는 경우도 있다. 스케줄러 파드 자체가 죽었거나, 노드가 전부 NotReady거나, 파드가 hostPort를 잡아 자리가 극단적으로 좁아진 상황이다. 그럴 땐 kubectl get nodes와 컨트롤플레인 상태부터 확인한다. 핵심은 하나다. 노드를 늘리기 전에 이벤트를 먼저 읽는다.

자주 묻는 질문

kubectl describe pod를 봐도 Events가 비어 있으면 어떻게 하나?

파드가 방금 생성됐으면 스케줄러가 아직 안 돈 상태일 수 있으니 몇 초 뒤 다시 본다. 그래도 없으면 이벤트 보존 시간(기본 1시간)이 지난 것일 수 있다. kubectl get events --sort-by=.lastTimestamp 로 네임스페이스 전체 이벤트를 다시 확인한다. 그래도 아무 스케줄링 이벤트가 없다면 kube-scheduler 파드가 정상인지, 노드가 전부 NotReady는 아닌지 kubectl get nodes 로 점검한다.

Insufficient cpu인데 kubectl top nodes로 보면 CPU 사용률이 낮다. 왜 그런가?

스케줄러는 실제 사용량이 아니라 이미 배치된 파드들의 requests 합계로 여유를 판단하기 때문이다. 파드들이 requests만 크게 예약하고 실제로는 놀고 있으면 사용률이 낮아도 자리가 없다고 나온다. kubectl describe node의 Allocated resources에서 Requests 비율을 보고, 과하게 잡힌 파드의 requests를 현실적으로 낮추는 것을 먼저 검토한다.

toleration을 추가했는데도 원하는 노드로 안 간다.

toleration은 tainted 노드에 갈 수 있다는 허가일 뿐 그 노드로 보내라는 지시가 아니다. 특정 노드에 확실히 배치하려면 toleration에 더해 nodeSelector나 nodeAffinity로 목적지를 지정해야 한다. 또한 toleration의 key, effect, value가 노드 taint와 정확히 일치하는지, operator가 Equal이면 value까지 같은지 확인한다.

PVC가 Pending인데 StorageClass는 존재한다. 원인이 뭘까?

동적 프로비저닝 환경이면 프로비저너가 볼륨을 만들지 못하는 상황일 수 있다. 클라우드에서 특정 존에만 볼륨을 만드는데 파드가 다른 존으로 가려는 topology 불일치가 대표적이다. 정적 PV를 쓰는 환경이면 요청한 용량이나 accessModes에 맞는 PV가 없는 경우다. kubectl describe pvc의 Events가 어느 쪽인지 대개 알려준다.

Pending과 ContainerCreating은 뭐가 다른가?

Pending은 아직 노드에 배치조차 안 된 스케줄링 단계의 문제다. ContainerCreating은 이미 노드에 배치됐고 kubelet이 컨테이너를 띄우는 중인데 이미지 풀이나 볼륨 마운트, 시크릿/컨피그맵 참조에서 막힌 상태다. Pending은 kubectl describe pod의 스케줄러 이벤트를, ContainerCreating은 kubelet 이벤트와 노드 로그를 본다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.