LabHub
学习 学习路径 课程

GPU Operator 与时间片

沙箱工作负载 — 标签决定资源名,库存随之被切开

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

노드 세 대를 각각 다른 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-0container, lab-node-1vm-passthrough, lab-node-2vm-vgpu 입니다. 그리고 /root/gpuwl/out/01-nodes.txt 에 세 줄을 적으세요 — 한 줄에 <노드이름> <라벨값> 입니다(노드 이름 오름차순).
  2. /root/gpuwl/out/operands.txt 에 다섯 줄을 적으세요. 앞 세 줄은 <라벨값>=<오퍼랜드 목록> 이고 목록은 쉼표로 잇습니다(순서는 상관없습니다) — containerdatacenter-driver container-toolkit device-plugin dcgm-exporter, vm-passthroughvfio-manager sandbox-device-plugin, vm-vgpuvgpu-manager vgpu-device-manager sandbox-device-plugin 입니다. 네 번째 줄은 NO_LABEL= 에 라벨이 없을 때 오퍼레이터가 가정하는 값을, 다섯 번째 줄은 ENABLE_FLAG= 에 이 라벨을 쓰이게 만드는 ClusterPolicy 플래그 이름을 적습니다.
  3. 세 노드의 status.capacitystatus.allocatable 양쪽에 각자의 자원을 넣으세요 — lab-node-0nvidia.com/gpu"4", lab-node-1nvidia.com/GA102GL_A10"2", lab-node-2nvidia.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, limitsnvidia.com/gpu: 1. nodeSelector 는 쓰지 마세요. 적용한 뒤 어느 노드에 떴는지 /root/gpuwl/out/04-container.txt 에 한 줄로 적으세요 — NODE=<노드이름>.
  5. /root/gpuwl/k8s/pod-passthrough.yaml 에 파드 vmi-passthrough 를 쓰세요 — 네임스페이스 gpu-vm, limitsnvidia.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, limitsnvidia.com/NVIDIA_A10-12Q: 1, nodeSelector 없음. 적용한 뒤 /root/gpuwl/out/06-placement.txt 에 세 줄을 적으세요 — 지금까지 만든 파드 셋이 각각 어디에 떴는지 <파드이름> <노드이름> 으로, 파드 이름 오름차순입니다.
  7. /root/gpuwl/k8s/pod-cross.yaml 에 파드 job-cross 를 쓰세요 — 네임스페이스 gpu-vm, nodeSelectornvidia.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.jsonkubectl get nodes -o json 결과를 저장하고, /root/gpuwl/bin/check-config.sh <노드JSON파일> 을 만드세요. 파일 안의 노드 가운데 nvidia.com/gpu.workload.config 라벨이 있는 것만 보고, 그 값과 광고한 nvidia.com/ 자원 이름이 맞는지 판정합니다 — containernvidia.com/gpu 여야 하고, vm-vgpu 는 vGPU 프로파일 이름(번호와 대문자로 끝나는 것)이어야 하며, vm-passthrough 는 그 둘 중 어느 쪽도 아닌 장치 모델 이름이어야 합니다. 어긋난 노드마다 MISMATCH=<노드이름> <이유> 를 한 줄씩 내고 1 로 끝내고, 전부 맞으면 OK=<검사한 노드 수> 를 내고 0 으로 끝냅니다. 만든 뒤 저장한 덤프에 물려 출력을 /root/gpuwl/out/consistency.txt 에 저장하세요.

참고

노드 세 대에 용도를 적는다

/root/gpuwl/out /root/gpuwl/bin /root/gpuwl/k8s 를 만들고 네임스페이스 gpu-vm 을 만드세요. 노드 세 대에 라벨 nvidia.com/gpu.workload.config 를 붙이세요 — lab-node-0container, lab-node-1vm-passthrough, lab-node-2vm-vgpu 입니다. 그리고 /root/gpuwl/out/01-nodes.txt 에 세 줄을 적으세요 — 한 줄에 <노드이름> <라벨값> 입니다(노드 이름 오름차순).

이 라벨 하나가 그 노드에 올라갈 오퍼레이터의 소프트웨어를 통째로 바꿉니다. 라벨은 kubectl label node <이름> --overwrite <키>=<값> 으로 붙입니다. 값은 셋뿐이고 오타를 내도 아무 경고가 없습니다 — 그래서 붙인 뒤 확인하는 습관이 필요합니다. kubectl get nodes -L <키> 로 한 화면에 볼 수 있습니다.

라벨 값마다 무엇이 올라오는지 표로 세운다

/root/gpuwl/out/operands.txt 에 다섯 줄을 적으세요. 앞 세 줄은 <라벨값>=<오퍼랜드 목록> 이고 목록은 쉼표로 잇습니다(순서는 상관없습니다) — containerdatacenter-driver container-toolkit device-plugin dcgm-exporter, vm-passthroughvfio-manager sandbox-device-plugin, vm-vgpuvgpu-manager vgpu-device-manager sandbox-device-plugin 입니다. 네 번째 줄은 NO_LABEL= 에 라벨이 없을 때 오퍼레이터가 가정하는 값을, 다섯 번째 줄은 ENABLE_FLAG= 에 이 라벨을 쓰이게 만드는 ClusterPolicy 플래그 이름을 적습니다.

세 줄을 나란히 놓고 보면 공통분모가 sandbox-device-plugin 하나뿐이라는 것이 보입니다 — VM 쪽 두 용도는 장치를 광고하는 방법은 같고 드라이버 준비 방법이 다릅니다. 다섯 번째 줄이 가장 중요합니다. 그 플래그가 꺼져 있으면(기본값이 꺼짐입니다) 라벨을 아무리 정확히 붙여도 읽히지 않고, 모든 노드가 컨테이너용으로 준비됩니다. 플래그 이름은 점으로 이어진 두 낱말입니다.

용도마다 다른 자원 이름을 광고한다

세 노드의 status.capacitystatus.allocatable 양쪽에 각자의 자원을 넣으세요 — lab-node-0nvidia.com/gpu"4", lab-node-1nvidia.com/GA102GL_A10"2", lab-node-2nvidia.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, limitsnvidia.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, limitsnvidia.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, limitsnvidia.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, nodeSelectornvidia.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.jsonkubectl get nodes -o json 결과를 저장하고, /root/gpuwl/bin/check-config.sh <노드JSON파일> 을 만드세요. 파일 안의 노드 가운데 nvidia.com/gpu.workload.config 라벨이 있는 것만 보고, 그 값과 광고한 nvidia.com/ 자원 이름이 맞는지 판정합니다 — containernvidia.com/gpu 여야 하고, vm-vgpu 는 vGPU 프로파일 이름(번호와 대문자로 끝나는 것)이어야 하며, vm-passthrough 는 그 둘 중 어느 쪽도 아닌 장치 모델 이름이어야 합니다. 어긋난 노드마다 MISMATCH=<노드이름> <이유> 를 한 줄씩 내고 1 로 끝내고, 전부 맞으면 OK=<검사한 노드 수> 를 내고 0 으로 끝냅니다. 만든 뒤 저장한 덤프에 물려 출력을 /root/gpuwl/out/consistency.txt 에 저장하세요.

이 점검기는 클러스터에 붙지 않고 파일 하나만 읽습니다. 그래야 남이 보내 준 덤프에도, 배포 전 심사에도 쓸 수 있습니다. 판정의 핵심은 이름의 모양입니다 — vGPU 프로파일은 -12Q -4Q 처럼 붙임표 뒤에 숫자와 대문자 한 글자로 끝납니다. 한 노드가 여러 종류의 nvidia 자원을 광고하는 것도 어긋남입니다. 한 워커 노드는 한 종류의 GPU 워크로드만 돌립니다. 광고량이 "0" 인 자원은 없는 것으로 봐야 합니다. 채점기는 일부러 어긋나게 만든 덤프도 물려 봅니다.