스케줄러는 GPU 를 세기만 한다
한 줄 요약
쿠버네티스는 GPU 를 모릅니다. 노드의 kubelet 은 CPU 와 메모리만 직접 세고, GPU 는 「장치 플러그인」이 「이 노드에 nvidia.com/gpu 가 몇 개 있다」 고 알려 준 숫자로만 압니다. 스케줄러는 그 숫자를 빼고 더할 뿐 카드가 무엇인지 보지 않습니다. 그래서 숫자만 그럴듯하게 광고하는 「가짜 GPU」 로도 배치 결정은 진짜와 똑같이 일어납니다.
왜 이게 필요했나
GPU 클러스터의 정책을 시험하려면 GPU 가 필요하다는 것이 문제의 출발점입니다. 큐 쿼터를 바꿔 보고, 갱 스케줄링이 정말 한꺼번에 띄우는지 보고, 공정 분배가 선점을 어떻게 일으키는지 보려면 GPU 가 여러 장 꽂힌 노드가 여러 대 있어야 합니다. 그런데 그런 노드는 비싸고, 대개 누군가의 학습이 이미 돌고 있습니다. 정책을 시험하다 운영 작업을 쫓아내면 그 시험은 사고가 됩니다.
여기서 한 가지 사실이 길을 엽니다. 스케줄러가 GPU 에 대해 아는 것은 「노드 status 의 숫자와 노드 라벨」뿐입니다. 드라이버도 CUDA 도 메모리 대역폭도 보지 않습니다. 그 숫자와 라벨을 만들어 주는 쪽을 가짜로 바꾸면, 스케줄러와 그 위의 큐·묶음 컨트롤러는 진짜 GPU 클러스터에서와 같은 결정을 내립니다. run.ai 의 fake-gpu-operator 가 이 일을 하고, 이 코스의 VM 실습은 전부 그 위에서 돕니다.
어떻게 동작하나
장치 플러그인은 kubelet 과 gRPC 로 대화하는 작은 데몬입니다. 순서는 이렇습니다.
1. Register 플러그인이 kubelet 소켓에 「nvidia.com/gpu 를 맡겠다」 고 등록한다
2. ListAndWatch 장치 ID 목록과 건강 상태를 스트림으로 계속 보낸다
3. kubelet 그 개수를 노드 status 의 capacity·allocatable 에 적는다
4. 스케줄러 파드의 limits 와 노드의 allocatable - 이미 배치된 요청 을 비교해 자리를 고른다
5. Allocate 파드가 그 노드에 오면 kubelet 이 플러그인에게 장치 ID 를 주고 받을 것을 묻는다
(환경 변수, 마운트, 장치 파일, 또는 CDI 이름)
확장 자원에는 CPU 와 다른 규칙 세 가지가 붙습니다. 정수만 됩니다(0.5 장은 API 서버가 거절합니다). requests 를 적는다면 limits 와 같아야 합니다(초과 할당이 없습니다). 그리고 노드가 광고한 숫자를 넘겨 배치하지 않습니다. 이 세 규칙 때문에 「한 장을 나눠 쓰기」 는 장치 플러그인 바깥의 기술(타임슬라이싱이 숫자를 부풀리거나, MIG 가 카드를 쪼개 다른 이름으로 광고하거나)이 필요해집니다. 그 이야기는 GPU Operator 와 타임슬라이싱 코스의 「한 장을 여럿이 쓴다는 말의 뜻」 모듈에서 다룹니다.
fake-gpu-operator 는 이 경로를 그대로 흉내 냅니다.
status-updater 풀 라벨(run.ai/simulated-gpu-node-pool)이 붙은 노드마다 GPU 목록(토폴로지 ConfigMap)을 만들고
nvidia.com/gpu.product·count·memory 같은 라벨을 붙인다
device-plugin 그 목록을 kubelet 에 광고하고, Allocate 때 MOCK_NVIDIA_VISIBLE_DEVICES 와 가짜 nvidia-smi 를 넣는다
kwok-gpu-device-plugin kubelet 이 없는 KWOK 노드의 status 에 직접 GPU 수를 적는다
status-exporter DCGM 과 같은 꼴의 지표를 낸다(사용률은 파드 주석으로 흉내)
진짜와 다른 점도 분명히 적어 둡니다. 진짜 장치 플러그인은 NVIDIA_VISIBLE_DEVICES 와 컨테이너 런타임 훅으로 장치 파일을 넣지만, 가짜는 이름에 MOCK_ 이 붙은 변수만 줍니다. 컨테이너 안에서 CUDA 를 부르면 아무 일도 일어나지 않고, 메모리 격리나 성능은 흉내조차 내지 않습니다. 이 코스가 가짜로 배우는 것은 「배치」뿐입니다.
현장에서 만나는 모습
2026-10-27 실험 VM(4 vCPU·4GiB, k3s v1.36.5)에서 직접 겪은 것 셋입니다.
- 「k3s 에서는 설치부터 실패합니다.」 k3s 는 RuntimeClass
nvidia를 미리 만들어 둡니다. 차트가 같은 이름을 만들려다invalid ownership metadata로 멈춥니다.runtimeClass.enabled=false로 피합니다. 진짜 GPU Operator 를 한 번이라도 깔았던 클러스터도 같은 일을 겪습니다. - 「값을 바꿔도 기존 노드는 그대로입니다.」 풀의
gpuCount를 2에서 4로 올리고 helm upgrade 를 했는데 3분이 지나도 노드는 2장이었습니다. 노드별 토폴로지 ConfigMap 이 한 번 만들어지면 status-updater 가 다시 계산하지 않기 때문입니다. 그 ConfigMap 을 지우고 status-updater 와 device-plugin 을 차례로 다시 띄워야 4장이 됩니다. 실습 6단계가 이 절차입니다. - 「스케줄러의 사유는 정확합니다.」 GPU 2장짜리 노드에 1장씩 세 파드를 넣으면 세 번째는
Insufficient nvidia.com/gpu로 Pending 입니다. 진짜 클러스터에서 보는 문장과 같습니다.
KWOK 노드는 kubelet 없이 노드 오브젝트만 있는 가짜 노드입니다. 파드는 Running 이 되지만 컨테이너는 돌지 않습니다. 그래서 「노드 수십 대에 GPU 수백 장」 같은 규모의 배치 실험을 VM 한 대에서 할 수 있습니다.
English corner
A device plugin advertises an extended resource; the scheduler only counts it.
advertise(광고하다)와 allocatable(내줄 수 있는 양)은 이 분야 문서에서 매일 보는 단어입니다.
다음 실습에서 할 것
노드가 광고한 GPU 를 읽고, 파드 하나에 한 장을 주고, 컨테이너 안에서 무엇을 받았는지 봅니다. 확장 자원의 두 규칙을 일부러 어겨 거절을 받아 보고, 노드를 꽉 채워 Pending 을 만든 뒤 광고 수를 늘려 풀어 줍니다. 마지막에는 KWOK 노드를 더해 GPU 장부를 직접 계산합니다.