GPU 노드를 쿠버네티스에 붙여본 사람이면 nvidia.com/gpu: 1 한 줄로 파드에 GPU를 꽂던 디바이스 플러그인 방식에 익숙할 것이다. 몇 년을 그렇게 썼는데, 1.34에서 DRA가 정식 기능이 되면서 할당 모델 자체가 바뀌었다. MIG 조각을 나눠 쓰거나 GPU 한 장을 여러 컨테이너가 공유하려고 꼼수를 부려본 적이 있다면, 이쪽이 왜 나왔는지 금방 감이 온다.
결론부터 말하자면, 쿠버네티스 1.34부터 Dynamic Resource Allocation(DRA)이 GA가 됐고 resource.k8s.io/v1 API가 기본으로 켜진다. NVIDIA DRA 드라이버를 Helm으로 설치하면 gpu.nvidia.com, mig.nvidia.com 같은 DeviceClass가 자동 등록되고, 파드는 ResourceClaimTemplate을 참조해 GPU를 받는다. 다만 MIG 동적 분할과 기존 nvidia.com/gpu 공존은 1.34에서 아직 알파 기능 게이트라 따로 켜야 한다.
용어부터 정리한다. DRA는 GPU나 FPGA 같은 특수 장치를 스토리지 PVC와 비슷한 방식으로 요청하고 할당하는 쿠버네티스 기능이다. 드라이버가 노드의 장치 목록을 ResourceSlice로 공개하면, 사용자는 DeviceClass(장치 종류와 담당 드라이버를 정의)를 바탕으로 ResourceClaim(실제 할당 요청)을 만든다. 파드마다 claim을 새로 찍어내고 싶으면 ResourceClaimTemplate을 쓴다. 스케줄러가 claim의 조건에 맞는 장치를 ResourceSlice에서 골라 파드가 뜰 노드와 장치를 함께 결정한다.
1. 디바이스 플러그인과 DRA가 갈라지는 지점
디바이스 플러그인은 GPU를 nvidia.com/gpu라는 정수형 확장 리소스로 노출한다. 노드 단위로 "이 노드에 GPU 4장"이라고 세는 모델이라, 파드는 개수만 요청할 수 있다. MIG 프로필을 고르거나, 한 장을 둘이 나눠 쓰거나, "메모리 40GB 이상인 GPU"처럼 속성으로 거르는 일은 이 모델 안에서 깔끔하게 표현되지 않는다.
DRA는 장치를 속성을 가진 객체로 다룬다. 드라이버가 각 GPU의 모델명, 메모리, MIG 지원 여부를 ResourceSlice에 적어 올리고, claim은 CEL 표현식으로 "이런 속성의 장치를 달라"고 요청한다. 할당 단위가 노드가 아니라 장치라서, 파드 하나가 특정 조건의 GPU를 골라 받고 여러 컨테이너가 같은 claim을 공유할 수 있다.
한 가지 짚어둘 제약이 있다. DRA가 할당한 장치를 쓰는 저순위 파드를, 고순위 파드가 밀어내는 선점은 아직 지원되지 않는다. 장치가 빌 때까지 고순위 파드도 Pending으로 대기한다.
2. NVIDIA DRA 드라이버 설치
DRA 드라이버는 커널 드라이버나 컨테이너 툴킷을 깔아주지 않는다. 노드에는 NVIDIA 드라이버와 컨테이너 툴킷이 이미 있어야 하고, CDI가 켜져 있어야 한다. GPU Operator를 쓰고 있다면 그쪽 가이드를 따르는 편이 충돌이 없다.
클러스터가 1.34면 핵심 기능 게이트 DynamicResourceAllocation은 기본으로 켜져 있다. 뒤에서 다룰 MIG 동적 분할과 기존 방식 공존까지 쓰려면 알파 게이트를 apiserver, scheduler, kubelet에 추가로 켜야 한다.
# kube-apiserver, kube-scheduler, kubelet 공통 --feature-gates=DRAPartitionableDevices=true,DRAExtendedResource=true
드라이버는 Helm 차트로 설치한다. 차트는 OCI 레지스트리에 올라와 있다.
$ helm install dra-driver-nvidia-gpu \
oci://registry.k8s.io/dra-driver-nvidia/charts/dra-driver-nvidia-gpu \
--version 0.5.1 \
--create-namespace \
--namespace dra-driver-nvidia-gpu \
--set gpuResourcesEnabledOverride=true
NAME: dra-driver-nvidia-gpu
LAST DEPLOYED: Wed Oct 29 11:20:14 2026
NAMESPACE: dra-driver-nvidia-gpu
STATUS: deployed
REVISION: 1
차트 버전 0.5.1은 저장소 상태에 따라 달라질 수 있으니 릴리스 페이지로 최신을 확인한다. GKE처럼 드라이버 경로가 다른 환경에서는 --set nvidiaDriverRoot=/home/kubernetes/bin/nvidia를 붙인다.
설치가 끝나면 DeviceClass가 자동으로 등록된다. 여기서 한 번 걸리는 부분이 있는데, 파드가 아무리 떠도 DeviceClass 목록이 비어 있으면 노드에 GPU 드라이버나 CDI가 빠진 경우다.
$ kubectl get deviceclass NAME AGE compute-domain-daemon.nvidia.com 2m compute-domain-default-channel.nvidia.com 2m gpu.nvidia.com 2m mig.nvidia.com 2m vfio.gpu.nvidia.com 2m
드라이버가 노드의 GPU를 ResourceSlice로 올렸는지도 확인한다.
$ kubectl get resourceslices NAME NODE DRIVER POOL AGE gpu-node-1-gpu.nvidia.com-7f9c gpu-node-1 gpu.nvidia.com gpu-node-1 2m
3. DeviceClass와 ResourceClaimTemplate 작성
DeviceClass는 드라이버가 알아서 만들어주니 직접 건드릴 일은 많지 않다. 작성할 쪽은 ResourceClaimTemplate이다. GPU 한 장을 요청하는 가장 단순한 형태는 이렇게 생겼다.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
namespace: gpu-test
name: single-gpu
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
v1에서 바뀐 점 하나. 요청의 조건을 exactly 블록 아래에 둔다. 예전 베타 API에서 deviceClassName을 request 바로 밑에 적던 글을 봤다면, GA에서는 이 중첩이 필요하다는 걸 기억해두자.
파드는 이 템플릿을 참조해 claim을 자동으로 받는다. spec.resourceClaims에 템플릿 이름을 걸고, 컨테이너의 resources.claims에서 그 이름을 다시 가리킨다.
apiVersion: v1
kind: Pod
metadata:
namespace: gpu-test
name: cuda-check
spec:
restartPolicy: OnFailure
resourceClaims:
- name: gpu
resourceClaimTemplateName: single-gpu
containers:
- name: ctr
image: nvidia/cuda:12.4.1-base-ubuntu22.04
command: ["nvidia-smi", "-L"]
resources:
claims:
- name: gpu
적용하고 claim이 할당됐는지, 파드 안에서 GPU가 보이는지 확인한다.
$ kubectl apply -f pod.yaml $ kubectl get resourceclaims -n gpu-test NAME STATE AGE cuda-check-gpu-abcd allocated,reserved 8s $ kubectl logs -n gpu-test cuda-check GPU 0: NVIDIA A100-SXM4-40GB (UUID: GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
같은 GPU를 두 컨테이너가 나눠 쓰고 싶으면, 두 컨테이너의 resources.claims가 같은 claim 이름을 가리키게 하면 된다. 디바이스 플러그인에서는 안 되던 공유가 여기서는 선언만으로 끝난다.
4. MIG 파티션 요청
MIG는 A100, H100 같은 GPU 한 장을 하드웨어 수준에서 여러 인스턴스로 쪼개는 기능이다. DRA 드라이버는 MIG 장치를 mig.nvidia.com DeviceClass로 노출한다. 특정 프로필을 고르려면 claim에 CEL 셀렉터를 건다.
프로필 속성의 정확한 키와 값은 ResourceSlice에 적혀 있으니, 셀렉터를 쓰기 전에 실제 노출된 속성을 먼저 들여다본다.
$ kubectl get resourceslice -o yaml | grep -A3 attributes
거기서 확인한 프로필 속성으로 1g.10gb 같은 조각을 요청한다.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
namespace: gpu-test
name: mig-1g10gb
spec:
spec:
devices:
requests:
- name: mig
exactly:
deviceClassName: mig.nvidia.com
selectors:
- cel:
expression: 'device.attributes["gpu.nvidia.com"].profile == "1g.10gb"'
주의할 점이 있다. GPU 한 장을 여러 MIG 조각으로 동적으로 쪼개 겹쳐 올리는 동작은 DRAPartitionableDevices 기능 게이트에 의존한다. 이 게이트는 1.34에서 알파이고 기본이 꺼져 있다. 앞서 켜두지 않았다면, 1.34에서는 미리 고정 분할해둔 MIG 인스턴스를 요청하는 선에서 쓰고, 동적 분할이 필요하면 게이트를 켜거나 1.36 이상으로 올리는 선택지가 있다.
5. 기존 nvidia.com/gpu와 공존
클러스터 전체를 하루아침에 DRA로 갈아엎기는 어렵다. 이미 돌아가는 매니페스트 수십 개가 resources.limits에 nvidia.com/gpu: 1을 박아두고 있을 테니까. 이 간극을 메우는 게 확장 리소스 매핑, 기능 게이트 이름으로는 DRAExtendedResource다.
DeviceClass에 확장 리소스 이름을 달아두면, 수정하지 않은 기존 파드가 nvidia.com/gpu를 요청해도 스케줄러가 그 요청을 DRA 드라이버로 넘겨 처리한다. 매니페스트를 건드리지 않고도 할당 경로만 DRA로 옮기는 셈이다.
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: gpu.nvidia.com
spec:
extendedResourceName: nvidia.com/gpu
selectors:
- cel:
expression: device.driver == "gpu.nvidia.com"
그러면 아래처럼 예전 방식 그대로 쓴 파드도 DRA 드라이버가 받는다.
resources:
limits:
nvidia.com/gpu: 1
단, DRAExtendedResource는 1.34에서 알파이고 기본이 꺼져 있다. 2절에서 게이트를 켜두지 않았다면 이 매핑은 동작하지 않는다. 이 기능은 1.36에서 베타, 1.37에서 GA로 올라가므로, 프로덕션에서 공존을 본격적으로 쓸 계획이면 클러스터 버전을 함께 고려하는 편이 낫다.
정리하면, 1.34에서 디바이스 플러그인을 걷어내지 않고도 DRA 드라이버를 나란히 올려 새 워크로드부터 ResourceClaimTemplate으로 받기 시작하고, 기존 매니페스트는 확장 리소스 매핑으로 천천히 흡수하는 이전 경로가 열려 있다. 다음으로 확인할 것은 본인 클러스터의 1.34 기능 게이트 설정이다. apiserver 기동 플래그에서 DRAExtendedResource, DRAPartitionableDevices가 실제로 켜졌는지부터 확인하고 나머지를 진행한다.