Sandbox workloads — the label that picks the resource name, and the inventory that splits
한국어 원문으로 표시합니다.
목표
노드 세 대를 각각 다른 GPU 워크로드 용도로 세우고, 용도마다 자원 이름이 달라진다는 것과 그 결과 재고가 갈라진다는 것을 실제 스케줄러로 확인한 뒤, 라벨과 광고가 맞는지 판정하는 점검기를 만듭니다.
왜 중요한가
GPU 를 쓰는 워크로드가 전부 컨테이너인 것은 아닙니다. 라이선스나 레거시 때문에 가상 머신 안에서 돌아야 하는 것이 있고, 그런 VM 에 카드를 넘기려면 노드에 깔리는 드라이버부터 달라야 합니다 — 컨테이너에는 데이터센터 드라이버, 패스스루에는 vfio-pci, vGPU 에는 vGPU Manager 입니다. GPU Operator 는 그 선택을 노드 라벨 한 줄로 받습니다. 그리고 그 선택의 결과가 노드가 광고하는 자원 이름까지 바꿉니다. 이름이 다르면 스케줄러에게는 서로 다른 자원이라 한쪽 대기열이 길어도 다른 쪽 자리는 비어 있는 채로 남습니다. 재고를 용도별로 나누는 일이 왜 되돌리기 어려운 결정인지가 여기서 나옵니다.
단계
/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에 세 줄을 적으세요 — 한 줄에<노드이름> <라벨값>입니다(노드 이름 오름차순)./root/gpuwl/out/operands.txt에 다섯 줄을 적으세요. 앞 세 줄은<라벨값>=<오퍼랜드 목록>이고 목록은 쉼표로 잇습니다(순서는 상관없습니다) —container는datacenter-drivercontainer-toolkitdevice-plugindcgm-exporter,vm-passthrough는vfio-managersandbox-device-plugin,vm-vgpu는vgpu-managervgpu-device-managersandbox-device-plugin입니다. 네 번째 줄은NO_LABEL=에 라벨이 없을 때 오퍼레이터가 가정하는 값을, 다섯 번째 줄은ENABLE_FLAG=에 이 라벨을 쓰이게 만드는 ClusterPolicy 플래그 이름을 적습니다.- 세 노드의
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에 세 줄을 적으세요 — 한 줄에<노드이름> <자원이름> <광고량>입니다(노드 이름 오름차순). /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=<노드이름>./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=(요청한 자원 이름 그대로)./root/gpuwl/k8s/pod-vgpu.yaml에 파드vmi-vgpu를 쓰세요 — 네임스페이스gpu-vm,limits에nvidia.com/NVIDIA_A10-12Q: 1, nodeSelector 없음. 적용한 뒤/root/gpuwl/out/06-placement.txt에 세 줄을 적으세요 — 지금까지 만든 파드 셋이 각각 어디에 떴는지<파드이름> <노드이름>으로, 파드 이름 오름차순입니다./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 조건의 메시지)./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" 인 자원을 '있다' 고 세면 마지막 점검기가 엉뚱한 판정을 합니다.
노드 세 대에 용도를 적는다
/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 에 세 줄을 적으세요 — 한 줄에 <노드이름> <라벨값> 입니다(노드 이름 오름차순).
이 라벨 하나가 그 노드에 올라갈 오퍼레이터의 소프트웨어를 통째로 바꿉니다. 라벨은 kubectl label node <이름> --overwrite <키>=<값> 으로 붙입니다. 값은 셋뿐이고 오타를 내도 아무 경고가 없습니다 — 그래서 붙인 뒤 확인하는 습관이 필요합니다. kubectl get nodes -L <키> 로 한 화면에 볼 수 있습니다.
라벨 값마다 무엇이 올라오는지 표로 세운다
/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 플래그 이름을 적습니다.
세 줄을 나란히 놓고 보면 공통분모가 sandbox-device-plugin 하나뿐이라는 것이 보입니다 — VM 쪽 두 용도는 장치를 광고하는 방법은 같고 드라이버 준비 방법이 다릅니다. 다섯 번째 줄이 가장 중요합니다. 그 플래그가 꺼져 있으면(기본값이 꺼짐입니다) 라벨을 아무리 정확히 붙여도 읽히지 않고, 모든 노드가 컨테이너용으로 준비됩니다. 플래그 이름은 점으로 이어진 두 낱말입니다.
용도마다 다른 자원 이름을 광고한다
세 노드의 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 에 세 줄을 적으세요 — 한 줄에 <노드이름> <자원이름> <광고량> 입니다(노드 이름 오름차순).
실제 클러스터에서는 노드마다 다른 장치 플러그인이 이 자리를 채웁니다 — 컨테이너 노드는 쿠버네티스 장치 플러그인이, VM 쪽 두 노드는 샌드박스 장치 플러그인이 채웁니다. 패스스루 자원 이름은 PCI 장치 모델에서 오고, vGPU 자원 이름은 프로파일에서 옵니다 — 뒤의 것만 번호와 대문자로 끝난다는 규칙이 마지막 단계의 판정 근거가 됩니다. JSON 패치에서 자원 이름의 점과 슬래시를 이스케이프할 필요는 없습니다. jsonpath 로 읽을 때만 점을 이스케이프합니다.
컨테이너 워크로드는 컨테이너 노드로 간다
/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=<노드이름>.
조건을 하나도 주지 않았는데 갈 수 있는 노드가 한 곳뿐입니다. 자원 이름 자체가 노드를 고른 것입니다 — 이 실습 전체의 요점이 이 한 줄에 들어 있습니다. 확장 자원은 limits 에만 적습니다. requests 와 limits 가 같아야 한다는 규칙 때문에 limits 만으로 충분합니다.
패스스루 요청은 장치 모델 이름을 부른다
/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= (요청한 자원 이름 그대로).
진짜 클러스터에서는 이 자리에 VirtualMachineInstance 가 오고, 자원은 spec.domain.devices.gpus[].deviceName 에 적습니다. 이 파드에는 KubeVirt 가 없어 VM 을 띄우지 못하지만, 스케줄링 단계에서 벌어지는 일은 같습니다 — VM 도 결국 그 자원을 요구하는 파드가 대신 실행되기 때문입니다. 자원 이름은 PCI 장치 모델에서 옵니다. 문자 하나만 틀려도 그 노드는 후보에서 사라집니다.
vGPU 요청은 프로파일 이름을 부른다
/root/gpuwl/k8s/pod-vgpu.yaml 에 파드 vmi-vgpu 를 쓰세요 — 네임스페이스 gpu-vm, limits 에 nvidia.com/NVIDIA_A10-12Q: 1, nodeSelector 없음. 적용한 뒤 /root/gpuwl/out/06-placement.txt 에 세 줄을 적으세요 — 지금까지 만든 파드 셋이 각각 어디에 떴는지 <파드이름> <노드이름> 으로, 파드 이름 오름차순입니다.
세 줄을 나란히 놓으면 자원 이름 하나가 배치를 통째로 정했다는 것이 한눈에 보입니다. 어느 파드에도 nodeSelector 를 주지 않았는데도 셋이 각자 다른 노드에 갔습니다. vGPU 프로파일 이름은 번호와 대문자로 끝납니다(예: 12Q). 그 형태가 패스스루 이름과 구분되는 지점이고, 8단계 점검기가 쓸 신호이기도 합니다. 배치는 kubectl get pods -n <ns> -o custom-columns= 로 한 번에 뽑을 수 있습니다.
남의 자원을 부르면 어디에도 못 간다
/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 조건의 메시지).
그 노드에는 카드가 두 장이나 광고돼 있고 자리도 비어 있습니다. 그런데도 못 갑니다 — 스케줄러는 자원 이름을 문자열로 대조할 뿐이기 때문입니다. 클러스터가 재고를 용도별로 나눠 두면 한쪽 대기열이 길어도 다른 쪽 자리는 비어 있는 채로 남습니다. 이것이 샌드박스 워크로드를 도입할 때 가장 먼저 계획해야 하는 제약입니다. 메시지가 무엇이라고 하는지 눈여겨보세요 — '자원이 모자라다' 고 합니다.
라벨과 자원 광고가 서로 맞는지 판정하는 점검기
/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 에 저장하세요.
이 점검기는 클러스터에 붙지 않고 파일 하나만 읽습니다. 그래야 남이 보내 준 덤프에도, 배포 전 심사에도 쓸 수 있습니다. 판정의 핵심은 이름의 모양입니다 — vGPU 프로파일은 -12Q -4Q 처럼 붙임표 뒤에 숫자와 대문자 한 글자로 끝납니다. 한 노드가 여러 종류의 nvidia 자원을 광고하는 것도 어긋남입니다. 한 워커 노드는 한 종류의 GPU 워크로드만 돌립니다. 광고량이 "0" 인 자원은 없는 것으로 봐야 합니다. 채점기는 일부러 어긋나게 만든 덤프도 물려 봅니다.