GPU Operator 와 타임슬라이싱 · 컨테이너만 GPU 를 쓰는 게 아니다 · 이론
workload.config — 라벨 하나가 노드의 정체를 정한다
한 줄 요약
GPU Operator 는 nvidia.com/gpu.workload.config 라벨의 값(container·vm-passthrough·vm-vgpu)을 보고 그 노드에 어떤 오퍼랜드를 올릴지 통째로 갈아 끼우고, 그 결과 노드가 광고하는 자원 이름까지 달라진다. 컨테이너 노드는 nvidia.com/gpu 를 광고하고, 패스스루 노드는 nvidia.com/GA102GL_A10 같은 장치 모델 이름을, vGPU 노드는 nvidia.com/NVIDIA_A10-12Q 같은 프로파일 이름을 광고한다.
왜 이게 필요했나
GPU 를 쓰는 워크로드가 전부 컨테이너인 것은 아니다. 라이선스가 특정 OS 를 요구하거나, 윈도우 드라이버가 필요하거나, 레거시 시뮬레이션이 통째로 가상 머신 안에 있는 경우가 실제로 많다. 그런 조직은 오랫동안 클러스터를 둘로 나눠 운영했다 — 컨테이너용 쿠버네티스 하나, 가상 머신용 하이퍼바이저 하나. 같은 GPU 재고를 두 곳에 나눠 두면 한쪽이 놀아도 다른 쪽에 못 빌려준다.
KubeVirt 는 가상 머신을 쿠버네티스 오브젝트로 만들어 그 벽을 없앴고, GPU Operator 는 그 위에서 워커 노드를 두 종류의 용도로 각각 준비할 수 있게 됐다. 공식 문서가 그 차이를 한 줄로 요약한다 — 컨테이너에는 데이터센터 드라이버가, GPU 패스스루에는 vfio-pci 드라이버가, vGPU 에는 NVIDIA vGPU Manager 가 필요하다. 필요한 드라이버가 다르다는 것이 모든 차이의 뿌리다.
라벨 하나가 노드의 소프트웨어를 통째로 바꾼다
노드에 라벨을 붙이는 것이 전부다.
kubectl label node <노드이름> --overwrite nvidia.com/gpu.workload.config=vm-vgpu그러면 오퍼레이터가 그 노드에 올리는 오퍼랜드가 달라진다.
| 라벨 값 | 그 노드에 올라가는 것 | 하는 일 |
| --- | --- | --- |
| container | 데이터센터 드라이버 · 컨테이너 툴킷 · 쿠버네티스 장치 플러그인 · DCGM 과 DCGM Exporter | 컨테이너가 GPU 를 쓰게 만들고 지표를 낸다 |
| vm-passthrough | VFIO Manager · 샌드박스 장치 플러그인 | vfio-pci 를 적재해 그 노드의 모든 GPU 에 바인드하고, 패스스루 GPU 를 kubelet 에 광고한다 |
| vm-vgpu | vGPU Manager · vGPU Device Manager · 샌드박스 장치 플러그인 | vGPU 드라이버를 깔고 vGPU 장치를 만든 뒤 kubelet 에 광고한다 |
여기서 반드시 붙잡을 문장이 둘이다.
첫째, 라벨이 없으면 기본값은 container 다. 오퍼레이터는 라벨 없는 노드를 컨테이너용으로 준비한다. 기본을 바꾸려면 ClusterPolicy 의 sandboxWorkloads.defaultWorkload 를 고친다.
둘째, 이 라벨은 sandboxWorkloads.enabled 가 켜져 있을 때만 쓰인다. 그 플래그는 기본이 꺼짐이고, 꺼져 있으면 모든 노드가 컨테이너용으로 준비되며 라벨은 아예 읽히지 않는다. 라벨만 붙여 놓고 왜 안 바뀌냐고 몇 시간 보는 자리가 여기다. 라벨은 문법 오류도 아니고 경고도 내지 않는다.
그리고 공식 문서가 못 박는 제약이 하나 더 있다. 한 워커 노드는 한 종류의 GPU 워크로드만 돌린다. 컨테이너와 패스스루 VM 을 한 노드에서 섞을 수 없다. vfio-pci 가 그 노드의 GPU 를 전부 가져가므로 호스트의 데이터센터 드라이버가 그 카드를 놓아야 하기 때문이다. 재고 계획이 노드 단위로 굳는다는 뜻이고, 이것이 실무에서 가장 크게 체감되는 제약이다.
자원 이름이 달라진다
컨테이너 노드가 광고하는 것은 우리가 아는 nvidia.com/gpu 다. 샌드박스 노드는 다르다. 공식 문서의 출력 예시가 그대로 말해 준다.
# 패스스루 노드$ kubectl get node <노드> -o json | jq '.status.allocatable | with_entries(select(.key | startswith("nvidia.com/")))'{ "nvidia.com/GA102GL_A10": "1" }# vGPU 노드 (기본 설정: 카드마다 절반 크기 Q 프로파일 두 개){ "nvidia.com/NVIDIA_A10-12Q": "4" }앞의 것은 PCI 장치 모델 이름에서, 뒤의 것은 vGPU 프로파일 이름에서 온다. A10 카드 두 장에 기본 설정을 적용하면 카드마다 12Q 두 개가 생겨 합계 4개가 된다. 여기에 노드 라벨 nvidia.com/vgpu.config 로 프로파일을 바꿀 수 있다 — A10-4Q 를 고르면 카드마다 여섯 개씩, 두 장이면 12개가 광고된다. 같은 하드웨어인데 라벨 하나로 광고량과 자원 이름이 함께 바뀐다.
이름이 다르다는 사실의 결과는 단순하고 가혹하다. nvidia.com/gpu 를 요구하는 파드는 패스스루 노드에 절대 못 간다. 자리가 남아 있어도 스케줄러에게 그 노드는 그 자원을 갖지 않은 노드다. 반대도 마찬가지다. 클러스터가 카드 재고를 나눠 두면 한쪽에서 대기열이 길어도 다른 쪽 자리는 비어 있는 채로 남는다.
VM 에 카드를 꽂는 모양
장치가 광고됐다고 VM 이 바로 쓸 수 있는 것은 아니다. KubeVirt 쪽에 허용 목록을 따로 적어야 한다 — 이것이 사람들이 가장 많이 빠뜨리는 단계다. KubeVirt 커스텀 리소스의 permittedHostDevices 아래에 패스스루면 pciHostDevices, vGPU 면 mediatedDevices 로 적고, 둘 다 externalResourceProvider: true 를 붙인다. 그 플래그가 "이 자원은 우리가 만든 게 아니라 외부 장치 플러그인(여기서는 샌드박스 장치 플러그인)이 광고한다" 는 뜻이다.
spec: configuration: developerConfiguration: featureGates: - GPU - DisableMDEVConfiguration permittedHostDevices: pciHostDevices: - externalResourceProvider: true pciVendorSelector: 10DE:2236 resourceName: nvidia.com/GA102GL_A10그 뒤에야 VM 쪽에서 카드를 요청할 수 있다. 파드의 resources.limits 와 자리가 다르다 — VirtualMachineInstance 는 spec.domain.devices.gpus 에 적는다.
spec: domain: devices: gpus: - deviceName: nvidia.com/GA102GL_A10 name: gpu1deviceName 이 자원 이름이고 name 은 VM 안에서 그 장치를 부르는 별명이다. 겉모양은 다르지만 스케줄링 단계에서 벌어지는 일은 파드와 같다 — VM 을 대신 실행하는 파드가 그 자원을 요구하고, 그 자원을 광고하는 노드로 간다.
마지막으로 공식 문서가 분명히 적어 둔 사실 하나. GPU Operator 는 VM 안의 NVIDIA 드라이버 설치는 자동화하지 않는다. 카드를 VM 에 꽂아 주는 데까지가 그 일이고, 게스트 OS 안의 드라이버는 VM 이미지를 만드는 쪽의 몫이다.
현장에서 만나는 모습
첫째, BIOS 와 커널 파라미터가 선행 조건이다. 가상화 확장과 IOMMU 가 BIOS 에서 켜져 있어야 하고 커널 명령줄에 intel_iommu=on 또는 amd_iommu=on 이 있어야 한다. Ampere 이후 카드로 vGPU 를 쓰려면 BIOS 에서 SR-IOV 도 켜야 한다. 이것들은 재부팅이 필요한 변경이라, 노드를 클러스터에 넣기 전에 이미지 단계에서 해 두지 않으면 나중에 노드를 한 대씩 비워 가며 고쳐야 한다.
둘째, 노드를 용도 사이에서 옮기는 일은 무중단이 아니다. 라벨을 바꾸면 오퍼랜드가 갈리고 드라이버가 바뀐다. 공식 문서는 vGPU 설정을 바꿀 때 그 노드에서 돌던 VM 을 먼저 끄거나 옮기라고 적어 둔다. 재고를 유연하게 굴리려는 계획은 이 지점에서 현실과 만난다 — 하루 단위로 옮기는 것은 되지만 분 단위로는 안 된다.
셋째, 자원 이름이 카드 모델에 매여 있어 매니페스트가 하드웨어에 묶인다. nvidia.com/GA102GL_A10 을 요구하는 VM 매니페스트는 A10 이 없는 클러스터에서 영영 Pending 이다. 컨테이너 쪽의 nvidia.com/gpu 가 모델과 무관했던 것과 대비된다. 그래서 샌드박스 워크로드를 쓰는 조직은 모델 이름을 매니페스트에 직접 쓰지 않고 헬름 값이나 kustomize 패치로 한 곳에 모아 둔다.
넷째, 이 환경의 정직한 한계. 실습 클러스터에는 KubeVirt 도 vfio-pci 도 없고 VM 은 띄우지 못한다. 그래서 다음 실습에서는 VM 요청을 같은 자원 이름을 요구하는 파드로 세운다. 스케줄링 단계에서 벌어지는 일이 실제로 같기 때문에(둘 다 확장 자원을 요구하는 파드다) 어디로 가고 어디서 막히는지는 실물로 배운다. 다만 VM 이 실제로 부팅해 카드를 쥐는 뒷부분은 확인할 수 없고, 그 부분은 위의 VMI 매니페스트 모양으로 대신한다.
참고 문서
- GPU Operator 와 KubeVirt(워크로드 라벨·오퍼랜드·자원 이름): https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-kubevirt.html
- GPU Operator 개요: https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html
- KubeVirt 호스트 장치 할당: https://kubevirt.io/user-guide/compute/host-devices/
- 노드에 확장 자원 광고하기: https://kubernetes.io/docs/tasks/administer-cluster/extended-resource-node/
- 파드를 노드에 배정하기: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
다음 실습에서 할 것
노드 세 대를 각각 container·vm-passthrough·vm-vgpu 로 라벨링하고, 각 라벨에 맞는 자원 이름을 노드 status 에 세운다. 라벨 값마다 어떤 오퍼랜드가 떠야 하는지를 표 파일로 정리하고, 세 가지 워크로드(컨테이너 파드 · 패스스루 요청 · vGPU 요청)를 올려 각자 제 노드로 가는 것과 남의 자원을 요구하면 어디에서도 못 뜨는 것을 실제 스케줄러로 확인한다. 마지막으로 노드 덤프를 받아 "이 라벨과 이 자원 광고가 서로 맞는가" 를 판정하는 점검기를 만들고, 일부러 어긋나게 만든 노드를 그 점검기가 잡아내는지 본다.