GPU Operator 와 타임슬라이싱 · 드라이버는 애플리케이션이 아니라 커널 모듈이다 · 실습
커널이 한 칸 올라가자 그 노드만 드라이버가 없다
목표
노드 라벨을 사실의 자리로 삼아 미리 컴파일된 드라이버 이미지의 태그를 만드는 도구와 어긋난 노드를 찾아내는 점검기를 만들고, 노드가 늘었을 때 무엇이 먼저 필요한지 겪고, PodDisruptionBudget 이 막는 자리를 지나 드레인부터 uncordon 까지 업그레이드 절차를 끝까지 걸어 봅니다.
왜 중요한가
GPU 드라이버는 애플리케이션이 아니라 커널 모듈입니다. GPU Operator 가 드라이버를 컨테이너로 올리더라도 그 컨테이너가 하는 일은 호스트 커널에 모듈을 적재하는 것이고, 그래서 커널이 바뀌면 드라이버도 그 커널에 맞춰 다시 빌드돼야 합니다. 미리 컴파일된 드라이버 이미지의 태그가 <드라이버브랜치>-<커널판>-<OS태그> 인 것은 이 사실을 이름에 박아 둔 것입니다. 여기서 두 가지가 따라 나옵니다. 첫째, 노드가 한 대 늘거나 커널이 한 칸 올라가는 것은 곧 이미지 작업입니다 — 모르고 지나가면 그 노드에서만 드라이버 파드가 이미지 없음으로 죽습니다. 둘째, 드라이버를 올리려면 그 노드의 GPU 워크로드를 먼저 내려야 하는데, 그 일은 eviction API 를 거치므로 PodDisruptionBudget 이 업그레이드를 막습니다. 업그레이드가 한 노드에서 멈췄다는 신고의 상당수가 드라이버 문제가 아니라 예산 문제이고, 원인을 모르면 드라이버 로그만 몇 시간 읽게 됩니다.
단계
1. /root/gpudrv 에서 작업합니다(export KUBECONFIG=/root/.kube/config). 먼저 네임스페이스 gpu-drv 를 만드세요(뒤 단계의 파드가 여기에 뜹니다). 세 노드에 라벨을 붙이세요. lab-node-0: nvidia.com/gpu.present=true, nvidia.com/cuda.driver.major=550, nvidia.com/cuda.driver.minor=90, nvidia.com/cuda.driver.rev=07, feature.node.kubernetes.io/kernel-version.full=5.15.0-119-generic, feature.node.kubernetes.io/system-os_release.ID=ubuntu, feature.node.kubernetes.io/system-os_release.VERSION_ID=22.04. lab-node-1: 같은 키로 드라이버 535/183/06, 커널 5.15.0-107-generic, ubuntu 22.04. lab-node-2: 드라이버 550/90/07, 커널 6.8.0-45-generic, ubuntu 24.04. 세 노드 모두 nvidia.com/gpu.present=true 입니다.
2. /root/gpudrv/drv-tag.sh <노드이름> 을 만드세요. 그 노드의 라벨을 읽어 미리 컴파일된 드라이버 이미지의 태그를 한 줄로 출력합니다. 형식은 공식 문서의 <드라이버브랜치>-<커널판>-<OS태그> 이고, 브랜치는 nvidia.com/cuda.driver.major, 커널판은 feature.node.kubernetes.io/kernel-version.full, OS 태그는 system-os_release.ID 와 system-os_release.VERSION_ID 를 붙여 쓴 것입니다 (예: ubuntu 와 22.04 이면 ubuntu22.04). 세 노드에 차례로 돌려 /root/gpudrv/out/tags.txt 에 <노드> <태그> 꼴로 세 줄을 적으세요. 노드 이름이나 태그를 스크립트 안에 적어 두지 마세요 — 채점기가 노드마다 직접 불러 봅니다.
3. /root/gpudrv/support-matrix.csv 를 만드세요. 첫 줄은 머리글 driver_branch,kernel,os_tag 이고, 그 뒤로 사내 레지스트리에 실제로 빌드해 둔 조합 세 줄을 적습니다 — 550,5.15.0-119-generic,ubuntu22.04, 535,5.15.0-107-generic,ubuntu22.04, 550,6.8.0-45-generic,ubuntu24.04. 그리고 /root/gpudrv/drv-audit.sh 를 만드세요 — nvidia.com/gpu.present=true 인 모든 노드를 돌며 그 노드의 조합이 이 표에 있는지 보고, 없으면 <노드> MISSING <태그> 를 한 줄씩 내고 종료 코드 1 로, 하나도 없으면 OK 한 줄과 0 으로 끝냅니다. 만든 뒤 돌려 출력을 /root/gpudrv/out/audit.txt 에 저장하세요(지금은 OK 여야 합니다).
4. /root/gpudrv/k8s/node3.yaml 로 새 노드 lab-node-3 을 클러스터에 넣으세요. 라벨은 nvidia.com/gpu.present=true, 드라이버 550/90/07, 커널 6.8.0-52-generic, ubuntu 24.04 이고, kwok.x-k8s.io/node: fake 어노테이션과 Ready 조건을 갖춘 가짜 노드입니다(형식은 예시를 보세요). 적용한 뒤 drv-audit.sh 를 돌려 출력을 /root/gpudrv/out/skew.txt 에 저장하세요 — 새 노드가 걸려야 정상입니다. 그 다음 그 조합을 빌드해 레지스트리에 올렸다고 치고 support-matrix.csv 에 줄을 하나 더해, 다시 돌린 출력을 /root/gpudrv/out/skew-fixed.txt 에 저장하세요(이제 OK 여야 합니다).
5. 1단계에서 만든 네임스페이스 gpu-drv 에 파드 둘을 올립니다. /root/gpudrv/k8s/cuda12-job.yaml 에 파드 cuda12-job 을 쓰세요 — 컨테이너 이름 trainer, 이미지 nvcr.io/nvidia/pytorch:24.07-py3, required nodeAffinity 의 한 term 안에 조건 두 개입니다: nvidia.com/cuda.driver.major 가 In 550, 그리고 feature.node.kubernetes.io/system-os_release.VERSION_ID 가 In 22.04. /root/gpudrv/k8s/cuda13-job.yaml 에 파드 cuda13-job 을 쓰세요 — 이미지는 nvcr.io/nvidia/pytorch:25.03-py3, 조건은 nvidia.com/cuda.driver.major 가 Gt 560 하나입니다. 둘 다 적용한 뒤 cuda12-job 은 lab-node-0 에서 뜨고 cuda13-job 은 대기하는 것을 확인하고, cuda13-job 의 PodScheduled 조건 메시지를 /root/gpudrv/out/cuda.txt 에 저장하세요.
6. /root/gpudrv/k8s/trainer.yaml 에 디플로이먼트 trainer 를 쓰세요 — 네임스페이스 gpu-drv, replicas: 4, 파드 라벨과 셀렉터는 app: trainer, 컨테이너 trainer, 이미지 nvcr.io/nvidia/pytorch:24.07-py3, 그리고 required podAntiAffinity 로 topologyKey: kubernetes.io/hostname 에 대해 같은 app: trainer 끼리 한 노드에 둘이 못 오게 하세요(노드 네 대에 하나씩 퍼집니다). /root/gpudrv/k8s/pdb.yaml 에 PodDisruptionBudget trainer-pdb 를 쓰세요 — minAvailable: 4, 셀렉터는 app: trainer. 네 파드가 모두 Running 이 된 뒤 kubectl drain lab-node-1 --ignore-daemonsets --delete-emptydir-data --timeout=20s 를 돌리고 출력을 표준 오류까지 함께 /root/gpudrv/out/drain-blocked.txt 에 저장하세요. 거절당해야 정상입니다.
7. 업그레이드 순서를 그대로 밟습니다. (1) lab-node-1 을 드라이버 550.90.07 로 올리면 필요한 태그가 550-5.15.0-107-generic-ubuntu22.04 입니다 — 그 조합을 support-matrix.csv 에 먼저 더하세요(이미지를 확보한 셈입니다). (2) 예산에 여유를 만드세요 — trainer-pdb 의 minAvailable 을 3 으로 낮춥니다. (3) 같은 drain 명령을 다시 돌려 이번에는 성공시키고 출력을 /root/gpudrv/out/drain-ok.txt 에 저장하세요. (4) lab-node-1 의 드라이버 라벨 셋을 550/90/07 로 바꾸세요(드라이버를 새로 올린 셈입니다 — 이 환경에서 실제 설치는 하지 않습니다). (5) kubectl uncordon lab-node-1 로 노드를 되돌리세요. 마지막으로 drv-audit.sh 를 다시 돌려 OK 가 나오는지 확인하세요.
8. GPU Operator 의 업그레이드 컨트롤러는 노드 라벨 nvidia.com/gpu-driver-upgrade-state 로 진행 상태를 나타냅니다. /root/gpudrv/out/upgrade-states.txt 에 그 상태를 문서에 나오는 차례대로 여덟 줄 적으세요 — upgrade-required, cordon-required, pod-deletion-required, drain-required, pod-restart-required, validation-required, uncordon-required, upgrade-done. 그리고 lab-node-1 에 nvidia.com/gpu-driver-upgrade-state=upgrade-done 라벨을 붙이세요. 마지막으로 /root/gpudrv/out/upgrade-report.txt 에 다섯 줄을 적으세요 — NODES=<gpu.present 가 true 인 노드 수>, SKEW=<drv-audit.sh 가 낸 MISSING 줄 수>, LAB_NODE_1_TAG=<drv-tag.sh 가 lab-node-1 에 대해 내는 태그>, MATRIX_ROWS=<support-matrix.csv 의 머리글을 뺀 줄 수>, UPGRADE_STATE=upgrade-done. 숫자와 태그는 지금 상태에서 명령으로 뽑아 채우세요.
참고
export KUBECONFIG=/root/.kube/config로 시작합니다. 노드는 lab-node-0/1/2 셋으로 시작하고 4단계에서 한 대를 직접 늘립니다. 산출물은/root/gpudrv, 오브젝트는 네임스페이스gpu-drv에 둡니다.- 드라이버를 실제로 설치하지 않습니다. 이 환경에는 GPU 도 커널 모듈도 nvidia-smi 도 없습니다. 그래서 커널 판과 드라이버 판은 노드 라벨로 표현하고, 업그레이드는 그 라벨을 바꾸는 것으로 나타냅니다. 실제 클러스터에서도 스케줄러와 오퍼레이터가 보는 것은 결국 그 라벨입니다.
- 반면 노드 추가·nodeAffinity·podAntiAffinity·PodDisruptionBudget·eviction 거부·drain·uncordon 은 진짜 컨트롤 플레인이 하는 일이라 그대로 동작합니다. 이 실습이 판정하는 것도 그쪽입니다.
- 흔한 실수:
--timeout없이 drain 을 돌리는 것. 예산에 막히면 영원히 다시 시도합니다. - 흔한 실수: 드레인이 끝난 뒤 uncordon 을 빠뜨리는 것. 그 노드는 오류 없이 조용히 놀게 됩니다.
- 흔한 실수: 점검기 안에 노드 이름이나 태그를 적어 두는 것. 노드가 늘면 그 도구는 그날부터 거짓말을 합니다.
- [GPU Driver Upgrades](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-driver-upgrades.html) · [Precompiled Driver Containers](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/precompiled-drivers.html) · [Safely Drain a Node](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) · [Specifying a Disruption Budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/)
단계 8개
- 커널 판과 드라이버 판을 노드의 사실로 세운다
- 노드 하나가 필요로 하는 드라이버 이미지 태그를 계산한다
- 레지스트리에 있는 조합과 대조해 어긋난 노드를 찾는다
- 노드가 한 대 늘면 드라이버 이미지부터 모자란다
- CUDA 는 앞으로만 호환된다 — 그 요구를 라벨 조건으로 적는다
- 업그레이드를 막는 것은 드라이버가 아니라 예산이다
- 이미지를 먼저 확보하고, 여유를 만들고, 절차를 끝까지 걷는다
- 업그레이드 상태 기계와 마무리 보고