GPU Operator 와 타임슬라이싱 · 컨테이너만 GPU 를 쓰는 게 아니다 · 실습
샌드박스 워크로드 — 라벨이 정하는 자원 이름과 갈라지는 재고
목표
노드 세 대를 각각 다른 GPU 워크로드 용도로 세우고, 용도마다 자원 이름이 달라진다는 것과 그 결과 재고가 갈라진다는 것을 실제 스케줄러로 확인한 뒤, 라벨과 광고가 맞는지 판정하는 점검기를 만듭니다.
왜 중요한가
GPU 를 쓰는 워크로드가 전부 컨테이너인 것은 아닙니다. 라이선스나 레거시 때문에 가상 머신 안에서 돌아야 하는 것이 있고, 그런 VM 에 카드를 넘기려면 노드에 깔리는 드라이버부터 달라야 합니다 — 컨테이너에는 데이터센터 드라이버, 패스스루에는 vfio-pci, vGPU 에는 vGPU Manager 입니다. GPU Operator 는 그 선택을 노드 라벨 한 줄로 받습니다. 그리고 그 선택의 결과가 노드가 광고하는 자원 이름까지 바꿉니다. 이름이 다르면 스케줄러에게는 서로 다른 자원이라 한쪽 대기열이 길어도 다른 쪽 자리는 비어 있는 채로 남습니다. 재고를 용도별로 나누는 일이 왜 되돌리기 어려운 결정인지가 여기서 나옵니다.
단계
1. /root/gpuwl/out /root/gpuwl/bin /root/gpuwl/k8s 를 만들고 네임스페이스 gpu-vm 을 만드세요. 노드 세 대에 라벨 nvidia.com/gpu.workload.config 를 붙이세요 — lab-node-0 은 container, lab-node-1 은 vm-passthrough, lab-node-2 는 vm-vgpu 입니다. 그리고 /root/gpuwl/out/01-nodes.txt 에 세 줄을 적으세요 — 한 줄에 <노드이름> <라벨값> 입니다(노드 이름 오름차순).
2. /root/gpuwl/out/operands.txt 에 다섯 줄을 적으세요. 앞 세 줄은 <라벨값>=<오퍼랜드 목록> 이고 목록은 쉼표로 잇습니다(순서는 상관없습니다) — container 는 datacenter-driver container-toolkit device-plugin dcgm-exporter, vm-passthrough 는 vfio-manager sandbox-device-plugin, vm-vgpu 는 vgpu-manager vgpu-device-manager sandbox-device-plugin 입니다. 네 번째 줄은 NO_LABEL= 에 라벨이 없을 때 오퍼레이터가 가정하는 값을, 다섯 번째 줄은 ENABLE_FLAG= 에 이 라벨을 쓰이게 만드는 ClusterPolicy 플래그 이름을 적습니다.
3. 세 노드의 status.capacity 와 status.allocatable 양쪽에 각자의 자원을 넣으세요 — lab-node-0 은 nvidia.com/gpu 를 "4", lab-node-1 은 nvidia.com/GA102GL_A10 를 "2", lab-node-2 는 nvidia.com/NVIDIA_A10-12Q 를 "4" 입니다. 그리고 /root/gpuwl/out/03-resources.txt 에 세 줄을 적으세요 — 한 줄에 <노드이름> <자원이름> <광고량> 입니다(노드 이름 오름차순).
4. /root/gpuwl/k8s/pod-container.yaml 에 파드 job-container 를 쓰세요 — 네임스페이스 gpu-vm, 컨테이너 이름 cuda, 이미지 nvcr.io/nvidia/cuda:12.4.1-base-ubuntu22.04, limits 에 nvidia.com/gpu: 1. nodeSelector 는 쓰지 마세요. 적용한 뒤 어느 노드에 떴는지 /root/gpuwl/out/04-container.txt 에 한 줄로 적으세요 — NODE=<노드이름>.
5. /root/gpuwl/k8s/pod-passthrough.yaml 에 파드 vmi-passthrough 를 쓰세요 — 네임스페이스 gpu-vm, limits 에 nvidia.com/GA102GL_A10: 1, nodeSelector 없음. 나머지는 4단계와 같습니다. 적용한 뒤 /root/gpuwl/out/05-passthrough.txt 에 두 줄을 적으세요 — NODE= 와 RESOURCE= (요청한 자원 이름 그대로).
6. /root/gpuwl/k8s/pod-vgpu.yaml 에 파드 vmi-vgpu 를 쓰세요 — 네임스페이스 gpu-vm, limits 에 nvidia.com/NVIDIA_A10-12Q: 1, nodeSelector 없음. 적용한 뒤 /root/gpuwl/out/06-placement.txt 에 세 줄을 적으세요 — 지금까지 만든 파드 셋이 각각 어디에 떴는지 <파드이름> <노드이름> 으로, 파드 이름 오름차순입니다.
7. /root/gpuwl/k8s/pod-cross.yaml 에 파드 job-cross 를 쓰세요 — 네임스페이스 gpu-vm, nodeSelector 로 nvidia.com/gpu.workload.config: vm-passthrough 를 걸고 limits 에는 nvidia.com/gpu: 1 을 요구합니다. 패스스루 노드에 컨테이너용 자원을 달라고 하는 셈입니다. 적용한 뒤 /root/gpuwl/out/07-cross.txt 에 세 줄을 적으세요 — PHASE=, NODE=(비어 있으면 none), MESSAGE=(PodScheduled 조건의 메시지).
8. /root/gpuwl/out/nodes.json 에 kubectl get nodes -o json 결과를 저장하고, /root/gpuwl/bin/check-config.sh <노드JSON파일> 을 만드세요. 파일 안의 노드 가운데 nvidia.com/gpu.workload.config 라벨이 있는 것만 보고, 그 값과 광고한 nvidia.com/ 자원 이름이 맞는지 판정합니다 — container 는 nvidia.com/gpu 여야 하고, vm-vgpu 는 vGPU 프로파일 이름(번호와 대문자로 끝나는 것)이어야 하며, vm-passthrough 는 그 둘 중 어느 쪽도 아닌 장치 모델 이름이어야 합니다. 어긋난 노드마다 MISMATCH=<노드이름> <이유> 를 한 줄씩 내고 1 로 끝내고, 전부 맞으면 OK=<검사한 노드 수> 를 내고 0 으로 끝냅니다. 만든 뒤 저장한 덤프에 물려 출력을 /root/gpuwl/out/consistency.txt 에 저장하세요.
참고
- 이 클러스터에는 KubeVirt 도 vfio-pci 도 없어 VM 을 띄우지 못합니다. VM 요청은 같은 자원 이름을 요구하는 파드로 대신 세웁니다 — 스케줄링 단계에서 벌어지는 일이 실제로 같기 때문입니다.
- 진짜 클러스터에서 VM 은
spec.domain.devices.gpus[].deviceName에 자원 이름을 적고, 그 전에 KubeVirt 커스텀 리소스의 permittedHostDevices 에 그 자원이 올라가 있어야 합니다. - 노드 status 는
kubectl patch node <이름> --subresource=status --type=merge로 고칩니다. - jsonpath 로 자원을 읽을 때는 이름의 점을 이스케이프합니다:
{.status.allocatable.nvidia\.com/gpu}. - 흔한 실수: 자원 이름을 한 글자만 틀려도 그 노드는 후보에서 조용히 사라집니다. 오류도 경고도 없습니다.
- 흔한 실수: 광고량이 "0" 인 자원을 '있다' 고 세면 마지막 점검기가 엉뚱한 판정을 합니다.
단계 8개
- 노드 세 대에 용도를 적는다
- 라벨 값마다 무엇이 올라오는지 표로 세운다
- 용도마다 다른 자원 이름을 광고한다
- 컨테이너 워크로드는 컨테이너 노드로 간다
- 패스스루 요청은 장치 모델 이름을 부른다
- vGPU 요청은 프로파일 이름을 부른다
- 남의 자원을 부르면 어디에도 못 간다
- 라벨과 자원 광고가 서로 맞는지 판정하는 점검기