GPU 4장이 꽂힌 서버에서 2장은 호스트에서 직접 돌리는 학습 · 분석 프로세스에 남기고, 나머지 2장만 쿠버네티스가 스케줄링하게 만드는 구성이다. 노드를 통째로 쿠버네티스에 내주지 않고 기존 워크로드와 공존해야 할 때 쓴다.
핵심은 NVIDIA device plugin 이 등록하는 장치 목록을 제한하는 것이다. 파드마다 제한하는 것이 아니라 플러그인 데몬셋 수준에서 제한해야 쿠버네티스의 자원 회계가 맞는다.
nvidia-smi --query-gpu=index,uuid,name,memory.total --format=csv
nvidia-smi
index 는 드라이버 로드 순서나 PCI 재배치에 따라 바뀔 수 있다. 장기 운영에서는 UUID 로 고정하는 편이 안전하다.
데몬셋 컨테이너에 NVIDIA_VISIBLE_DEVICES 를 주면 플러그인은 그 목록만 보고, 그만큼만 nvidia.com/gpu 자원으로 등록한다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
template:
spec:
containers:
- name: nvidia-device-plugin-ctr
image: nvcr.io/nvidia/k8s-device-plugin:v0.17.0
env:
- name: NVIDIA_VISIBLE_DEVICES
value: "GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,GPU-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
- name: NVIDIA_DRIVER_CAPABILITIES
value: "compute,utility"
securityContext:
privileged: true
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
노드마다 남길 GPU 가 다르다면 데몬셋 하나로는 안 된다. 노드 라벨로 갈라 데몬셋을 여러 개 두거나, GPU Operator 를 쓰면서 노드별 ClusterPolicy 를 나눈다.
kubectl -n kube-system rollout restart ds/nvidia-device-plugin-daemonset
kubectl describe node <node> | grep -A3 'Capacity:\|Allocatable:'
nvidia.com/gpu 가 2로 잡히면 적용된 것이다.
여기서 자주 헷갈리는 지점이 하나 있다. kubectl describe node 의 Capacity · Allocatable 은 호스트에서 GPU 를 누가 쓰고 있는지와 무관하다. 그것은 device plugin 이 "내가 관리할 수 있다"고 신고한 개수일 뿐이다. 호스트 프로세스가 GPU 메모리를 전부 점유하고 있어도 쿠버네티스는 그 GPU 를 여전히 할당 가능하다고 본다.
| 명령 | 보여 주는 것 |
|---|---|
nvidia-smi |
실제 GPU 점유 · 메모리 사용량 (실시간) |
kubectl describe node |
device plugin 이 등록한 자원 개수 |
따라서 노출 범위를 제한하지 않은 채 호스트와 쿠버네티스가 같은 GPU 를 공유하면, 파드가 스케줄링은 되지만 실행 시점에 CUDA out of memory 나 장치 사용 중 오류로 죽는다. 회계상 분리가 곧 실제 분리여야 한다.
파드 스펙에 NVIDIA_VISIBLE_DEVICES 를 직접 넣어 특정 GPU 를 지정하는 예시가 돌아다니지만, 운영에서는 피한다. device plugin 이 할당한 장치를 컨테이너 런타임이 다시 덮어쓰는 형태라 스케줄러가 아는 할당 상태와 실제가 어긋난다. 두 파드가 같은 GPU 를 잡거나, 반대로 할당받은 GPU 를 못 쓰는 상황이 생긴다.
파드에서 지정해야 할 것은 자원 요청뿐이다.
resources:
limits:
nvidia.com/gpu: 1
| 방식 | 격리 수준 | 쓰는 상황 |
|---|---|---|
| 장치 목록 제한 | 장치 단위, 완전 격리 | 호스트 프로세스와 공존 |
| MIG | 하드웨어 파티션, 완전 격리 | A100 · H100 을 여러 워크로드에 나눔 |
| time-slicing | 시간 분할, 메모리 격리 없음 | 개발 · 시험용, 메모리 여유 있을 때 |
MIG 는 A100 · H100 계열에서만 지원되고, 파티션 구성을 바꾸려면 GPU 를 쓰는 프로세스가 전부 내려가야 한다. time-slicing 은 메모리를 격리하지 않으므로 한 파드의 OOM 이 다른 파드를 끌고 들어간다.