DRA(Dynamic Resource Allocation) 가 1.34에서 드디어 GA로 올라갔다. 그 사이에 1.35, 1.36까지 나오면서 드라이버 생태계도 꽤 안정화됐고, 최근 KubeCon에서도 "이제는 device plugin 대신 DRA를 쓸 시기"라는 얘기가 자주 나온다. 우리 팀도 GPU 워크로드가 늘면서 기존 nvidia.com/gpu 정수 카운트 방식으로는 감당이 안 되는 케이스가 생겨서, 최근 두 주 동안 DRA로 옮겨봤다. 이 글은 그 이관 과정을 정리한 가이드다.
왜 device plugin으로는 부족했나
기존 device plugin 방식은 GPU를 정수 개수로만 노출한다. 그래서 "메모리 20GB 이상인 GPU 아무거나", "MIG로 잘라진 slice", "같은 노드 안에서 NVLink 로 묶인 두 장" 같은 요구를 표현할 방법이 없다. 워크로드마다 노드 라벨/테인트로 우회하다 보면 스케줄러가 처리해야 할 조합이 폭발한다.
DRA는 이걸 스케줄러 쪽으로 밀어 넣는다. 워크로드는 "이런 특성을 가진 device가 필요하다"고 선언만 하고, 실제 매칭과 할당은 스케줄러가 드라이버와 협력해서 처리한다. Extended resource 매핑도 1.34에서 들어와서, 기존 resources.requests 문법을 유지하면서 뒤에서 DRA로 받을 수도 있다. 이관 부담이 훨씬 줄었다.
사전 준비: 클러스터와 드라이버
우선 클러스터 버전이 1.34 이상이어야 한다. 1.33에서는 아직 beta라서 feature gate를 켜야 하고, 프로덕션에서 굳이 그 리스크를 질 필요는 없다. 우리 팀은 EKS 1.34가 나오길 기다리다가 자체 klaster 하나만 먼저 올렸다.
드라이버는 NVIDIA GPU 기준으로 nvidia-dra-driver-gpu 를 쓴다. Helm으로 올리면 되는데, RBAC 이 좀 많이 필요해서 values를 미리 확인해두는 게 좋다.
helm upgrade --install nvidia-dra-driver-gpu \
oci://nvcr.io/nvidia/k8s/nvidia-dra-driver-gpu \
--namespace nvidia-dra-driver-gpu \
--create-namespace \
--set nvidiaDriverRoot=/run/nvidia/driver \
--set resources.gpus.enabled=true \
--set resources.computeDomains.enabled=true
드라이버가 올라오면 각 노드에서 ResourceSlice 리소스를 만들어낸다. 이게 DRA의 핵심이다. 노드가 어떤 device를 가지고 있는지, 어떤 속성(메모리, driver 버전, MIG 여부 등)을 갖는지를 클러스터에 광고한다.
kubectl get resourceslices
kubectl get resourceslice <name> -o yaml | grep -A5 attributes
여기서 attributes가 눈에 안 들어오면 DRA 매칭이 뜻대로 안 된다. 시간을 좀 들여서 훑어보자.
실제 워크로드 정의하기
DRA에서 워크로드가 GPU를 요청하는 방법은 ResourceClaim 또는 ResourceClaimTemplate 을 만들고 Pod에서 참조하는 형태다. 여러 Pod가 같은 device를 공유하려면 ResourceClaim, Pod 별로 하나씩 잡히길 원하면 ResourceClaimTemplate 을 쓴다. 우리는 대부분 후자다.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-20gb-any
spec:
spec:
devices:
requests:
- name: gpu
deviceClassName: gpu.nvidia.com
selectors:
- cel:
expression: |
device.attributes["gpu.nvidia.com"].memory.quantity >= quantity("20Gi") &&
device.attributes["gpu.nvidia.com"].productName.matches("A100|H100|H200")
selectors[].cel.expression 이 하이라이트다. CEL로 device attribute를 직접 필터링할 수 있어서, 기존에 노드 라벨로 처리하던 조건 대부분이 이쪽으로 옮겨진다. "메모리 20GB 이상이면서 A100/H100/H200 중 하나" 같은 표현이 한 줄에 담긴다.
Pod에서는 이렇게 참조한다.
apiVersion: v1
kind: Pod
metadata:
name: llm-inference
spec:
resourceClaims:
- name: my-gpu
resourceClaimTemplateName: gpu-20gb-any
containers:
- name: server
image: my-registry/llm:v1
resources:
claims:
- name: my-gpu
Pod가 뜨면 스케줄러가 selector 조건에 맞는 device를 찾아서 할당하고, ResourceClaim 이 실제로 생성된다. kubectl get resourceclaims 로 확인 가능하다.
실무에서 부딪히는 것들
1. 스케줄러 결정이 안 보인다
기존 device plugin은 실패하면 이벤트가 꽤 명확했다. DRA는 selector 조건 하나만 안 맞아도 스케줄이 안 잡히는데, 로그가 예상보다 조용하다. kube-scheduler 로그를 --v=4 이상으로 올리고 plugin=DynamicResources 관련 라인을 봐야 한다. 이걸 몰라서 처음에 한나절 낭비했다.
2. Extended resource 매핑을 언제 쓸까
레거시 워크로드는 매니페스트를 다 뜯어고칠 여유가 없다. 이때 관리자가 DeviceClass에 extendedResourceName 을 붙여두면 기존 nvidia.com/gpu: 1 요청이 그대로 뒤에서 DRA로 처리된다. 처음부터 완벽하게 다 옮기려 하지 말고, 신규 워크로드는 ResourceClaim으로 새로 짜고 레거시는 이 매핑으로 뒤에 붙이는 게 현실적이었다.
3. Consumable capacity
1.35에서 들어온 기능인데, 하나의 device 안에서 메모리를 잘게 나눠 여러 Pod가 공유하도록 하는 방식이다. 우리 팀은 inference 트래픽이 들쭉날쭉해서 이게 꽤 유용했다. 대신 드라이버가 이 기능을 지원해야 하고, MIG 를 이미 쓰고 있다면 어느 쪽으로 갈지 결정이 필요하다. 아직 우리도 검증 중이라 결론이 없다.
4. Multi-node claim
훈련 워크로드 중에는 여러 노드에 걸쳐 GPU를 잡아야 하는 케이스가 있다. 1.35에서 alpha로 들어온 multi-node claim이 이걸 해결한다. 다만 alpha라서 프로덕션에는 아직 안 넣었다.
이관 순서에 대한 우리 팀 결론
한 번에 다 옮기는 건 위험하다. 우리는 이렇게 나눴다.
첫째, 클러스터 하나를 1.34로 올리고 신규 GPU 워크로드만 DRA로 새로 배포한다. 기존 워크로드는 건드리지 않는다. 이 단계에서 드라이버 안정성과 로그/알람 정비를 마무리한다.
둘째, 자주 이슈가 생기던 워크로드부터 ResourceClaim으로 이관한다. 여기서 "메모리 조건이 안 맞아서 특정 노드로만 몰리던" 것 같은 문제가 자연스럽게 해소되는 경험을 하게 된다.
셋째, 나머지 레거시는 extended resource 매핑으로 뒤에서 흡수한다. 매니페스트는 그대로 두고 클러스터 관리 쪽에서만 바꾸면 되니 부담이 적다.
마무리
DRA는 처음 두세 번은 학습곡선이 있지만, 한 번 selector 문법에 익숙해지고 나면 이전으로 돌아가고 싶지 않다. 노드 라벨과 테인트로 GPU 스케줄을 억지로 맞추던 시절을 떠올리면 확실히 낫다. 우리 팀은 아직 consumable capacity와 multi-node claim은 안정 단계까지 조금 더 기다릴 생각이다. 혹시 DRA 이관을 이미 해보신 분이 계시면, 스케줄러 로그 필터링 어떻게 하시는지 댓글로 알려주시면 좋겠다.
'IT > Kubernets' 카테고리의 다른 글
| kube-proxy nftables 모드, 사실 내부적으로는 이렇게 돌아간다 (0) | 2026.08.28 |
|---|---|
| distroless 컨테이너, kubectl debug 없으면 진짜 답 없다 (0) | 2026.08.27 |
| HPA 진동 잡느라 이틀 태운 이야기 (0) | 2026.08.26 |
| kubectl events --for, 이거 모르는 분 꽤 많더라 (0) | 2026.08.23 |
| CronJob startingDeadlineSeconds 잘못 만졌다가 새벽 배치가 3일간 안 돌던 이야기 (0) | 2026.08.23 |