# GPU는 쪼개 쓸 수 없다 - 확장 리소스와 디바이스 플러그인
CPU는 500m처럼 소수로 요청할 수 있고, 노드에 남은 것보다 많이 배정하는 오버커밋도 가능합니다. GPU에 같은 감각으로 접근하면 곧바로 막힙니다. nvidia.com/gpu: 0.5는 스케줄링되지 않고, GPU 한 장을 두 파드가 나눠 쓰게 만드는 설정도 기본 제공되지 않습니다.
이 차이는 구현 편의가 아니라 확장 리소스(extended resource)의 설계 자체에서 나옵니다. 이 글에서는 GPU가 어떤 경로로 스케줄러에게 보이는지, 왜 정수만 허용되는지, 그리고 여러 종류의 GPU가 섞인 클러스터에서 스케줄링을 어떻게 통제하는지 정리합니다.
# 1. GPU는 코어 리소스가 아니다
cpu와 memory는 kubelet이 직접 계산해 보고하는 코어 리소스입니다. GPU는 그렇지 않습니다. 디바이스 플러그인이 노드에 있는 장치를 발견해 kubelet에 등록하고, kubelet이 그것을 확장 리소스로 API 서버에 광고합니다. 이름은 벤더별로 다릅니다.
nvidia.com/gpuamd.com/gpuintel.com/gpu
이름에 슬래시가 들어간 것이 힌트입니다. 도메인이 붙은 리소스는 전부 확장 리소스이고, Kubernetes 코어는 그 의미를 모릅니다. 스케줄러 입장에서 GPU는 그냥 "노드에 N개 있는 셀 수 있는 무언가" 입니다. 계산 능력도, 메모리 크기도, 모델명도 모릅니다.
여기서 첫 번째 결론이 나옵니다. 플러그인이 안 떠 있으면 GPU는 존재하지 않는 것과 같습니다. 노드에 물리적으로 카드가 꽂혀 있어도 스케줄러는 볼 수 없습니다.
# 노드가 실제로 광고하는 값
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu'
# 값이 비어 있으면 플러그인부터 확인
kubectl get pods -n kube-system -l k8s-app=nvidia-device-plugin-daemonset
# 2. 요청 방식의 제약
확장 리소스에는 코어 리소스에 없는 규칙이 붙습니다.
- GPU는
limits에 지정합니다. limits만 쓰면 Kubernetes가 같은 값을requests로 씁니다.- 둘 다 쓸 수 있지만 값이 같아야 합니다.
requests만 쓰고limits를 빼면 안 됩니다.- 정수여야 합니다. GPU의 일부를 요청할 수 없습니다.
- 오버커밋도, 컨테이너 간 공유도 기본으로는 불가능합니다.
3번과 6번을 이어 보면 중요한 성질이 드러납니다. GPU를 쓰는 파드는 requests == limits가 강제되므로, 컨테이너 자원 설정에서 GPU 항목만큼은 항상 Guaranteed 조건을 만족합니다. 다만 파드 전체의 QoS 클래스는 CPU와 메모리까지 봐야 정해집니다. 이 관계는 메모리 request=limit이 노드를 지키는 이유 (opens new window)에 정리했습니다. GPU만 맞춰 놓고 메모리를 열어 두면 여전히 Burstable입니다.
resources:
limits:
nvidia.com/gpu: 1 # 정수, limits 에 지정
memory: "32Gi"
cpu: "8"
requests:
memory: "32Gi" # GPU 워크로드는 메모리도 맞추는 편이 안전하다
cpu: "8"
6번이 만드는 운영상의 결과는 큽니다. GPU 한 장은 한 컨테이너가 통째로 점유합니다. 작은 모델을 여러 개 띄우려고 해도 카드 수만큼만 파드가 뜹니다. 이 제약을 우회하려면 벤더가 제공하는 별도 메커니즘(시분할, MIG 같은 하드웨어 분할)을 도입해야 하고, 그것은 기본 스케줄링이 아니라 플러그인 설정 영역입니다. "파드를 늘리면 되겠지"가 통하지 않는 유일한 자원이라고 생각하는 편이 맞습니다.
# 3. GPU 종류가 섞인 클러스터
스케줄러가 카드 종류를 모른다는 사실이 여기서 문제가 됩니다. T4와 L40S가 같은 클러스터에 있으면, nvidia.com/gpu: 1 요청은 둘 중 아무 데나 갈 수 있습니다. 큰 모델이 작은 카드에 배정되어 OOM으로 죽는 사고가 여기서 나옵니다.
해결은 라벨입니다. 문서가 제시하는 기본 방식은 노드에 라벨을 붙이고 셀렉터로 고르는 것입니다.
kubectl label nodes node1 accelerator=example-gpu-x100
kubectl label nodes node2 accelerator=other-gpu-k915
노드가 늘어나면 수동 라벨링은 유지되지 않습니다. Node Feature Discovery(NFD)를 붙이면 하드웨어 특성을 자동으로 감지해 라벨로 광고하고, 그 라벨로 노드 어피니티를 걸 수 있습니다. 테인트와 조합해 특정 워크로드만 그 노드에 들어오게 막을 수도 있습니다.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "feature.node.kubernetes.io/pci-10.present"
operator: In
values: ["true"]
여기에 테인트를 더하는 이유는 방향이 반대이기 때문입니다. 어피니티는 "이 파드를 GPU 노드로 보내라"이고, 테인트는 "GPU가 필요 없는 파드를 GPU 노드에서 몰아내라"입니다. 둘 다 있어야 비싼 노드가 일반 워크로드로 채워지는 것을 막을 수 있습니다.
# GPU 노드에 테인트
# kubectl taint nodes gpu-1 nvidia.com/gpu=present:NoSchedule
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
테인트는 스프레드 계산에도 영향을 줍니다. topologySpreadConstraints의 nodeTaintsPolicy 기본값이 Ignore라서 갈 수 없는 GPU 노드까지 도메인에 포함됩니다. 이 문제는 파드를 흩뿌리는 두 가지 방법 (opens new window)에 정리했습니다.
# 4. 자원 회계가 어긋나는 지점
GPU 노드에서 자주 겪는 혼란이 하나 더 있습니다. 할당(allocated)과 실제 사용률이 완전히 별개라는 점입니다.
# 스케줄러 관점: 몇 장이 배정됐는가
kubectl describe node <gpu-node> | grep -A5 "Allocated resources"
# 실제 관점: 그 카드가 일을 하고 있는가
kubectl exec -it <pod> -- nvidia-smi
파드가 GPU 1장을 점유한 채 아무 계산도 하지 않아도, 스케줄러에게 그 카드는 사용 중입니다. 다른 파드는 배정받지 못합니다. CPU라면 유휴 시간을 다른 파드가 가져가지만 GPU는 그렇지 않습니다. 유휴 GPU를 찾는 일은 스케줄러가 해 주지 않으므로, 별도 지표 수집이 필요합니다. DCGM 계열 익스포터로 실사용률을 따로 봐야 "배정은 100%인데 실사용은 20%"인 상태를 발견할 수 있습니다.
이 성질은 노드 축소에도 영향을 줍니다. 오토스케일러 입장에서 GPU 파드가 하나라도 붙어 있는 노드는 비어 있지 않습니다. 유휴 GPU 파드를 정리하는 정책이 없으면 비싼 노드가 계속 살아 있습니다.
# 5. 트러블슈팅
| 증상 | 원인 | 확인·조치 |
|---|---|---|
| GPU 파드가 계속 Pending | 노드가 GPU를 광고하지 않음 | allocatable에 리소스 이름이 있는지, 플러그인 파드 상태 |
0.5 요청이 거부됨 | 확장 리소스는 정수만 | 정수로 변경, 분할이 필요하면 벤더 메커니즘 검토 |
파드는 떴는데 nvidia-smi가 안 보임 | 런타임 설정 또는 이미지 문제 | 컨테이너 런타임의 GPU 지원 설정 확인 |
| 큰 모델이 작은 카드에 배정됨 | 스케줄러는 카드 종류를 모름 | 노드 라벨 + 노드 어피니티 |
| GPU 노드가 일반 파드로 채워짐 | 테인트 없음 | 테인트 + 톨러레이션 |
| 배정률은 높은데 사용률이 낮음 | 점유와 사용은 별개 | 사용률 지표 수집 후 유휴 파드 정리 정책 |
# 6. 마무리
- GPU는 코어 리소스가 아니라 디바이스 플러그인이 광고하는 확장 리소스입니다. 플러그인이 없으면 카드가 꽂혀 있어도 없는 것과 같습니다.
- 정수만 허용되고 오버커밋과 공유가 기본으로 막혀 있습니다. 파드를 늘려서 처리량을 올리는 접근이 통하지 않는 유일한 자원입니다.
- 스케줄러는 카드 종류를 모릅니다. 라벨과 노드 어피니티로 알려 주고, 테인트로 반대 방향을 막아야 합니다.
- 배정과 실사용은 별개입니다. 실사용률은 별도로 수집해야 유휴 GPU를 발견할 수 있습니다.